diff --git a/.changeset/violet-houses-argue.md b/.changeset/violet-houses-argue.md new file mode 100644 index 00000000..0ae30058 --- /dev/null +++ b/.changeset/violet-houses-argue.md @@ -0,0 +1,7 @@ +--- +'@reactflow/background': minor +'@reactflow/core': minor +'@reactflow/minimap': minor +--- + +edges: add updatable option diff --git a/examples/vite-app/src/examples/UpdatableEdge/index.tsx b/examples/vite-app/src/examples/UpdatableEdge/index.tsx index 51760701..f9a109d1 100644 --- a/examples/vite-app/src/examples/UpdatableEdge/index.tsx +++ b/examples/vite-app/src/examples/UpdatableEdge/index.tsx @@ -36,7 +36,7 @@ const initialNodes: Node[] = [ ), }, - position: { x: 100, y: 100 }, + position: { x: 75, y: 0 }, }, { id: '3', @@ -55,9 +55,46 @@ const initialNodes: Node[] = [ width: 180, }, }, + { + id: '4', + data: { + label: ( + <> + Node D + + ), + }, + position: { x: -75, y: 100 }, + }, + { + id: '5', + data: { + label: ( + <> + Node E + + ), + }, + position: { x: 150, y: 100 }, + }, + { + id: '6', + data: { + label: ( + <> + Node F + + ), + }, + position: { x: 150, y: 250 }, + }, ]; -const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }]; +const initialEdges: Edge[] = [ + { id: 'e1-3', source: '1', target: '3', label: 'This edge can only be updated from source', updatable: 'source' }, + { id: 'e2-4', source: '2', target: '4', label: 'This edge can only be updated from target', updatable: 'target' }, + { id: 'e5-6', source: '5', target: '6', label: 'This edge can be updated from both sides' }, +]; const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView(); const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) => diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 607f2c5a..cb7d987f 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -55,6 +55,7 @@ export default (EdgeComponent: ComponentType) => { rfId, ariaLabel, isFocusable, + isUpdatable, pathOptions, interactionWidth, }: WrapEdgeProps): JSX.Element | null => { @@ -138,7 +139,6 @@ export default (EdgeComponent: ComponentType) => { const onEdgeUpdaterMouseOut = () => setUpdateHover(false); const inactive = !elementsSelectable && !onClick; - const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined'; const onKeyDown = (event: KeyboardEvent) => { if (elementSelectionKeys.includes(event.key) && elementsSelectable) { @@ -205,28 +205,32 @@ export default (EdgeComponent: ComponentType) => { interactionWidth={interactionWidth} /> )} - {handleEdgeUpdate && ( + {isUpdatable && ( <> - - + {(isUpdatable === 'source' || isUpdatable === true) && ( + + )} + {(isUpdatable === 'target' || isUpdatable === true) && ( + + )} )} diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 4491bb94..e31fba17 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -20,6 +20,7 @@ type StoreUpdaterProps = Pick< | 'nodesConnectable' | 'nodesFocusable' | 'edgesFocusable' + | 'edgesUpdatable' | 'minZoom' | 'maxZoom' | 'nodeExtent' @@ -98,6 +99,7 @@ const StoreUpdater = ({ nodesConnectable, nodesFocusable, edgesFocusable, + edgesUpdatable, elevateNodesOnSelect, minZoom, maxZoom, @@ -162,6 +164,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState); useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState); useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState); + useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState); useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState); useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState); useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState); diff --git a/packages/core/src/container/EdgeRenderer/index.tsx b/packages/core/src/container/EdgeRenderer/index.tsx index 09b8f0c6..9f7a2f1b 100644 --- a/packages/core/src/container/EdgeRenderer/index.tsx +++ b/packages/core/src/container/EdgeRenderer/index.tsx @@ -39,6 +39,7 @@ type EdgeRendererProps = Pick< const selector = (s: ReactFlowState) => ({ nodesConnectable: s.nodesConnectable, edgesFocusable: s.edgesFocusable, + edgesUpdatable: s.edgesUpdatable, elementsSelectable: s.elementsSelectable, width: s.width, height: s.height, @@ -66,10 +67,8 @@ const EdgeRenderer = ({ onEdgeUpdateEnd, children, }: EdgeRendererProps) => { - const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore( - selector, - shallow - ); + const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = + useStore(selector, shallow); const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect); if (!width) { @@ -114,6 +113,9 @@ const EdgeRenderer = ({ const sourcePosition = sourceHandle?.position || Position.Bottom; const targetPosition = targetHandle?.position || Position.Top; const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); + const isUpdatable = + typeof onEdgeUpdate !== 'undefined' && + (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined')); if (!sourceHandle || !targetHandle) { onError?.('008', errorMessages['error008'](sourceHandle, edge)); @@ -173,6 +175,7 @@ const EdgeRenderer = ({ rfId={rfId} ariaLabel={edge.ariaLabel} isFocusable={isFocusable} + isUpdatable={isUpdatable} pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined} interactionWidth={edge.interactionWidth} /> diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 9fa23f9c..1d36123d 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -115,6 +115,7 @@ const ReactFlow = forwardRef( nodesFocusable, nodeOrigin = initNodeOrigin, edgesFocusable, + edgesUpdatable, elementsSelectable, defaultViewport = initDefaultViewport, minZoom = 0.5, @@ -266,6 +267,7 @@ const ReactFlow = forwardRef( nodesConnectable={nodesConnectable} nodesFocusable={nodesFocusable} edgesFocusable={edgesFocusable} + edgesUpdatable={edgesUpdatable} elementsSelectable={elementsSelectable} elevateNodesOnSelect={elevateNodesOnSelect} minZoom={minZoom} diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index 8c4a5d76..2d1bb93d 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -46,6 +46,7 @@ const initialState: ReactFlowStore = { nodesConnectable: true, nodesFocusable: true, edgesFocusable: true, + edgesUpdatable: true, elementsSelectable: true, elevateNodesOnSelect: true, fitViewOnInit: false, diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index cae9da3b..46364257 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -112,6 +112,7 @@ export type ReactFlowProps = HTMLAttributes & { nodesFocusable?: boolean; nodeOrigin?: NodeOrigin; edgesFocusable?: boolean; + edgesUpdatable?: boolean; initNodeOrigin?: NodeOrigin; elementsSelectable?: boolean; selectNodesOnDrag?: boolean; diff --git a/packages/core/src/types/edges.ts b/packages/core/src/types/edges.ts index 143c7fa6..7358b0e8 100644 --- a/packages/core/src/types/edges.ts +++ b/packages/core/src/types/edges.ts @@ -36,8 +36,11 @@ type DefaultEdge = { ariaLabel?: string; interactionWidth?: number; focusable?: boolean; + updatable?: EdgeUpdatable; } & EdgeLabelOptions; +export type EdgeUpdatable = boolean | HandleType; + export type SmoothStepPathOptions = { offset?: number; borderRadius?: number; @@ -88,6 +91,7 @@ export type WrapEdgeProps = Omit, 'sourceHandle' | 'targetHandl onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void; rfId?: string; isFocusable: boolean; + isUpdatable: EdgeUpdatable; pathOptions?: BezierPathOptions | SmoothStepPathOptions; }; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index aeb33632..f57a1ea7 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -182,6 +182,7 @@ export type ReactFlowStore = { nodesConnectable: boolean; nodesFocusable: boolean; edgesFocusable: boolean; + edgesUpdatable: boolean; elementsSelectable: boolean; elevateNodesOnSelect: boolean;