From 1be6dfa8e3bfa19b05cfec318371c1ff417d5c11 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:22:05 +0100 Subject: [PATCH 01/29] feat(core): add option to enable/disable replacing id when updating an edge --- packages/core/src/utils/graph.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index c7772a1c..4b2983b1 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -93,32 +93,34 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => return edges.concat(edge); }; -export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[]): Edge[] => { +export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[], shouldReplaceId = true): Edge[] => { + const { id: oldEdgeId, ...rest } = oldEdge; + if (!newConnection.source || !newConnection.target) { devWarn('006', errorMessages['006']()); return edges; } - const foundEdge = edges.find((e) => e.id === oldEdge.id) as Edge; + const foundEdge = edges.find((e) => e.id === oldEdgeId) as Edge; if (!foundEdge) { - devWarn('007', errorMessages['007'](oldEdge.id)); + devWarn('007', errorMessages['007'](oldEdgeId)); return edges; } // Remove old edge and create the new edge with parameters of old edge. const edge = { - ...oldEdge, - id: getEdgeId(newConnection), + ...rest, + id: shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId, source: newConnection.source, target: newConnection.target, sourceHandle: newConnection.sourceHandle, targetHandle: newConnection.targetHandle, } as Edge; - return edges.filter((e) => e.id !== oldEdge.id).concat(edge); + return edges.filter((e) => e.id !== oldEdgeId).concat(edge); }; export const pointToRendererPoint = ( From 16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 16 Feb 2023 19:46:29 +0100 Subject: [PATCH 02/29] chore(changeset): add --- .changeset/yellow-chicken-sparkle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yellow-chicken-sparkle.md diff --git a/.changeset/yellow-chicken-sparkle.md b/.changeset/yellow-chicken-sparkle.md new file mode 100644 index 00000000..f729cf0e --- /dev/null +++ b/.changeset/yellow-chicken-sparkle.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': minor +--- + +Add option to enable/disable replacing edge id when using `updateEdge` From 5becbb1ebe331c822ca7d8e71a2d7af49505975d Mon Sep 17 00:00:00 2001 From: stffabi Date: Fri, 17 Feb 2023 08:28:48 +0100 Subject: [PATCH 03/29] feat(resizer): Add support for maxWidth and maxHeight --- packages/node-resizer/src/NodeResizer.tsx | 6 ++++++ packages/node-resizer/src/ResizeControl.tsx | 8 +++++--- packages/node-resizer/src/types.ts | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/node-resizer/src/NodeResizer.tsx b/packages/node-resizer/src/NodeResizer.tsx index c7a3b8a6..2560f60f 100644 --- a/packages/node-resizer/src/NodeResizer.tsx +++ b/packages/node-resizer/src/NodeResizer.tsx @@ -14,6 +14,8 @@ export default function NodeResizer({ color, minWidth = 10, minHeight = 10, + maxWidth = Number.MAX_VALUE, + maxHeight = Number.MAX_VALUE, shouldResize, onResizeStart, onResize, @@ -36,6 +38,8 @@ export default function NodeResizer({ color={color} minWidth={minWidth} minHeight={minHeight} + maxWidth={maxWidth} + maxHeight={maxHeight} onResizeStart={onResizeStart} shouldResize={shouldResize} onResize={onResize} @@ -52,6 +56,8 @@ export default function NodeResizer({ color={color} minWidth={minWidth} minHeight={minHeight} + maxWidth={maxWidth} + maxHeight={maxHeight} onResizeStart={onResizeStart} shouldResize={shouldResize} onResize={onResize} diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx index 3397537d..1d1be3e2 100644 --- a/packages/node-resizer/src/ResizeControl.tsx +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -32,6 +32,8 @@ function ResizeControl({ color, minWidth = 10, minHeight = 10, + maxWidth = Number.MAX_VALUE, + maxHeight = Number.MAX_VALUE, shouldResize, onResizeStart, onResize, @@ -96,8 +98,8 @@ function ResizeControl({ const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current; const distX = Math.floor(enableX ? xSnapped - startX : 0); const distY = Math.floor(enableY ? ySnapped - startY : 0); - const width = Math.max(startWidth + (invertX ? -distX : distX), minWidth); - const height = Math.max(startHeight + (invertY ? -distY : distY), minHeight); + const width = Math.min(Math.max(startWidth + (invertX ? -distX : distX), minWidth), maxWidth); + const height = Math.min(Math.max(startHeight + (invertY ? -distY : distY), minHeight), maxHeight); const isWidthChange = width !== prevWidth; const isHeightChange = height !== prevHeight; @@ -183,7 +185,7 @@ function ResizeControl({ return () => { selection.on('.drag', null); }; - }, [id, controlPosition, minWidth, minHeight, getPointerPosition]); + }, [id, controlPosition, minWidth, minHeight, maxWidth, maxHeight, getPointerPosition]); const positionClassNames = controlPosition.split('-'); const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor'; diff --git a/packages/node-resizer/src/types.ts b/packages/node-resizer/src/types.ts index 75d40ace..bd0862a4 100644 --- a/packages/node-resizer/src/types.ts +++ b/packages/node-resizer/src/types.ts @@ -29,6 +29,8 @@ export type NodeResizerProps = { isVisible?: boolean; minWidth?: number; minHeight?: number; + maxWidth?: number; + maxHeight?: number; shouldResize?: ShouldResize; onResizeStart?: OnResizeStart; onResize?: OnResize; @@ -46,7 +48,7 @@ export enum ResizeControlVariant { export type ResizeControlProps = Pick< NodeResizerProps, - 'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd' + 'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'maxWidth' | 'maxHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd' > & { position?: ControlPosition; variant?: ResizeControlVariant; From 24d4c56343ff660c2ec276a638a317aeb50b0ed7 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:59:43 +0100 Subject: [PATCH 04/29] chore(core,types): add `UpdateEdgeOptions` type --- packages/core/src/types/general.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index f628727e..6a25c612 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -277,3 +277,7 @@ export type SelectionRect = Rect & { }; export type OnError = (id: string, message: string) => void; + +export interface UpdateEdgeOptions { + shouldReplaceId?: boolean; +} \ No newline at end of file From eeed4478503e0ededc7bdd69a74349c2f3be9ad9 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 2 Mar 2023 17:00:19 +0100 Subject: [PATCH 05/29] chore(core,utils): add options obj to updateEdge util --- packages/core/src/utils/graph.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 4b2983b1..0d28d659 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -12,6 +12,7 @@ import { Rect, NodeInternals, NodeOrigin, + UpdateEdgeOptions, } from '../types'; import { errorMessages } from '../contants'; @@ -93,7 +94,7 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => return edges.concat(edge); }; -export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[], shouldReplaceId = true): Edge[] => { +export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[], options: UpdateEdgeOptions = { shouldReplaceId: true }): Edge[] => { const { id: oldEdgeId, ...rest } = oldEdge; if (!newConnection.source || !newConnection.target) { @@ -113,7 +114,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge // Remove old edge and create the new edge with parameters of old edge. const edge = { ...rest, - id: shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId, + id: options.shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId, source: newConnection.source, target: newConnection.target, sourceHandle: newConnection.sourceHandle, From 99ec9f0f034e67799075e5334c4a364091d1eba6 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 2 Mar 2023 17:42:07 +0100 Subject: [PATCH 06/29] feat(reactflow): add isValidConnection prop closes #2876 --- .../vite-app/src/examples/Validation/index.tsx | 15 ++++++++++++--- .../core/src/components/Edges/wrapEdge.tsx | 8 ++++++-- packages/core/src/components/Handle/index.tsx | 18 ++++++++++++------ .../core/src/components/StoreUpdater/index.tsx | 3 +++ .../core/src/container/ReactFlow/index.tsx | 2 ++ packages/core/src/store/index.ts | 5 +++-- packages/core/src/store/initialState.ts | 1 + packages/core/src/types/component-props.ts | 2 ++ packages/core/src/types/general.ts | 4 ++++ 9 files changed, 45 insertions(+), 13 deletions(-) diff --git a/examples/vite-app/src/examples/Validation/index.tsx b/examples/vite-app/src/examples/Validation/index.tsx index 1488b48c..c55d8178 100644 --- a/examples/vite-app/src/examples/Validation/index.tsx +++ b/examples/vite-app/src/examples/Validation/index.tsx @@ -12,6 +12,8 @@ import ReactFlow, { OnConnectStart, OnConnectEnd, OnConnect, + updateEdge, + Edge, } from 'reactflow'; import styles from './validation.module.css'; @@ -28,15 +30,15 @@ const isValidConnection = (connection: Connection) => connection.target === 'B'; const CustomInput: FC = () => ( <>
Only connectable with B
- + ); const CustomNode: FC = ({ id }) => ( <> - +
{id}
- + ); @@ -74,6 +76,11 @@ const ValidationFlow = () => { [value] ); + const onEdgeUpdate = useCallback( + (oldEdge: Edge, newConnection: Connection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)), + [setEdges] + ); + return ( { nodeTypes={nodeTypes} onConnectStart={onConnectStart} onConnectEnd={onConnectEnd} + onEdgeUpdate={onEdgeUpdate} + isValidConnection={isValidConnection} fitView /> ); diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 80a6be7d..607f2c5a 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -11,6 +11,8 @@ import { getMouseHandler } from './utils'; import { elementSelectionKeys } from '../../utils'; import type { EdgeProps, WrapEdgeProps, Connection } from '../../types'; +const alwaysValidConnection = () => true; + export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ id, @@ -94,12 +96,14 @@ export default (EdgeComponent: ComponentType) => { return; } + const { edges, isValidConnection: isValidConnectionStore } = store.getState(); const nodeId = isSourceHandle ? target : source; const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null; const handleType = isSourceHandle ? 'target' : 'source'; - const isValidConnection = () => true; + const isValidConnection = isValidConnectionStore || alwaysValidConnection; + const isTarget = isSourceHandle; - const edge = store.getState().edges.find((e) => e.id === id)!; + const edge = edges.find((e) => e.id === id)!; setUpdating(true); onEdgeUpdateStart?.(event, edge, handleType); diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index dab16653..e2d57d4d 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -27,7 +27,7 @@ const Handle = forwardRef( { type = 'source', position = Position.Top, - isValidConnection = alwaysValid, + isValidConnection, isConnectable = true, id, onConnect, @@ -61,8 +61,8 @@ const Handle = forwardRef( ...params, }; if (hasDefaultEdges) { - const { edges } = store.getState(); - store.setState({ edges: addEdge(edgeParams, edges) }); + const { edges, setEdges } = store.getState(); + setEdges(addEdge(edgeParams, edges)); } onConnectAction?.(edgeParams); @@ -81,7 +81,7 @@ const Handle = forwardRef( isTarget, getState: store.getState, setState: store.setState, - isValidConnection, + isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid, }); } @@ -93,7 +93,12 @@ const Handle = forwardRef( }; const onClick = (event: ReactMouseEvent) => { - const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState(); + const { + onClickConnectStart, + onClickConnectEnd, + connectionMode, + isValidConnection: isValidConnectionStore, + } = store.getState(); if (!connectionStartHandle) { onClickConnectStart?.(event, { nodeId, handleId, handleType: type }); store.setState({ connectionStartHandle: { nodeId, type, handleId } }); @@ -101,6 +106,7 @@ const Handle = forwardRef( } const doc = getHostForElement(event.target as HTMLElement); + const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid; const { connection, isValid } = isValidHandle( event, { @@ -112,7 +118,7 @@ const Handle = forwardRef( connectionStartHandle.nodeId, connectionStartHandle.handleId || null, connectionStartHandle.type, - isValidConnection, + isValidConnectionHandler, doc ); diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index febd3b0c..4491bb94 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -49,6 +49,7 @@ type StoreUpdaterProps = Pick< | 'autoPanOnNodeDrag' | 'onError' | 'connectionRadius' + | 'isValidConnection' > & { rfId: string }; const selector = (s: ReactFlowState) => ({ @@ -127,6 +128,7 @@ const StoreUpdater = ({ autoPanOnNodeDrag, onError, connectionRadius, + isValidConnection, }: StoreUpdaterProps) => { const { setNodes, @@ -184,6 +186,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); useDirectStoreUpdater('onError', onError, store.setState); useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState); + useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState); useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 9e7b7a49..9fa23f9c 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -163,6 +163,7 @@ const ReactFlow = forwardRef( autoPanOnConnect = true, autoPanOnNodeDrag = true, connectionRadius = 20, + isValidConnection, onError, style, id, @@ -295,6 +296,7 @@ const ReactFlow = forwardRef( autoPanOnNodeDrag={autoPanOnNodeDrag} onError={onError} connectionRadius={connectionRadius} + isValidConnection={isValidConnection} /> {children} diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index c85225db..79d95193 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -33,8 +33,9 @@ const createRFStore = () => return Array.from(get().nodeInternals.values()); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = {} } = get(); - set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); + const { defaultEdgeOptions = null, isValidConnection } = get(); + const validEdges = isValidConnection ? edges.filter(isValidConnection) : edges; + set({ edges: defaultEdgeOptions ? validEdges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); }, setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => { const hasDefaultNodes = typeof nodes !== 'undefined'; diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index 99e9d298..55aca27f 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -62,6 +62,7 @@ const initialState: ReactFlowStore = { autoPanOnNodeDrag: true, connectionRadius: 20, onError: devWarn, + isValidConnection: undefined, }; export default initialState; diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index 1614805a..cae9da3b 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -38,6 +38,7 @@ import type { SelectionMode, OnError, } from '.'; +import { ValidConnectionFunc } from '../components/Handle/utils'; export type ReactFlowProps = HTMLAttributes & { nodes?: Node[]; @@ -144,6 +145,7 @@ export type ReactFlowProps = HTMLAttributes & { autoPanOnConnect?: boolean; connectionRadius?: number; onError?: OnError; + isValidConnection?: ValidConnectionFunc; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index f628727e..a34f4a73 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -61,6 +61,8 @@ export interface Connection { targetHandle: string | null; } +export type IsValidConnection = (edge: Edge | Connection) => boolean; + export type ConnectionStatus = 'valid' | 'invalid'; export enum ConnectionMode { @@ -223,6 +225,8 @@ export type ReactFlowStore = { autoPanOnConnect: boolean; autoPanOnNodeDrag: boolean; connectionRadius: number; + + isValidConnection?: IsValidConnection; }; export type ReactFlowActions = { From 5293fd74bed6c6035f0f762fbd5498b548181c89 Mon Sep 17 00:00:00 2001 From: Daniel Leal Date: Tue, 28 Feb 2023 16:37:21 +0000 Subject: [PATCH 07/29] fix(core): track modifier keys on useKey press --- .../vite-app/src/examples/Figma/index.tsx | 3 +++ .../examples/UseOnSelectionChange/index.tsx | 19 ++++++++++++++++++- packages/core/src/hooks/useKeyPress.ts | 11 +++++++++-- packages/core/src/utils/index.ts | 5 ++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/examples/vite-app/src/examples/Figma/index.tsx b/examples/vite-app/src/examples/Figma/index.tsx index 22f0dcce..64cfb4bb 100644 --- a/examples/vite-app/src/examples/Figma/index.tsx +++ b/examples/vite-app/src/examples/Figma/index.tsx @@ -38,6 +38,9 @@ const BasicFlow = () => { onSelectionContextMenu={onPaneContextMenu} > +
+ +
); }; diff --git a/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx index 160ca769..b6f5531f 100644 --- a/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx +++ b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx @@ -18,6 +18,20 @@ const initialNodes: Node[] = [ data: { label: 'Node 1' }, position: { x: 250, y: 5 }, }, + { + id: '2', + type: 'default', + data: { label: 'Node 2' }, + position: { x: 250, y: 100 }, + }, +]; + +const initialEdges: Edge[] = [ + { + id: 'e1-2', + source: '1', + target: '2', + }, ]; const SelectionLogger = () => { @@ -34,7 +48,7 @@ const SelectionLogger = () => { const Flow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); return ( @@ -52,6 +66,9 @@ const WrappedFlow = () => ( +
+ +
); diff --git a/packages/core/src/hooks/useKeyPress.ts b/packages/core/src/hooks/useKeyPress.ts index 20dcf37e..3301fc8f 100644 --- a/packages/core/src/hooks/useKeyPress.ts +++ b/packages/core/src/hooks/useKeyPress.ts @@ -19,6 +19,9 @@ const doc = typeof document !== 'undefined' ? document : null; export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { target: doc }): boolean => { const [keyPressed, setKeyPressed] = useState(false); + // we need to remember if a modifier key is pressed in order to track it + const modifierPressed = useRef(false); + // we need to remember the pressed keys in order to support combinations const pressedKeys = useRef(new Set([])); @@ -43,7 +46,10 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { useEffect(() => { if (keyCode !== null) { const downHandler = (event: KeyboardEvent) => { - if (isInputDOMNode(event)) { + + modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey; + + if (!modifierPressed.current && isInputDOMNode(event)) { return false; } const keyOrCode = useKeyOrCode(event.code, keysToWatch); @@ -56,7 +62,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { }; const upHandler = (event: KeyboardEvent) => { - if (isInputDOMNode(event)) { + if (!modifierPressed.current && isInputDOMNode(event)) { return false; } const keyOrCode = useKeyOrCode(event.code, keysToWatch); @@ -67,6 +73,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { } else { pressedKeys.current.delete(event[keyOrCode]); } + modifierPressed.current = false; }; const resetHandler = () => { diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 2d801d05..b4f0ca3f 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -104,11 +104,10 @@ export function isInputDOMNode(event: KeyboardEvent | ReactKeyboardEvent): boole const target = (kbEvent.composedPath?.()?.[0] || event.target) as HTMLElement; const isInput = ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || target?.hasAttribute('contenteditable'); - // we want to be able to do a multi selection event if we are in an input field - const isModifierKey = event.ctrlKey || event.metaKey || event.shiftKey; + // when an input field is focused we don't want to trigger deletion or movement of nodes - return (isInput && !isModifierKey) || !!target?.closest('.nokey'); + return isInput || !!target?.closest('.nokey'); } export const isMouseEvent = ( From f783bdc25fbe7ca102c7c02a08688434a7121aec Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 14:58:32 +0100 Subject: [PATCH 08/29] refactor(store): dont use isValidConn for edges --- packages/core/src/store/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index 79d95193..056957c3 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -33,9 +33,8 @@ const createRFStore = () => return Array.from(get().nodeInternals.values()); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = null, isValidConnection } = get(); - const validEdges = isValidConnection ? edges.filter(isValidConnection) : edges; - set({ edges: defaultEdgeOptions ? validEdges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); + const { defaultEdgeOptions = null } = get(); + set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); }, setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => { const hasDefaultNodes = typeof nodes !== 'undefined'; From b88865140c72fa7e92a883498768000cb2cc96a7 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 14:59:59 +0100 Subject: [PATCH 09/29] chore(changeset): add --- .changeset/thick-garlics-tap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-garlics-tap.md diff --git a/.changeset/thick-garlics-tap.md b/.changeset/thick-garlics-tap.md new file mode 100644 index 00000000..14705425 --- /dev/null +++ b/.changeset/thick-garlics-tap.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': minor +--- + +add isValidConnection prop for ReactFlow component From 5fabd2720f6367f75f79a45822d8f675a3b8e1cf Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 15:24:17 +0100 Subject: [PATCH 10/29] chore(changeset): add --- .changeset/rare-points-change.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rare-points-change.md diff --git a/.changeset/rare-points-change.md b/.changeset/rare-points-change.md new file mode 100644 index 00000000..9ce10e25 --- /dev/null +++ b/.changeset/rare-points-change.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': major +--- + +track modifier keys on useKeypress From 06ae32d9825d54e7829b64616d9e3f4f1d8808d9 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 15:31:44 +0100 Subject: [PATCH 11/29] fix(handles): check if handle is connectable closes #2869 --- packages/core/src/components/Handle/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/Handle/utils.ts b/packages/core/src/components/Handle/utils.ts index 58263781..bfd181e1 100644 --- a/packages/core/src/components/Handle/utils.ts +++ b/packages/core/src/components/Handle/utils.ts @@ -104,9 +104,10 @@ export function isValidHandle( // in strict mode we don't allow target to target or source to source connections const isValid = - connectionMode === ConnectionMode.Strict + handleToCheck.classList.contains('connectable') && + (connectionMode === ConnectionMode.Strict ? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target') - : handleNodeId !== fromNodeId || handleId !== fromHandleId; + : handleNodeId !== fromNodeId || handleId !== fromHandleId); if (isValid) { result.isValid = isValidConnection(connection); From 8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 15:32:46 +0100 Subject: [PATCH 12/29] chore(chnageset): add --- .changeset/real-cats-sneeze.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/real-cats-sneeze.md diff --git a/.changeset/real-cats-sneeze.md b/.changeset/real-cats-sneeze.md new file mode 100644 index 00000000..2b6c0dcd --- /dev/null +++ b/.changeset/real-cats-sneeze.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': major +--- + +fix: check if handle is connectable From e3eb256c772707c18c7d56488f486413015156d9 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 15:33:32 +0100 Subject: [PATCH 13/29] chore(changesets): adjust --- .changeset/rare-points-change.md | 2 +- .changeset/real-cats-sneeze.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/rare-points-change.md b/.changeset/rare-points-change.md index 9ce10e25..28a38179 100644 --- a/.changeset/rare-points-change.md +++ b/.changeset/rare-points-change.md @@ -1,5 +1,5 @@ --- -'@reactflow/core': major +'@reactflow/core': patch --- track modifier keys on useKeypress diff --git a/.changeset/real-cats-sneeze.md b/.changeset/real-cats-sneeze.md index 2b6c0dcd..3f8f02bc 100644 --- a/.changeset/real-cats-sneeze.md +++ b/.changeset/real-cats-sneeze.md @@ -1,5 +1,5 @@ --- -'@reactflow/core': major +'@reactflow/core': patch --- fix: check if handle is connectable From 738320038839bf557f1e9a729294b66917748f0c Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 16:35:24 +0100 Subject: [PATCH 14/29] fix(nodes): blur when node gets unselected closes #2887, closes #2886 --- packages/core/src/components/Nodes/utils.ts | 6 +++++- packages/core/src/components/Nodes/wrapNode.tsx | 6 +++--- packages/core/src/hooks/useDrag/index.ts | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/Nodes/utils.ts b/packages/core/src/components/Nodes/utils.ts index dca9f297..174b1be7 100644 --- a/packages/core/src/components/Nodes/utils.ts +++ b/packages/core/src/components/Nodes/utils.ts @@ -1,4 +1,4 @@ -import { MouseEvent } from 'react'; +import { MouseEvent, RefObject } from 'react'; import { StoreApi } from 'zustand'; import { getDimensions } from '../../utils'; @@ -58,6 +58,7 @@ export function handleNodeClick({ id, store, unselect = false, + nodeRef, }: { id: string; store: { @@ -65,6 +66,7 @@ export function handleNodeClick({ setState: StoreApi['setState']; }; unselect?: boolean; + nodeRef?: RefObject; }) { const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeInternals } = store.getState(); const node = nodeInternals.get(id)!; @@ -75,5 +77,7 @@ export function handleNodeClick({ addSelectedNodes([id]); } else if (unselect || (node.selected && multiSelectionActive)) { unselectNodesAndEdges({ nodes: [node] }); + + requestAnimationFrame(() => nodeRef?.current?.blur()); } } diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx index f56f117b..6f95dbdd 100644 --- a/packages/core/src/components/Nodes/wrapNode.tsx +++ b/packages/core/src/components/Nodes/wrapNode.tsx @@ -74,6 +74,7 @@ export default (NodeComponent: ComponentType) => { handleNodeClick({ id, store, + nodeRef, }); } @@ -90,13 +91,12 @@ export default (NodeComponent: ComponentType) => { if (elementSelectionKeys.includes(event.key) && isSelectable) { const unselect = event.key === 'Escape'; - if (unselect) { - nodeRef.current?.blur(); - } + handleNodeClick({ id, store, unselect, + nodeRef, }); } else if ( !disableKeyboardA11y && diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 22d06daf..8821ab4a 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -153,6 +153,7 @@ function useDrag({ handleNodeClick({ id: nodeId, store, + nodeRef: nodeRef as RefObject, }); } From 83fc467545527729633e817dbccfe59d0040da4b Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 16:36:18 +0100 Subject: [PATCH 15/29] chore(changeset): add --- .changeset/khaki-spoons-cheer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/khaki-spoons-cheer.md diff --git a/.changeset/khaki-spoons-cheer.md b/.changeset/khaki-spoons-cheer.md new file mode 100644 index 00000000..135186a2 --- /dev/null +++ b/.changeset/khaki-spoons-cheer.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': patch +--- + +fix(nodes): blur when node gets unselected From d85ce8283d23c069ca852435c80deb21f147d1a1 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 16:44:45 +0100 Subject: [PATCH 16/29] chore(plugins): add data-testids closes #2854 --- packages/background/src/Background.tsx | 1 + packages/controls/src/Controls.tsx | 7 ++++++- packages/minimap/src/MiniMap.tsx | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/background/src/Background.tsx b/packages/background/src/Background.tsx index 10702d81..bf8a4336 100644 --- a/packages/background/src/Background.tsx +++ b/packages/background/src/Background.tsx @@ -59,6 +59,7 @@ function Background({ left: 0, }} ref={ref} + data-testid="rf__background" > > = ({ }; return ( - + {showZoom && ( <> + Date: Mon, 6 Mar 2023 16:45:39 +0100 Subject: [PATCH 17/29] chore(changeset): add --- .changeset/empty-carpets-reply.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/empty-carpets-reply.md diff --git a/.changeset/empty-carpets-reply.md b/.changeset/empty-carpets-reply.md new file mode 100644 index 00000000..0f28abd2 --- /dev/null +++ b/.changeset/empty-carpets-reply.md @@ -0,0 +1,8 @@ +--- +'@reactflow/background': patch +'@reactflow/controls': patch +'@reactflow/core': patch +'@reactflow/minimap': patch +--- + +add data-testid for controls, minimap and background From 8b0808ec03f22d193d923b5966df3c84055679fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=B6ller?= Date: Tue, 7 Mar 2023 16:41:15 +0100 Subject: [PATCH 18/29] feat(node-resizer): add flag to keep aspect ratio, cleanup example --- .../examples/NodeResizer/CustomResizer.tsx | 17 ++- .../examples/NodeResizer/CustomResizer2.tsx | 19 --- .../examples/NodeResizer/CustomResizer3.tsx | 33 ------ .../examples/NodeResizer/DefaultResizer.tsx | 29 +++++ .../NodeResizer/HorizontalResizer.tsx | 43 +++++++ .../examples/NodeResizer/NodeResizerNode.tsx | 44 ------- .../examples/NodeResizer/VerticalResizer.tsx | 43 +++++++ .../src/examples/NodeResizer/index.tsx | 112 +++++++++++------- packages/core/src/index.ts | 2 +- packages/node-resizer/src/NodeResizer.tsx | 3 + packages/node-resizer/src/ResizeControl.tsx | 34 +++++- packages/node-resizer/src/types.ts | 13 +- 12 files changed, 241 insertions(+), 151 deletions(-) delete mode 100644 examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx delete mode 100644 examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx create mode 100644 examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx create mode 100644 examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx delete mode 100644 examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx create mode 100644 examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx index 6cf58fd9..a36d9f18 100644 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx +++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx @@ -10,10 +10,21 @@ const controlStyle = { border: 'none', }; -const CustomNode: FC = ({ id, data }) => { +const CustomResizerNode: FC = ({ data }) => { return ( <> - + @@ -24,4 +35,4 @@ const CustomNode: FC = ({ id, data }) => { ); }; -export default memo(CustomNode); +export default memo(CustomResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx deleted file mode 100644 index 63ff061b..00000000 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizeControl } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const CustomNode: FC = ({ id, data }) => { - return ( - <> - - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx deleted file mode 100644 index 422f2982..00000000 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizeControl, OnResize, ShouldResize } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const shouldResize: ShouldResize = (event, params) => { - console.log('before resize', params); - - if (params.width > 100) { - return false; - } - - return true; -}; - -const onResize: OnResize = (event, params) => { - console.log('resize', params.direction); -}; - -const CustomNode: FC = ({ id, data }) => { - return ( - <> - - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx b/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx new file mode 100644 index 00000000..3d9649a5 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx @@ -0,0 +1,29 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; +import { NodeResizer } from '@reactflow/node-resizer'; + +import '@reactflow/node-resizer/dist/style.css'; + +const DefaultResizerNode: FC = ({ data, selected }) => { + return ( + <> + + +
{data.label}
+ + + ); +}; + +export default memo(DefaultResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx b/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx new file mode 100644 index 00000000..2798ca3f --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx @@ -0,0 +1,43 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; +import { NodeResizeControl } from '@reactflow/node-resizer'; + +import '@reactflow/node-resizer/dist/style.css'; + +const HorizontalResizerNode: FC = ({ data }) => { + return ( + <> + + + +
{data.label}
+ + + ); +}; + +export default memo(HorizontalResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx deleted file mode 100644 index 03e4960f..00000000 --- a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizer, ShouldResize, OnResize, OnResizeEnd, OnResizeStart } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const onResizeStart: OnResizeStart = (_, params) => { - console.log('resize start', params); -}; - -const onResize: OnResize = (_, params) => { - console.log('resize', params); -}; - -const onResizeEnd: OnResizeEnd = (_, params) => { - console.log('resize end', params); -}; - -const shouldResize: ShouldResize = (_, params) => { - console.log('should resize', params); - - return true; -}; - -const CustomNode: FC = ({ data, selected }) => { - return ( - <> - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx b/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx new file mode 100644 index 00000000..a3d6515e --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx @@ -0,0 +1,43 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; + +import { NodeResizeControl } from '@reactflow/node-resizer'; +import '@reactflow/node-resizer/dist/style.css'; + +const CustomNode: FC = ({ id, data }) => { + return ( + <> + + + +
{data.label}
+ + + ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/index.tsx b/examples/vite-app/src/examples/NodeResizer/index.tsx index 01bb0851..d1523443 100644 --- a/examples/vite-app/src/examples/NodeResizer/index.tsx +++ b/examples/vite-app/src/examples/NodeResizer/index.tsx @@ -1,73 +1,97 @@ import { useCallback, useState } from 'react'; -import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState, Panel } from 'reactflow'; +import ReactFlow, { Controls, addEdge, Connection, useNodesState, useEdgesState, Panel, Node, Edge } from 'reactflow'; -import NodeResizerNode from './NodeResizerNode'; +import DefaultResizer from './DefaultResizer'; import CustomResizer from './CustomResizer'; -import CustomResizer2 from './CustomResizer2'; -import CustomResizer3 from './CustomResizer3'; +import VerticalResizer from './VerticalResizer'; +import HorizontalResizer from './HorizontalResizer'; const nodeTypes = { - resizer: NodeResizerNode, + defaultResizer: DefaultResizer, customResizer: CustomResizer, - customResizer2: CustomResizer2, - customResizer3: CustomResizer3, + verticalResizer: VerticalResizer, + horizontalResizer: HorizontalResizer, }; -const initialEdges = [ - { - id: 'e1-2', - source: '1', - target: '2', - }, -]; +const nodeStyle = { + border: '1px solid #222', + fontSize: 10, + backgroundColor: '#ddd', +}; -const initialNodes = [ +const initialEdges: Edge[] = []; + +const initialNodes: Node[] = [ { id: '1', - type: 'input', - data: { label: 'An input node' }, + type: 'defaultResizer', + data: { label: 'default resizer' }, position: { x: 0, y: 0 }, - sourcePosition: Position.Right, + style: { ...nodeStyle }, }, { - id: '2', - type: 'resizer', - data: { label: 'default resizer' }, + id: '1a', + type: 'defaultResizer', + data: { + label: 'default resizer with min and max dimensions', + minWidth: 100, + minHeight: 80, + maxWidth: 200, + maxHeight: 200, + }, + position: { x: 0, y: 60 }, + style: { ...nodeStyle, width: 100, height: 80 }, + }, + { + id: '1b', + type: 'defaultResizer', + data: { label: 'default resizer with initial size and aspect ratio', keepAspectRatio: true }, position: { x: 250, y: 0 }, style: { - width: 200, - height: 150, - border: '1px solid #222', - fontSize: 10, + width: 174, + height: 123, + ...nodeStyle, }, }, { - id: '3', + id: '2', type: 'customResizer', - data: { label: 'resize control with child component' }, + data: { label: 'custom resize icon' }, + position: { x: 0, y: 200 }, + style: { width: 100, height: 60, ...nodeStyle }, + }, + { + id: '3', + type: 'verticalResizer', + data: { label: 'vertical resizer' }, position: { x: 250, y: 200 }, - style: { border: '1px solid #222', fontSize: 10, width: 100 }, + style: { ...nodeStyle }, + }, + { + id: '3a', + type: 'verticalResizer', + data: { + label: 'vertical resizer with min/maxHeight and aspect ratio', + minHeight: 50, + maxHeight: 200, + keepAspectRatio: true, + }, + position: { x: 400, y: 200 }, + style: { ...nodeStyle, height: 50 }, }, { id: '4', - type: 'customResizer2', - data: { label: 'resize controls' }, - position: { x: 100, y: 150 }, - style: { border: '1px solid #222', fontSize: 10 }, + type: 'horizontalResizer', + data: { label: 'horizontal resizer with aspect ratio', keepAspectRatio: true }, + position: { x: 250, y: 300 }, + style: { ...nodeStyle }, }, { - id: '5', - type: 'customResizer2', - data: { label: 'min width and height' }, - position: { x: 250, y: 250 }, - style: { border: '1px solid #222', fontSize: 10 }, - }, - { - id: '6', - type: 'customResizer3', - data: { label: 'resize controls' }, - position: { x: 400, y: 200 }, - style: { border: '1px solid #222', fontSize: 10 }, + id: '4a', + type: 'horizontalResizer', + data: { label: 'horizontal resizer with maxWidth', maxWidth: 300 }, + position: { x: 250, y: 400 }, + style: { ...nodeStyle }, }, ]; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c4a5b19e..158c9094 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/E export { default as SmoothStepEdge, getSmoothStepPath } from './components/Edges/SmoothStepEdge'; export { default as BaseEdge } from './components/Edges/BaseEdge'; -export { internalsSymbol, rectToBox, boxToRect, getBoundsOfRects } from './utils'; +export { internalsSymbol, rectToBox, boxToRect, getBoundsOfRects, clamp } from './utils'; export { isNode, isEdge, diff --git a/packages/node-resizer/src/NodeResizer.tsx b/packages/node-resizer/src/NodeResizer.tsx index 2560f60f..485b8efc 100644 --- a/packages/node-resizer/src/NodeResizer.tsx +++ b/packages/node-resizer/src/NodeResizer.tsx @@ -16,6 +16,7 @@ export default function NodeResizer({ minHeight = 10, maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, + keepAspectRatio = false, shouldResize, onResizeStart, onResize, @@ -41,6 +42,7 @@ export default function NodeResizer({ maxWidth={maxWidth} maxHeight={maxHeight} onResizeStart={onResizeStart} + keepAspectRatio={keepAspectRatio} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} @@ -59,6 +61,7 @@ export default function NodeResizer({ maxWidth={maxWidth} maxHeight={maxHeight} onResizeStart={onResizeStart} + keepAspectRatio={keepAspectRatio} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx index 1d1be3e2..3da40e8c 100644 --- a/packages/node-resizer/src/ResizeControl.tsx +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -9,6 +9,7 @@ import { NodeDimensionChange, useNodeId, NodePositionChange, + clamp, } from '@reactflow/core'; import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types'; @@ -20,6 +21,7 @@ const initStartValues = { ...initPrevValues, pointerX: 0, pointerY: 0, + aspectRatio: 1, }; function ResizeControl({ @@ -34,6 +36,7 @@ function ResizeControl({ minHeight = 10, maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, + keepAspectRatio = false, shouldResize, onResizeStart, onResize, @@ -55,6 +58,12 @@ function ResizeControl({ } const selection = select(resizeControlRef.current); + + const enableX = controlPosition.includes('right') || controlPosition.includes('left'); + const enableY = controlPosition.includes('bottom') || controlPosition.includes('top'); + const invertX = controlPosition.includes('left'); + const invertY = controlPosition.includes('top'); + const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { const node = store.getState().nodeInternals.get(id); @@ -71,6 +80,7 @@ function ResizeControl({ ...prevValues.current, pointerX: xSnapped, pointerY: ySnapped, + aspectRatio: prevValues.current.width / prevValues.current.height, }; onResizeStart?.(event, { ...prevValues.current }); @@ -79,10 +89,6 @@ function ResizeControl({ const { nodeInternals, triggerNodeChanges } = store.getState(); const { xSnapped, ySnapped } = getPointerPosition(event); const node = nodeInternals.get(id); - const enableX = controlPosition.includes('right') || controlPosition.includes('left'); - const enableY = controlPosition.includes('bottom') || controlPosition.includes('top'); - const invertX = controlPosition.includes('left'); - const invertY = controlPosition.includes('top'); if (node) { const changes: NodeChange[] = []; @@ -93,13 +99,29 @@ function ResizeControl({ height: startHeight, x: startNodeX, y: startNodeY, + aspectRatio, } = startValues.current; const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current; const distX = Math.floor(enableX ? xSnapped - startX : 0); const distY = Math.floor(enableY ? ySnapped - startY : 0); - const width = Math.min(Math.max(startWidth + (invertX ? -distX : distX), minWidth), maxWidth); - const height = Math.min(Math.max(startHeight + (invertY ? -distY : distY), minHeight), maxHeight); + + let width = startWidth + (invertX ? -distX : distX); + let height = startHeight + (invertY ? -distY : distY); + + width = clamp(width, minWidth, maxWidth); + height = clamp(height, minHeight, maxHeight); + + if (keepAspectRatio) { + const nextAspectRatio = width / height; + const isDiagonal = enableX && enableY; + const isHorizontal = enableX && !enableY; + const isVertical = enableY && !enableX; + + width = (nextAspectRatio <= aspectRatio && isDiagonal) || isVertical ? height * aspectRatio : width; + height = (nextAspectRatio > aspectRatio && isDiagonal) || isHorizontal ? width / aspectRatio : height; + } + const isWidthChange = width !== prevWidth; const isHeightChange = height !== prevHeight; diff --git a/packages/node-resizer/src/types.ts b/packages/node-resizer/src/types.ts index bd0862a4..ad9114b2 100644 --- a/packages/node-resizer/src/types.ts +++ b/packages/node-resizer/src/types.ts @@ -31,6 +31,7 @@ export type NodeResizerProps = { minHeight?: number; maxWidth?: number; maxHeight?: number; + keepAspectRatio?: boolean; shouldResize?: ShouldResize; onResizeStart?: OnResizeStart; onResize?: OnResize; @@ -48,7 +49,17 @@ export enum ResizeControlVariant { export type ResizeControlProps = Pick< NodeResizerProps, - 'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'maxWidth' | 'maxHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd' + | 'nodeId' + | 'color' + | 'minWidth' + | 'minHeight' + | 'maxWidth' + | 'maxHeight' + | 'keepAspectRatio' + | 'shouldResize' + | 'onResizeStart' + | 'onResize' + | 'onResizeEnd' > & { position?: ControlPosition; variant?: ResizeControlVariant; From 2a352a67de170d111bf423be819ef59a8ff3b8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=B6ller?= Date: Tue, 7 Mar 2023 16:49:15 +0100 Subject: [PATCH 19/29] refactor(node-resizer): add keepAspectRatio to dependency array, cleanup drag handler --- packages/node-resizer/src/ResizeControl.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx index 3da40e8c..27df1096 100644 --- a/packages/node-resizer/src/ResizeControl.tsx +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -103,14 +103,12 @@ function ResizeControl({ } = startValues.current; const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current; + const distX = Math.floor(enableX ? xSnapped - startX : 0); const distY = Math.floor(enableY ? ySnapped - startY : 0); - let width = startWidth + (invertX ? -distX : distX); - let height = startHeight + (invertY ? -distY : distY); - - width = clamp(width, minWidth, maxWidth); - height = clamp(height, minHeight, maxHeight); + let width = clamp(startWidth + (invertX ? -distX : distX), minWidth, maxWidth); + let height = clamp(startHeight + (invertY ? -distY : distY), minHeight, maxHeight); if (keepAspectRatio) { const nextAspectRatio = width / height; @@ -207,7 +205,7 @@ function ResizeControl({ return () => { selection.on('.drag', null); }; - }, [id, controlPosition, minWidth, minHeight, maxWidth, maxHeight, getPointerPosition]); + }, [id, controlPosition, minWidth, minHeight, maxWidth, maxHeight, keepAspectRatio, getPointerPosition]); const positionClassNames = controlPosition.split('-'); const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor'; From dcc2ce4baac0b1497e90c4f5fc31cc64952a9641 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 7 Mar 2023 17:54:39 +0100 Subject: [PATCH 20/29] refactor(resize-control): handle min max width + aspect ratio --- .../vite-app/src/examples/NodeResizer/index.tsx | 17 +++++++++++++++-- packages/node-resizer/src/ResizeControl.tsx | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/examples/vite-app/src/examples/NodeResizer/index.tsx b/examples/vite-app/src/examples/NodeResizer/index.tsx index d1523443..d94a8d8b 100644 --- a/examples/vite-app/src/examples/NodeResizer/index.tsx +++ b/examples/vite-app/src/examples/NodeResizer/index.tsx @@ -45,7 +45,14 @@ const initialNodes: Node[] = [ { id: '1b', type: 'defaultResizer', - data: { label: 'default resizer with initial size and aspect ratio', keepAspectRatio: true }, + data: { + label: 'default resizer with initial size and aspect ratio', + keepAspectRatio: true, + minWidth: 100, + minHeight: 60, + maxWidth: 400, + maxHeight: 400, + }, position: { x: 250, y: 0 }, style: { width: 174, @@ -82,7 +89,13 @@ const initialNodes: Node[] = [ { id: '4', type: 'horizontalResizer', - data: { label: 'horizontal resizer with aspect ratio', keepAspectRatio: true }, + data: { + label: 'horizontal resizer with aspect ratio', + keepAspectRatio: true, + minHeight: 20, + maxHeight: 80, + maxWidth: 300, + }, position: { x: 250, y: 300 }, style: { ...nodeStyle }, }, diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx index 27df1096..6757b2a3 100644 --- a/packages/node-resizer/src/ResizeControl.tsx +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -118,6 +118,22 @@ function ResizeControl({ width = (nextAspectRatio <= aspectRatio && isDiagonal) || isVertical ? height * aspectRatio : width; height = (nextAspectRatio > aspectRatio && isDiagonal) || isHorizontal ? width / aspectRatio : height; + + if (width >= maxWidth) { + width = maxWidth; + height = maxWidth / aspectRatio; + } else if (width <= minWidth) { + width = minWidth; + height = minWidth / aspectRatio; + } + + if (height >= maxHeight) { + height = maxHeight; + width = maxHeight * aspectRatio; + } else if (height <= minHeight) { + height = minHeight; + width = minHeight * aspectRatio; + } } const isWidthChange = width !== prevWidth; From b11908370bc438ca8d4179497cd4eb1f8c656798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=B6ller?= Date: Tue, 7 Mar 2023 17:59:46 +0100 Subject: [PATCH 21/29] chore(packages): add changeset --- .changeset/polite-pumas-mate.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/polite-pumas-mate.md diff --git a/.changeset/polite-pumas-mate.md b/.changeset/polite-pumas-mate.md new file mode 100644 index 00000000..01b6d8db --- /dev/null +++ b/.changeset/polite-pumas-mate.md @@ -0,0 +1,6 @@ +--- +'@reactflow/node-resizer': minor +'@reactflow/core': patch +--- + +node resizer: add maxWidth, maxHeight and keepAspectRatio props From 46f3f81dc43b41a164d3f1ca06db5aa912cd50b7 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 9 Mar 2023 14:01:55 +0000 Subject: [PATCH 22/29] :truck: Move 'MiniMapNodeProps' into shared types module. --- packages/minimap/src/MiniMapNode.tsx | 18 +----------------- packages/minimap/src/types.ts | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/minimap/src/MiniMapNode.tsx b/packages/minimap/src/MiniMapNode.tsx index ae000d02..54072f02 100644 --- a/packages/minimap/src/MiniMapNode.tsx +++ b/packages/minimap/src/MiniMapNode.tsx @@ -1,23 +1,7 @@ import { memo } from 'react'; -import type { CSSProperties, MouseEvent } from 'react'; +import type { MiniMapNodeProps } from './types'; import cc from 'classcat'; -interface MiniMapNodeProps { - id: string; - x: number; - y: number; - width: number; - height: number; - borderRadius: number; - className: string; - color: string; - shapeRendering: string; - strokeColor: string; - strokeWidth: number; - style?: CSSProperties; - onClick?: (event: MouseEvent, id: string) => void; -} - const MiniMapNode = ({ id, x, diff --git a/packages/minimap/src/types.ts b/packages/minimap/src/types.ts index 7d006329..ca28981a 100644 --- a/packages/minimap/src/types.ts +++ b/packages/minimap/src/types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { HTMLAttributes, MouseEvent } from 'react'; +import type { CSSProperties, HTMLAttributes, MouseEvent } from 'react'; import type { Node, PanelPosition, XYPosition } from '@reactflow/core'; export type GetMiniMapNodeAttribute = (node: Node) => string; @@ -20,3 +20,19 @@ export type MiniMapProps = Omit, ' zoomable?: boolean; ariaLabel?: string | null; }; + +export interface MiniMapNodeProps { + id: string; + x: number; + y: number; + width: number; + height: number; + borderRadius: number; + className: string; + color: string; + shapeRendering: string; + strokeColor: string; + strokeWidth: number; + style?: CSSProperties; + onClick?: (event: MouseEvent, id: string) => void; +} From 1415ed003e2fd4ceb07b88bd3e0303960c03790a Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 9 Mar 2023 14:25:23 +0000 Subject: [PATCH 23/29] :sparkles: Add an optional 'MiniMapNode' field to 'MiniMapProps'. This lets users supply a custom SVG node to render in the minimap instead of the default rectangle. --- packages/minimap/src/MiniMap.tsx | 5 ++++- packages/minimap/src/types.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index bb4a2f89..378ee4f6 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -64,6 +64,9 @@ function MiniMap({ pannable = false, zoomable = false, ariaLabel = 'React Flow mini map', + // Rename the field to avoid clashes with the default `MiniMapNode` component. + // Fallback to that component if none is provided. + MiniMapNode: CustomMiniMapNode = MiniMapNode, }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); @@ -176,7 +179,7 @@ function MiniMap({ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; return ( - = (node: Node) => string; @@ -19,6 +19,7 @@ export type MiniMapProps = Omit, ' pannable?: boolean; zoomable?: boolean; ariaLabel?: string | null; + MiniMapNode?: ComponentType; }; export interface MiniMapNodeProps { From 2acc5b7706de1f52d87323989d27c9e04bd21577 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 9 Mar 2023 14:46:46 +0000 Subject: [PATCH 24/29] :recycle: Rename 'MiniMapNode' prop to 'nodeComponent' for greater consistency. --- packages/minimap/src/MiniMap.tsx | 8 ++++---- packages/minimap/src/types.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 378ee4f6..b4bc9ee2 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -55,6 +55,9 @@ function MiniMap({ nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth = 2, + // We need to rename the prop to be `CapitalCase` so that JSX will render it as + // a component properly. + nodeComponent: NodeComponent = MiniMapNode, maskColor = 'rgb(240, 240, 240, 0.6)', maskStrokeColor = 'none', maskStrokeWidth = 1, @@ -64,9 +67,6 @@ function MiniMap({ pannable = false, zoomable = false, ariaLabel = 'React Flow mini map', - // Rename the field to avoid clashes with the default `MiniMapNode` component. - // Fallback to that component if none is provided. - MiniMapNode: CustomMiniMapNode = MiniMapNode, }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); @@ -179,7 +179,7 @@ function MiniMap({ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; return ( - = Omit, ' nodeClassName?: string | GetMiniMapNodeAttribute; nodeBorderRadius?: number; nodeStrokeWidth?: number; + nodeComponent?: ComponentType; maskColor?: string; maskStrokeColor?: string; maskStrokeWidth?: number; @@ -19,7 +20,6 @@ export type MiniMapProps = Omit, ' pannable?: boolean; zoomable?: boolean; ariaLabel?: string | null; - MiniMapNode?: ComponentType; }; export interface MiniMapNodeProps { From 8b9f2a8f011f22979c28ecbcf1f55b4a417fa47f Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 9 Mar 2023 14:48:03 +0000 Subject: [PATCH 25/29] :sparkles: Add an example flow to demo custom minimap nodes. --- examples/vite-app/src/App/index.tsx | 6 ++ .../src/examples/CustomMiniMapNode/index.tsx | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 examples/vite-app/src/examples/CustomMiniMapNode/index.tsx diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 02eaac1e..8ecdd8b8 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -5,6 +5,7 @@ import Basic from '../examples/Basic'; import Backgrounds from '../examples/Backgrounds'; import ControlledUncontrolled from '../examples/ControlledUncontrolled'; import CustomConnectionLine from '../examples/CustomConnectionLine'; +import CustomMiniMapNode from '../examples/CustomMiniMapNode'; import CustomNode from '../examples/CustomNode'; import DefaultNodes from '../examples/DefaultNodes'; import DragHandle from '../examples/DragHandle'; @@ -77,6 +78,11 @@ const routes: IRoute[] = [ path: '/custom-connectionline', component: CustomConnectionLine, }, + { + name: 'Custom Minimap Node', + path: '/custom-minimap-node', + component: CustomMiniMapNode, + }, { name: 'Custom Node', path: '/custom-node', diff --git a/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx new file mode 100644 index 00000000..fcb5b5b0 --- /dev/null +++ b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx @@ -0,0 +1,73 @@ +import { MouseEvent, CSSProperties, useCallback } from 'react'; + +import ReactFlow, { + addEdge, + Background, + BackgroundVariant, + Connection, + Controls, + Edge, + MiniMap, + Node, + ReactFlowInstance, + useEdgesState, + useNodesState, +} from 'reactflow'; + +const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); + +const buttonStyle: CSSProperties = { + position: 'absolute', + left: 10, + top: 10, + zIndex: 4, +}; + +const CustomMiniMapNodeFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]); + const addRandomNode = () => { + const nodeId = (nodes.length + 1).toString(); + const newNode: Node = { + id: nodeId, + data: { label: `Node: ${nodeId}` }, + position: { + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + }, + }; + setNodes((nds) => nds.concat(newNode)); + }; + + return ( + onConnect(p)} + onNodeDragStop={onNodeDragStop} + onlyRenderVisibleElements={false} + > + + + + + + + ); +}; + +const CustomMiniMapNode = ({ x, y, width, height }) => ( + +) + +export default CustomMiniMapNodeFlow; From 84ffe34f87156eb1ae6aa2d2ba7f0eef417e57b6 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 13 Mar 2023 13:08:29 +0100 Subject: [PATCH 26/29] chore(custom-minimap-node-example): cleanup --- .../vite-app/src/examples/CustomMiniMapNode/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx index fcb5b5b0..6a8e5807 100644 --- a/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx +++ b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx @@ -8,6 +8,7 @@ import ReactFlow, { Controls, Edge, MiniMap, + MiniMapNodeProps, Node, ReactFlowInstance, useEdgesState, @@ -25,6 +26,10 @@ const buttonStyle: CSSProperties = { zIndex: 4, }; +const CustomMiniMapNode = ({ x, y, width, height, color }: MiniMapNodeProps) => ( + +); + const CustomMiniMapNodeFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -66,8 +71,4 @@ const CustomMiniMapNodeFlow = () => { ); }; -const CustomMiniMapNode = ({ x, y, width, height }) => ( - -) - export default CustomMiniMapNodeFlow; From 4a30185a12899691ff61259f0db84bc5494cb573 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 13 Mar 2023 13:09:23 +0100 Subject: [PATCH 27/29] chore(changeset): add --- .changeset/green-files-act.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/green-files-act.md diff --git a/.changeset/green-files-act.md b/.changeset/green-files-act.md new file mode 100644 index 00000000..0ff03536 --- /dev/null +++ b/.changeset/green-files-act.md @@ -0,0 +1,5 @@ +--- +'@reactflow/minimap': minor +--- + +feat: add nodeComponent prop for passing custom component From 05b5781881fd8293ce0f7ec3db27f04c83d9e671 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 13 Mar 2023 13:34:37 +0100 Subject: [PATCH 28/29] chore(edge-renderer): accept invalid handleid config --- packages/core/src/container/EdgeRenderer/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/container/EdgeRenderer/utils.ts b/packages/core/src/container/EdgeRenderer/utils.ts index edd74543..0bf08e2d 100644 --- a/packages/core/src/container/EdgeRenderer/utils.ts +++ b/packages/core/src/container/EdgeRenderer/utils.ts @@ -77,10 +77,10 @@ export function getHandle(bounds: HandleElement[], handleId?: string | null): Ha return null; } - if (handleId) { - return bounds.find((d) => d.id === handleId)!; - } else if (bounds.length === 1) { + if (bounds.length === 1 || !handleId) { return bounds[0]; + } else if (handleId) { + return bounds.find((d) => d.id === handleId) || null; } return null; From b671cf50d67afbff132545d2afbb217022b78a3f Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 13 Mar 2023 16:57:34 +0100 Subject: [PATCH 29/29] chore(packages): bump --- .changeset/empty-carpets-reply.md | 8 -------- .changeset/green-files-act.md | 5 ----- .changeset/khaki-spoons-cheer.md | 5 ----- .changeset/polite-pumas-mate.md | 6 ------ .changeset/rare-points-change.md | 5 ----- .changeset/real-cats-sneeze.md | 5 ----- .changeset/thick-garlics-tap.md | 5 ----- .changeset/yellow-chicken-sparkle.md | 5 ----- packages/background/CHANGELOG.md | 9 +++++++++ packages/background/package.json | 2 +- packages/controls/CHANGELOG.md | 9 +++++++++ packages/controls/package.json | 2 +- packages/core/CHANGELOG.md | 14 ++++++++++++++ packages/core/package.json | 2 +- packages/minimap/CHANGELOG.md | 13 +++++++++++++ packages/minimap/package.json | 2 +- packages/node-resizer/CHANGELOG.md | 12 ++++++++++++ packages/node-resizer/package.json | 4 ++-- packages/node-toolbar/CHANGELOG.md | 7 +++++++ packages/node-toolbar/package.json | 2 +- packages/reactflow/CHANGELOG.md | 24 ++++++++++++++++++++++++ packages/reactflow/package.json | 2 +- 22 files changed, 96 insertions(+), 52 deletions(-) delete mode 100644 .changeset/empty-carpets-reply.md delete mode 100644 .changeset/green-files-act.md delete mode 100644 .changeset/khaki-spoons-cheer.md delete mode 100644 .changeset/polite-pumas-mate.md delete mode 100644 .changeset/rare-points-change.md delete mode 100644 .changeset/real-cats-sneeze.md delete mode 100644 .changeset/thick-garlics-tap.md delete mode 100644 .changeset/yellow-chicken-sparkle.md diff --git a/.changeset/empty-carpets-reply.md b/.changeset/empty-carpets-reply.md deleted file mode 100644 index 0f28abd2..00000000 --- a/.changeset/empty-carpets-reply.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@reactflow/background': patch -'@reactflow/controls': patch -'@reactflow/core': patch -'@reactflow/minimap': patch ---- - -add data-testid for controls, minimap and background diff --git a/.changeset/green-files-act.md b/.changeset/green-files-act.md deleted file mode 100644 index 0ff03536..00000000 --- a/.changeset/green-files-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/minimap': minor ---- - -feat: add nodeComponent prop for passing custom component diff --git a/.changeset/khaki-spoons-cheer.md b/.changeset/khaki-spoons-cheer.md deleted file mode 100644 index 135186a2..00000000 --- a/.changeset/khaki-spoons-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': patch ---- - -fix(nodes): blur when node gets unselected diff --git a/.changeset/polite-pumas-mate.md b/.changeset/polite-pumas-mate.md deleted file mode 100644 index 01b6d8db..00000000 --- a/.changeset/polite-pumas-mate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@reactflow/node-resizer': minor -'@reactflow/core': patch ---- - -node resizer: add maxWidth, maxHeight and keepAspectRatio props diff --git a/.changeset/rare-points-change.md b/.changeset/rare-points-change.md deleted file mode 100644 index 28a38179..00000000 --- a/.changeset/rare-points-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': patch ---- - -track modifier keys on useKeypress diff --git a/.changeset/real-cats-sneeze.md b/.changeset/real-cats-sneeze.md deleted file mode 100644 index 3f8f02bc..00000000 --- a/.changeset/real-cats-sneeze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': patch ---- - -fix: check if handle is connectable diff --git a/.changeset/thick-garlics-tap.md b/.changeset/thick-garlics-tap.md deleted file mode 100644 index 14705425..00000000 --- a/.changeset/thick-garlics-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': minor ---- - -add isValidConnection prop for ReactFlow component diff --git a/.changeset/yellow-chicken-sparkle.md b/.changeset/yellow-chicken-sparkle.md deleted file mode 100644 index f729cf0e..00000000 --- a/.changeset/yellow-chicken-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': minor ---- - -Add option to enable/disable replacing edge id when using `updateEdge` diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 4729e9a4..d179fca5 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/background +## 11.1.9 + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) Thanks [@moklick](https://github.com/moklick)! - add data-testid for controls, minimap and background + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 11.1.8 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index 8dcd3148..5f2a889f 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.1.8", + "version": "11.1.9", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md index 1f7dd6ea..373d9973 100644 --- a/packages/controls/CHANGELOG.md +++ b/packages/controls/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/controls +## 11.1.9 + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) Thanks [@moklick](https://github.com/moklick)! - add data-testid for controls, minimap and background + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 11.1.8 ### Patch Changes diff --git a/packages/controls/package.json b/packages/controls/package.json index fda1919c..5f01e229 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/controls", - "version": "11.1.8", + "version": "11.1.9", "description": "Component to control the viewport of a React Flow instance", "keywords": [ "react", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 9fc50c2a..5adab66f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,19 @@ # @reactflow/core +## 11.6.0 + +### Minor Changes + +- [#2877](https://github.com/wbkd/react-flow/pull/2877) [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7) - add `isValidConnection` prop for ReactFlow component +- [#2847](https://github.com/wbkd/react-flow/pull/2847) [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Add option to enable/disable replacing edge id when using `updateEdge` + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) - add data-testid for controls, minimap and background +- [#2894](https://github.com/wbkd/react-flow/pull/2894) [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b) - fix(nodes): blur when node gets unselected +- [#2892](https://github.com/wbkd/react-flow/pull/2892) [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf) Thanks [@danielgek](https://github.com/danielgek) - track modifier keys on useKeypress +- [#2893](https://github.com/wbkd/react-flow/pull/2893) [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861) - fix: check if handle is connectable + ## 11.5.5 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index a9789782..eb444565 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/core", - "version": "11.5.5", + "version": "11.6.0", "description": "Core components and util functions of React Flow.", "keywords": [ "react", diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index e0ab8a44..a1a322a9 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,18 @@ # @reactflow/minimap +## 11.4.0 + +### Minor Changes + +- [#2906](https://github.com/wbkd/react-flow/pull/2906) [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573) Thanks [@hayleigh-dot-dev](https://github.com/hayleigh-dot-dev)! - Add `nodeComponent` prop for passing custom component for the nodes + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) - add data-testid for controls, minimap and background + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 11.3.8 ### Patch Changes diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 66e22ced..81f0ff1c 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.3.8", + "version": "11.4.0", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/node-resizer/CHANGELOG.md b/packages/node-resizer/CHANGELOG.md index 9d7870b5..b8cfb5cb 100644 --- a/packages/node-resizer/CHANGELOG.md +++ b/packages/node-resizer/CHANGELOG.md @@ -1,5 +1,17 @@ # @reactflow/node-resizer +## 2.1.0 + +### Minor Changes + +- [#2900](https://github.com/wbkd/react-flow/pull/2900) [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798) Thanks [@stffabi](https://github.com/stffabi)! - add `maxWidth` and `maxHeight` props +- [#2900](https://github.com/wbkd/react-flow/pull/2900) [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798) - add `keepAspectRatio` prop + +### Patch Changes + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/node-resizer/package.json b/packages/node-resizer/package.json index 6bbc73e4..b40e0ead 100644 --- a/packages/node-resizer/package.json +++ b/packages/node-resizer/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/node-resizer", - "version": "2.0.1", + "version": "2.1.0", "description": "A helper component for resizing nodes.", "keywords": [ "react", @@ -38,7 +38,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@reactflow/core": "workspace:^11.3.3", + "@reactflow/core": "workspace:^11.6.0", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", diff --git a/packages/node-toolbar/CHANGELOG.md b/packages/node-toolbar/CHANGELOG.md index ddd7225e..7fc031e2 100644 --- a/packages/node-toolbar/CHANGELOG.md +++ b/packages/node-toolbar/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/node-toolbar +## 1.1.9 + +### Patch Changes + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 1.1.8 ### Patch Changes diff --git a/packages/node-toolbar/package.json b/packages/node-toolbar/package.json index 8500fda7..35378ec0 100644 --- a/packages/node-toolbar/package.json +++ b/packages/node-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/node-toolbar", - "version": "1.1.8", + "version": "1.1.9", "description": "A toolbar component for React Flow that can be attached to a node.", "keywords": [ "react", diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index 1a6a38dd..b8d6693f 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,5 +1,29 @@ # reactflow +## 11.6.0 + +This release introduces a new `isValidConnection` prop for the ReactFlow component. You no longer need to pass it to all your Handle components but can pass it once. We also added a new option for the `updateEdge` function that allows you to specify if you want to replace an id when updating it. More over the `MiniMap` got a new `nodeComponent` prop to pass a custom component for the mini map nodes. + +### Minor Changes + +- [#2877](https://github.com/wbkd/react-flow/pull/2877) [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7) - add `isValidConnection` prop for ReactFlow component +- [#2847](https://github.com/wbkd/react-flow/pull/2847) [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Add option to enable/disable replacing edge id when using `updateEdge` +- [#2906](https://github.com/wbkd/react-flow/pull/2906) [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573) Thanks [@hayleigh-dot-dev](https://github.com/hayleigh-dot-dev)! - Minimap: add nodeComponent prop for passing custom component + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) - add data-testid for controls, minimap and background +- [#2894](https://github.com/wbkd/react-flow/pull/2894) [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b) - fix(nodes): blur when node gets unselected +- [#2892](https://github.com/wbkd/react-flow/pull/2892) [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf) Thanks [@danielgek](https://github.com/danielgek) - track modifier keys on useKeypress +- [#2893](https://github.com/wbkd/react-flow/pull/2893) [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861) - fix: check if handle is connectable + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/background@11.1.9 + - @reactflow/controls@11.1.9 + - @reactflow/core@11.6.0 + - @reactflow/minimap@11.4.0 + - @reactflow/node-toolbar@1.1.9 + ## 11.5.6 ### Patch Changes diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index c2b29c19..2663a4cd 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.5.6", + "version": "11.6.0", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react",