diff --git a/.changeset/good-lamps-think.md b/.changeset/good-lamps-think.md new file mode 100644 index 000000000..fa33aaa46 --- /dev/null +++ b/.changeset/good-lamps-think.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Ensure visual nodes selection state is cleared when zero selected nodes remain in the flow diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 5ca9bac2e..895719ef6 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -97,7 +97,16 @@ const createStore = ({ zIndexMode, }), setNodes: (nodes: Node[]) => { - const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued, zIndexMode } = get(); + const { + nodeLookup, + parentLookup, + nodeOrigin, + elevateNodesOnSelect, + fitViewQueued, + zIndexMode, + nodesSelectionActive, + } = get(); + /* * setNodes() is called exclusively in response to user actions: * - either when the `` prop is updated in the controlled ReactFlow setup, @@ -107,7 +116,7 @@ const createStore = ({ * relevant for internal React Flow operations. */ - const nodesInitialized = adoptUserNodes(nodes, nodeLookup, parentLookup, { + const { nodesInitialized, hasSelectedNodes } = adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, nodeExtent, elevateNodesOnSelect, @@ -115,11 +124,19 @@ const createStore = ({ zIndexMode, }); + const nextNodesSelectionActive = nodesSelectionActive && hasSelectedNodes; + if (fitViewQueued && nodesInitialized) { resolveFitView(); - set({ nodes, nodesInitialized, fitViewQueued: false, fitViewOptions: undefined }); + set({ + nodes, + nodesInitialized, + fitViewQueued: false, + fitViewOptions: undefined, + nodesSelectionActive: nextNodesSelectionActive + }); } else { - set({ nodes, nodesInitialized }); + set({ nodes, nodesInitialized, nodesSelectionActive: nextNodesSelectionActive }); } }, setEdges: (edges: Edge[]) => { diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 26a5c045d..53470d11a 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -56,7 +56,7 @@ const getInitialState = ({ const storeNodeExtent = nodeExtent ?? infiniteExtent; updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); - const nodesInitialized = adoptUserNodes(storeNodes, nodeLookup, parentLookup, { + const { nodesInitialized } = adoptUserNodes(storeNodes, nodeLookup, parentLookup, { nodeOrigin: storeNodeOrigin, nodeExtent: storeNodeExtent, zIndexMode, diff --git a/packages/svelte/src/lib/store/initial-store.svelte.ts b/packages/svelte/src/lib/store/initial-store.svelte.ts index 515d1dabc..62165d46b 100644 --- a/packages/svelte/src/lib/store/initial-store.svelte.ts +++ b/packages/svelte/src/lib/store/initial-store.svelte.ts @@ -120,7 +120,7 @@ export function getInitialStore(signals.props.zIndexMode ?? 'basic'); nodesInitialized: boolean = $derived.by(() => { - const nodesInitialized = adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, { + const { nodesInitialized } = adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, { nodeExtent: this.nodeExtent, nodeOrigin: this.nodeOrigin, elevateNodesOnSelect: signals.props.elevateNodesOnSelect ?? true, diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 2bae20c85..114133899 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -121,18 +121,24 @@ export function isManualZIndexMode(zIndexMode?: ZIndexMode): boolean { return zIndexMode === 'manual'; } +type AdoptUserNodesReturn = { + nodesInitialized: boolean; + hasSelectedNodes: boolean; +}; + export function adoptUserNodes( nodes: NodeType[], nodeLookup: NodeLookup>, parentLookup: ParentLookup>, options: UpdateNodesOptions = {} -): boolean { +): AdoptUserNodesReturn { const _options = mergeObjects(adoptUserNodesDefaultOptions, options); const rootParentIndex = { i: 0 }; const tmpLookup = new Map(nodeLookup); const selectedNodeZ: number = _options?.elevateNodesOnSelect && !isManualZIndexMode(_options.zIndexMode) ? SELECTED_NODE_Z : 0; let nodesInitialized = nodes.length > 0; + let hasSelectedNodes = false; nodeLookup.clear(); parentLookup.clear(); @@ -178,9 +184,11 @@ export function adoptUserNodes( if (userNode.parentId) { updateChildNode(internalNode, nodeLookup, parentLookup, options, rootParentIndex); } + + hasSelectedNodes ||= userNode.selected ?? false; } - return nodesInitialized; + return { nodesInitialized, hasSelectedNodes }; } function updateParentLookup(