From 08e61f31113b0f166af1fe760f37f27b64ee23a3 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 16 Nov 2023 15:41:30 +0100 Subject: [PATCH 01/51] refactor(edges): simplify edge rendering --- .../react/src/components/Edges/wrapEdge.tsx | 163 +++++++++--------- .../src/container/EdgeRenderer/index.tsx | 134 +++++++------- packages/react/src/hooks/useVisibleEdges.ts | 21 +-- .../components/EdgeWrapper/EdgeWrapper.svelte | 79 +++++---- .../EdgeRenderer/EdgeRenderer.svelte | 96 +++++------ packages/svelte/src/lib/store/index.ts | 4 +- .../svelte/src/lib/store/initial-store.ts | 2 +- .../store/{edge-tree.ts => visible-edges.ts} | 21 ++- packages/svelte/src/lib/types/edges.ts | 1 + packages/system/src/styles/init.css | 5 - packages/system/src/styles/style.css | 6 + packages/system/src/utils/edges/general.ts | 61 ++----- 12 files changed, 275 insertions(+), 318 deletions(-) rename packages/svelte/src/lib/store/{edge-tree.ts => visible-edges.ts} (78%) diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index b0a876b0..afdeead5 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -26,6 +26,7 @@ export default (EdgeComponent: ComponentType) => { labelBgPadding, labelBgBorderRadius, style, + zIndex, source, target, isSelectable, @@ -201,86 +202,88 @@ export default (EdgeComponent: ComponentType) => { }; return ( - - {!updating && ( - - )} - {isUpdatable && ( - <> - {(isUpdatable === 'source' || isUpdatable === true) && ( - - )} - {(isUpdatable === 'target' || isUpdatable === true) && ( - - )} - - )} - + + + {!updating && ( + + )} + {isUpdatable && ( + <> + {(isUpdatable === 'source' || isUpdatable === true) && ( + + )} + {(isUpdatable === 'target' || isUpdatable === true) && ( + + )} + + )} + + ); }; diff --git a/packages/react/src/container/EdgeRenderer/index.tsx b/packages/react/src/container/EdgeRenderer/index.tsx index 24825fe6..35137619 100644 --- a/packages/react/src/container/EdgeRenderer/index.tsx +++ b/packages/react/src/container/EdgeRenderer/index.tsx @@ -63,82 +63,76 @@ const EdgeRenderer = ({ children, }: EdgeRendererProps) => { const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); - // we are grouping edges by zIndex here in order to be able to render them in the correct order - // each zIndex gets its own svg element - const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect); + const edges = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect); return ( - <> - {edgeTree.map(({ level, edges, isMaxLevel }) => ( - - {isMaxLevel && } - <> - {edges.map((edge) => { - let edgeType = edge.type || 'default'; +
+ + + - if (!edgeTypes[edgeType]) { - onError?.('011', errorMessages['error011'](edgeType)); - edgeType = 'default'; - } + {edges.map((edge) => { + let edgeType = edge.type || 'default'; - const EdgeComponent = edgeTypes[edgeType]; - const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); - const isUpdatable = - typeof onEdgeUpdate !== 'undefined' && - (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined')); - const isSelectable = !!( - edge.selectable || - (elementsSelectable && typeof edge.selectable === 'undefined') - ); + if (!edgeTypes[edgeType]) { + onError?.('011', errorMessages['error011'](edgeType)); + edgeType = 'default'; + } + + const EdgeComponent = edgeTypes[edgeType]; + const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); + const isUpdatable = + typeof onEdgeUpdate !== 'undefined' && + (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined')); + const isSelectable = !!(edge.selectable || (elementsSelectable && typeof edge.selectable === 'undefined')); + + return ( +
); }; diff --git a/packages/react/src/hooks/useVisibleEdges.ts b/packages/react/src/hooks/useVisibleEdges.ts index 67d02c92..ca035fbb 100644 --- a/packages/react/src/hooks/useVisibleEdges.ts +++ b/packages/react/src/hooks/useVisibleEdges.ts @@ -1,11 +1,11 @@ import { useCallback } from 'react'; -import { GroupedEdges, groupEdgesByZLevel, isEdgeVisible } from '@xyflow/system'; +import { shallow } from 'zustand/shallow'; +import { adjustEdgeZIndex, isEdgeVisible } from '@xyflow/system'; import { useStore } from '../hooks/useStore'; -import { Edge, type ReactFlowState } from '../types'; -import { shallow } from 'zustand/shallow'; +import { type Edge, type ReactFlowState } from '../types'; -function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): GroupedEdges[] { +function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): Edge[] { const edges = useStore( useCallback( (s: ReactFlowState) => { @@ -29,20 +29,11 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole }) : s.edges; - return groupEdgesByZLevel(visibleEdges, s.nodeLookup, elevateEdgesOnSelect); + return visibleEdges.map((edge) => adjustEdgeZIndex(edge, s.nodeLookup, elevateEdgesOnSelect)); }, [onlyRenderVisible, elevateEdgesOnSelect] ), - (groupA, groupB) => { - const unEqual = groupA.some( - (item, index) => - item.isMaxLevel !== groupB[index].isMaxLevel || - item.level !== groupB[index].level || - !shallow(item.edges, groupB[index].edges) - ); - - return !unEqual; - } + shallow ); return edges; diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 7a8df68c..763c6efb 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -18,6 +18,7 @@ export let target: $$Props['target'] = ''; export let data: $$Props['data'] = {}; export let style: $$Props['style'] = undefined; + export let zIndex: $$Props['zIndex'] = undefined; export let animated: $$Props['animated'] = false; export let selected: $$Props['selected'] = false; @@ -95,42 +96,44 @@ {#if !hidden} - - - + + + + + {/if} diff --git a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte index 27bc7d72..6dcc16fa 100644 --- a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte +++ b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte @@ -9,7 +9,7 @@ const { elementsSelectable, - edgeTree, + visibleEdges, edges: { setDefaultOptions } } = useStore(); @@ -18,57 +18,47 @@ }); -{#each $edgeTree as group (group.level)} - - {#if group.isMaxLevel} {/if} - - {#each group.edges as edge (edge.id)} - {@const edgeType = edge.type || 'default'} - {@const selectable = !!( - edge.selectable || - ($elementsSelectable && typeof edge.selectable === 'undefined') - )} - - - {/each} - +
+ + -{/each} - + {#each $visibleEdges as edge (edge.id)} + {@const edgeType = edge.type || 'default'} + {@const selectable = !!( + edge.selectable || + ($elementsSelectable && typeof edge.selectable === 'undefined') + )} + +
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 625191e0..a16844d2 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -24,7 +24,7 @@ import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store'; import type { SvelteFlowStore } from './types'; import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils'; -import { getEdgeTree } from './edge-tree'; +import { getVisibleEdges } from './visible-edges'; import { getVisibleNodes } from './visible-nodes'; import { getDerivedConnectionProps } from './derived-connection-props'; @@ -349,8 +349,8 @@ export function createStore({ ...store, // derived state - edgeTree: getEdgeTree(store), connection: getDerivedConnectionProps(store, currentConnection), + visibleEdges: getVisibleEdges(store), visibleNodes: getVisibleNodes(store), markers: derived( [store.edges, store.defaultMarkerColor, store.flowId], diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index d6b621d9..3821023a 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -91,7 +91,7 @@ export const getInitialStore = ({ nodeLookup: readable>(nodeLookup), visibleNodes: readable([]), edges: createEdgesStore(edges), - edgeTree: readable[]>([]), + visibleEdges: readable([]), height: writable(500), width: writable(500), minZoom: writable(0.5), diff --git a/packages/svelte/src/lib/store/edge-tree.ts b/packages/svelte/src/lib/store/visible-edges.ts similarity index 78% rename from packages/svelte/src/lib/store/edge-tree.ts rename to packages/svelte/src/lib/store/visible-edges.ts index cd5448ab..d9903261 100644 --- a/packages/svelte/src/lib/store/edge-tree.ts +++ b/packages/svelte/src/lib/store/visible-edges.ts @@ -1,10 +1,10 @@ import { derived } from 'svelte/store'; -import { groupEdgesByZLevel, isEdgeVisible, getEdgePosition } from '@xyflow/system'; +import { isEdgeVisible, getEdgePosition, adjustEdgeZIndex } from '@xyflow/system'; import type { EdgeLayouted } from '$lib/types'; import type { SvelteFlowStoreState } from './types'; -export function getEdgeTree(store: SvelteFlowStoreState) { +export function getVisibleEdges(store: SvelteFlowStoreState) { const visibleEdges = derived( [ store.edges, @@ -41,8 +41,15 @@ export function getEdgeTree(store: SvelteFlowStoreState) { ); return derived( - [visibleEdges, store.nodes, store.nodeLookup, store.connectionMode, store.onerror], - ([visibleEdges, , nodeLookup, connectionMode, onerror]) => { + [ + visibleEdges, + store.nodes, + store.nodeLookup, + store.connectionMode, + store.onlyRenderVisibleElements, + store.onerror + ], + ([visibleEdges, , nodeLookup, connectionMode, onlyRenderVisibleElements, onerror]) => { const layoutedEdges = visibleEdges.reduce((res, edge) => { const sourceNode = nodeLookup.get(edge.source); const targetNode = nodeLookup.get(edge.target); @@ -63,7 +70,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) { if (edgePosition) { res.push({ - ...edge, + ...adjustEdgeZIndex(edge, nodeLookup, onlyRenderVisibleElements), ...edgePosition }); } @@ -71,9 +78,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) { return res; }, []); - const groupedEdges = groupEdgesByZLevel(layoutedEdges, nodeLookup, false); - - return groupedEdges; + return layoutedEdges; } ); } diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 33f0d908..4ae172e6 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -70,6 +70,7 @@ export type EdgeLayouted = Pick< | 'ariaLabel' | 'hidden' | 'class' + | 'zIndex' > & EdgePosition & { sourceNode?: Node; diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 9fed0d7b..26530e9b 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -57,11 +57,6 @@ outline: none; } -.xy-flow .xy-flow__edges { - pointer-events: none; - overflow: visible; -} - .xy-flow__edge-path { stroke: var(--edge-stroke, var(--edge-stroke-default)); stroke-width: var(--edge-stroke-width, var(--edge-stroke-width-default)); diff --git a/packages/system/src/styles/style.css b/packages/system/src/styles/style.css index 99fa8876..2e6a95c8 100644 --- a/packages/system/src/styles/style.css +++ b/packages/system/src/styles/style.css @@ -20,6 +20,12 @@ --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } +.xy-flow__edgewrapper { + overflow: visible; + position: absolute; + pointer-events: none; +} + .xy-flow__edge { &.updating { .xy-flow__edge-path { diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index bdffbbd8..ca1dd390 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -23,63 +23,32 @@ export function getEdgeCenter({ return [centerX, centerY, xOffset, yOffset]; } -const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }]; - export type GroupedEdges = { edges: EdgeType[]; level: number; isMaxLevel: boolean; }; -export function groupEdgesByZLevel( - edges: EdgeType[], - nodeLookup: Map, - elevateEdgesOnSelect = false -): GroupedEdges[] { - let maxLevel = -1; +export function adjustEdgeZIndex(edge: EdgeBase, nodeLookup: Map, elevateEdgesOnSelect: boolean) { + const hasZIndex = isNumeric(edge.zIndex); + let z = hasZIndex ? edge.zIndex! : 0; - const levelLookup = edges.reduce>((tree, edge) => { - const hasZIndex = isNumeric(edge.zIndex); - let z = hasZIndex ? edge.zIndex! : 0; + if (elevateEdgesOnSelect) { + const targetNode = nodeLookup.get(edge.target)!; + const sourceNode = nodeLookup.get(edge.source)!; + const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; + const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000); + z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); - if (elevateEdgesOnSelect) { - const targetNode = nodeLookup.get(edge.target); - const sourceNode = nodeLookup.get(edge.source); - const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; - const selectedZIndex = Math.max( - sourceNode?.[internalsSymbol]?.z || 0, - targetNode?.[internalsSymbol]?.z || 0, - 1000 - ); - z = (hasZIndex ? edge.zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0); + if (edge.zIndex !== z) { + return { + ...edge, + zIndex: z, + }; } - - if (tree[z]) { - tree[z].push(edge); - } else { - tree[z] = [edge]; - } - - maxLevel = z > maxLevel ? z : maxLevel; - - return tree; - }, {}); - - const edgeTree = Object.entries(levelLookup).map(([key, edges]) => { - const level = +key; - - return { - edges, - level, - isMaxLevel: level === maxLevel, - }; - }); - - if (edgeTree.length === 0) { - return defaultEdgeTree; } - return edgeTree; + return edge; } type IsEdgeVisibleParams = { From 2fe9d57d2a33e4e3b5e9d39ea289415668875d59 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 16 Nov 2023 15:45:36 +0100 Subject: [PATCH 02/51] chore(edges): remove group type --- packages/svelte/src/lib/store/initial-store.ts | 1 - packages/system/src/utils/edges/general.ts | 6 ------ 2 files changed, 7 deletions(-) diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 3821023a..b9f5a524 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -10,7 +10,6 @@ import { type PanZoomInstance, type CoordinateExtent, type IsValidConnection, - type GroupedEdges, type NodeOrigin, type OnError, devWarn, diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index ca1dd390..518f6963 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -23,12 +23,6 @@ export function getEdgeCenter({ return [centerX, centerY, xOffset, yOffset]; } -export type GroupedEdges = { - edges: EdgeType[]; - level: number; - isMaxLevel: boolean; -}; - export function adjustEdgeZIndex(edge: EdgeBase, nodeLookup: Map, elevateEdgesOnSelect: boolean) { const hasZIndex = isNumeric(edge.zIndex); let z = hasZIndex ? edge.zIndex! : 0; From 622c987e46395d8d91a58530b44bd398113db2f1 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 16 Nov 2023 15:52:57 +0100 Subject: [PATCH 03/51] refactor(edges): cleanup markup --- packages/react/src/components/Edges/wrapEdge.tsx | 2 +- packages/react/src/container/EdgeRenderer/index.tsx | 2 +- .../svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte | 2 +- .../svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte | 2 +- packages/system/src/styles/style.css | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index afdeead5..7d3f92e6 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -202,7 +202,7 @@ export default (EdgeComponent: ComponentType) => { }; return ( - + - + diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 763c6efb..fb90ca8b 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -96,7 +96,7 @@ {#if !hidden} - +
- + diff --git a/packages/system/src/styles/style.css b/packages/system/src/styles/style.css index 2e6a95c8..1ba81595 100644 --- a/packages/system/src/styles/style.css +++ b/packages/system/src/styles/style.css @@ -20,7 +20,7 @@ --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } -.xy-flow__edgewrapper { +.xy-flow__edges svg { overflow: visible; position: absolute; pointer-events: none; From 05bd2e866639853742682ad45eb611393e058607 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Thu, 23 Nov 2023 13:58:34 +0100 Subject: [PATCH 04/51] chore(svelte): changelog --- packages/svelte/CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 60674c25..12086a6a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,12 +1,15 @@ ## 0.0.28 -There are some breaking changes again (sorry!) but we are very close to the final API for Svelte Flow 1.0.0. The biggest change is that we group node attriubutes (`width`, `height`, `positionAbsolute`) that are added by the library under `node.computed`. This makes it easier to understand, that this stuff comes from the library itself. `node.width` and `node.height` is still an optional node option and can be used to set certain dimensions for SSR or on the client. +This is a huge update! We added a new `` component and a new `colorMode` ('light' | 'dark' | 'system') prop for toggling dark/light mode. + +There are also some breaking changes again (sorry!) but we are very close to the final API for Svelte Flow 1.0.0. The biggest change is that we group node attriubutes (`width`, `height`, `positionAbsolute`) that are added by the library under `node.computed`. This makes it easier to understand, that this stuff comes from the library itself. `node.width` and `node.height` is still an optional node option and can be used to set certain dimensions for SSR or on the client. - add `` component - add `on:selectionclick` and `on:selectioncontextmenu` event handlers - add `ondelete({ nodes, edges })` handler - add `zoomActivationKey` prop - add `width` and `height` prop to custom `NodeProps` type +- add `colorMode` prop ('light' | 'dark' | 'system') - ⚠️ replace `xPos` and `yPos` with `positionAbsolute` prop to custom `NodeProps` type - ⚠️ `node.width/height` and `node.positionAbsolute` can now be found under `node.computed.width/height/positionAbsolute` - ⚠️ `node.width/height` is still optional an can be used for forcing certain dimensions and SSR @@ -159,4 +162,4 @@ This very first release comes with lots of features already: - draggable, selectable and deletable nodes - support for custom `nodeTypes` and `edgeTypes` - basic viewport settings like `fitView`, `minZoom` and `maxZoom` -- additional components: ``, `` & `` \ No newline at end of file +- additional components: ``, `` & `` From b0ca5a5bc9eccbe511153a3ed17c0ca55656d0e9 Mon Sep 17 00:00:00 2001 From: Ivan Akulov Date: Wed, 22 Nov 2023 18:50:15 +0100 Subject: [PATCH 05/51] Set nodeDragThreshold to 1 This removes an expensive `dragging: false` update on mouseup (with `dragging: true` never even dispatched) --- packages/react/src/store/initialState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 48e5434f..e0482961 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -61,7 +61,7 @@ const getInitialState = ({ paneDragging: false, noPanClassName: 'nopan', nodeOrigin: [0, 0], - nodeDragThreshold: 0, + nodeDragThreshold: 1, snapGrid: [15, 15], snapToGrid: false, From 24390162e08867a7c68c309a0ebcd52dafd6d425 Mon Sep 17 00:00:00 2001 From: Ivan Akulov Date: Thu, 23 Nov 2023 01:17:55 +0100 Subject: [PATCH 06/51] =?UTF-8?q?Store:=20don=E2=80=99t=20regenerate=20int?= =?UTF-8?q?ernal=20nodes=20if=20user-provided=20nodes=20haven=E2=80=99t=20?= =?UTF-8?q?changed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react/src/store/index.ts | 19 ++++++++++++------- packages/react/src/store/initialState.ts | 4 ++-- .../svelte/src/lib/store/initial-store.ts | 4 ++-- packages/svelte/src/lib/store/utils.ts | 4 ++-- packages/system/src/types/nodes.ts | 4 ++++ packages/system/src/utils/store.ts | 5 ++++- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 0f50f385..49c7c688 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -2,7 +2,7 @@ import { createWithEqualityFn } from 'zustand/traditional'; import { clampPosition, fitView as fitViewSystem, - updateNodes, + adoptUserProvidedNodes, updateAbsolutePositions, panBy as panBySystem, Dimensions, @@ -42,11 +42,16 @@ const createRFStore = ({ ...getInitialState({ nodes, edges, width, height, fitView }), setNodes: (nodes: Node[]) => { const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get(); - // Whenver new nodes are set, we need to calculate the absolute positions of the nodes - // and update the nodeLookup. - const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect }); + // setNodes() is called exclusively in response to user actions: + // - either when the `` prop is updated in the controlled ReactFlow setup, + // - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup. + // + // When this happens, we take the note objects passed by the user and extend them with fields + // relevant for internal React Flow operations. + // TODO: consider updating the types to reflect the distinction between user-provided nodes and internal nodes. + const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect }); - set({ nodes: nextNodes }); + set({ nodes: nodesWithInternalData }); }, setEdges: (edges: Edge[]) => { const { defaultEdgeOptions = {} } = get(); @@ -69,7 +74,7 @@ const createRFStore = ({ }; if (hasDefaultNodes) { - nextState.nodes = updateNodes(nodes, new Map(), { + nextState.nodes = adoptUserProvidedNodes(nodes, new Map(), { nodeOrigin: get().nodeOrigin, elevateNodesOnSelect: get().elevateNodesOnSelect, }); @@ -163,7 +168,7 @@ const createRFStore = ({ if (changes?.length) { if (hasDefaultNodes) { const updatedNodes = applyNodeChanges(changes, nodes); - const nextNodes = updateNodes(updatedNodes, nodeLookup, { + const nextNodes = adoptUserProvidedNodes(updatedNodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect, }); diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 48e5434f..d54ff61c 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -1,7 +1,7 @@ import { infiniteExtent, ConnectionMode, - updateNodes, + adoptUserProvidedNodes, getNodesBounds, getViewportForBounds, Transform, @@ -23,7 +23,7 @@ const getInitialState = ({ fitView?: boolean; } = {}): ReactFlowStore => { const nodeLookup = new Map(); - const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); + const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); let transform: Transform = [0, 0, 1]; diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index dbbb17fc..a3ac2f1e 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -15,7 +15,7 @@ import { type OnError, devWarn, type Viewport, - updateNodes, + adoptUserProvidedNodes, getNodesBounds, getViewportForBounds } from '@xyflow/system'; @@ -68,7 +68,7 @@ export const getInitialStore = ({ fitView?: boolean; }) => { const nodeLookup = new Map(); - const nextNodes = updateNodes(nodes, nodeLookup, { + const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts index a11bf1f6..2d53ac90 100644 --- a/packages/svelte/src/lib/store/utils.ts +++ b/packages/svelte/src/lib/store/utils.ts @@ -6,7 +6,7 @@ import { type Writable, get } from 'svelte/store'; -import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system'; +import { adoptUserProvidedNodes, type Viewport, type PanZoomInstance } from '@xyflow/system'; import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types'; @@ -133,7 +133,7 @@ export const createNodesStore = ( let elevateNodesOnSelect = true; const _set = (nds: Node[]): Node[] => { - const nextNodes = updateNodes(nds, nodeLookup, { + const nextNodes = adoptUserProvidedNodes(nds, nodeLookup, { elevateNodesOnSelect, defaults }); diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index b55f3f54..33884bf7 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -40,6 +40,10 @@ export type NodeBase z?: number; handleBounds?: NodeHandleBounds; isParent?: boolean; + /** Holds a reference to the original node object provided by the user + * (which may lack some fields, like `computed` or `[internalSymbol]`. Used + * as an optimization to avoid certain operations. */ + userProvidedNode: WeakRef; }; }; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index eca6b8de..34d3d529 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -62,7 +62,7 @@ type UpdateNodesOptions = { defaults?: Partial; }; -export function updateNodes( +export function adoptUserProvidedNodes( nodes: NodeType[], nodeLookup: Map, options: UpdateNodesOptions = { @@ -76,6 +76,8 @@ export function updateNodes( const nextNodes = nodes.map((n) => { const currentStoreNode = nodeLookup.get(n.id); + if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode.deref()) return currentStoreNode; + const node: NodeType = { ...options.defaults, ...n, @@ -97,6 +99,7 @@ export function updateNodes( value: { handleBounds: currInternals?.handleBounds, z, + userProvidedNode: new WeakRef(n), }, }); From 99d945f33c9ebf84e24b460b89cafce087bbc710 Mon Sep 17 00:00:00 2001 From: Ivan Akulov Date: Thu, 23 Nov 2023 01:19:33 +0100 Subject: [PATCH 07/51] Split NodeRenderer into NodeRenderer and NodeComponentWrapper --- .../src/container/NodeRenderer/index.tsx | 174 ++++++++++++------ 1 file changed, 113 insertions(+), 61 deletions(-) diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 73808da1..7c8137ca 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -76,74 +76,29 @@ const NodeRenderer = (props: NodeRendererProps) => { return (
{nodes.map((node) => { - let nodeType = node.type || 'default'; - - if (!props.nodeTypes[nodeType]) { - onError?.('003', errorMessages['error003'](nodeType)); - - nodeType = 'default'; - } - - const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; - const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); - const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); - const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); - const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined')); - - const clampedPosition = props.nodeExtent - ? clampPosition(node.computed?.positionAbsolute, props.nodeExtent) - : node.computed?.positionAbsolute; - - const posX = clampedPosition?.x ?? 0; - const posY = clampedPosition?.y ?? 0; - const posOrigin = getPositionWithOrigin({ - x: posX, - y: posY, - width: node.computed?.width ?? node.width ?? 0, - height: node.computed?.height ?? node.height ?? 0, - origin: node.origin || props.nodeOrigin, - }); - const initialized = (!!node.computed?.width && !!node.computed?.height) || (!!node.width && !!node.height); - return ( -