diff --git a/examples/vite-app/cypress/e2e/minimap.cy.ts b/examples/vite-app/cypress/e2e/minimap.cy.ts index d6cb7d46..46a5a819 100644 --- a/examples/vite-app/cypress/e2e/minimap.cy.ts +++ b/examples/vite-app/cypress/e2e/minimap.cy.ts @@ -35,17 +35,20 @@ describe('Minimap Testing', () => { }); it('changes node position', () => { - const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('x'); - const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('y'); + const minimapNode = Cypress.$('.react-flow__minimap-node:first'); + + const xPosBeforeDrag = Number(minimapNode.attr('x')); + const yPosBeforeDrag = Number(minimapNode.attr('y')); cy.drag('.react-flow__node:first', { x: 500, y: 25 }) .wait(100) .then(() => { - const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('x'); - const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('y'); + const xPosAfterDrag = Number(minimapNode.attr('x')); + const yPosAfterDrag = Number(minimapNode.attr('y')); - expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag); - expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag); + expect(xPosAfterDrag).not.to.equal(xPosBeforeDrag); + expect(yPosAfterDrag).not.to.equal(yPosBeforeDrag); + expect(xPosAfterDrag - xPosBeforeDrag).to.be.greaterThan(yPosAfterDrag - yPosBeforeDrag); }); }); diff --git a/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx new file mode 100644 index 00000000..349db385 --- /dev/null +++ b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx @@ -0,0 +1,17 @@ +import { NodeToolbar, ReactFlowState, useStore } from 'reactflow'; + +const selectedNodesSelector = (state: ReactFlowState) => + Array.from(state.nodeInternals.values()) + .filter((node) => node.selected) + .map((node) => node.id); + +export default function SelectedNodesToolbar() { + const selectedNodeIds = useStore(selectedNodesSelector); + const isVisible = selectedNodeIds.length > 1; + + return ( + + + + ); +} diff --git a/examples/vite-app/src/examples/NodeToolbar/index.tsx b/examples/vite-app/src/examples/NodeToolbar/index.tsx index 9a5815df..601d8618 100644 --- a/examples/vite-app/src/examples/NodeToolbar/index.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/index.tsx @@ -11,6 +11,7 @@ import ReactFlow, { } from 'reactflow'; import CustomNode from './CustomNode'; +import SelectedNodesToolbar from './SelectedNodesToolbar'; const nodeTypes: NodeTypes = { custom: CustomNode, @@ -79,6 +80,7 @@ export default function NodeToolbarExample() { + ); } diff --git a/examples/vite-app/src/examples/Subflow/index.tsx b/examples/vite-app/src/examples/Subflow/index.tsx index 84003c83..55aea018 100644 --- a/examples/vite-app/src/examples/Subflow/index.tsx +++ b/examples/vite-app/src/examples/Subflow/index.tsx @@ -11,6 +11,7 @@ import ReactFlow, { Controls, MiniMap, Background, + NodeOrigin, } from 'reactflow'; import DebugNode from './DebugNode'; @@ -90,7 +91,7 @@ const initialNodes: Node[] = [ { id: '5a', data: { label: 'Node 5a' }, - position: { x: 25, y: 50 }, + position: { x: 0, y: 0 }, className: 'light', parentNode: '5', extent: 'parent', diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index eff5abc5..1713e819 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/background +## 11.0.6 + +### Patch Changes + +- Updated dependencies [[`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4), [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f), [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af)]: + - @reactflow/core@11.3.1 + ## 11.0.5 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index b821f6b8..7df6af43 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.0.5", + "version": "11.0.6", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md index 729e65b7..40de22b6 100644 --- a/packages/controls/CHANGELOG.md +++ b/packages/controls/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/controls +## 11.0.6 + +### Patch Changes + +- Updated dependencies [[`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4), [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f), [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af)]: + - @reactflow/core@11.3.1 + ## 11.0.5 ### Patch Changes diff --git a/packages/controls/package.json b/packages/controls/package.json index 3084073e..91d19ade 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/controls", - "version": "11.0.5", + "version": "11.0.6", "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 89c25608..59a8cfca 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,13 @@ # @reactflow/core +## 11.3.1 + +### Patch Changes + +- [#2595](https://github.com/wbkd/react-flow/pull/2595) [`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4) Thanks [@chrtze](https://github.com/chrtze)! - Fix and improve the behaviour when using nodeOrigin in combination with subflows +- [#2602](https://github.com/wbkd/react-flow/pull/2602) [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f) Thanks [@sdegueldre](https://github.com/sdegueldre)! - Don't use try catch in wrapper for checking if provider is available +- [#2601](https://github.com/wbkd/react-flow/pull/2601) [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af) Thanks [@hoondeveloper](https://github.com/hoondeveloper)! - fix isRectObject function + ## 11.3.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 3f112b18..58f31d51 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/core", - "version": "11.3.0", + "version": "11.3.1", "description": "Core components and util functions of React Flow.", "keywords": [ "react", diff --git a/packages/core/src/container/ReactFlow/Wrapper.tsx b/packages/core/src/container/ReactFlow/Wrapper.tsx index 297e3b49..f0f56d24 100644 --- a/packages/core/src/container/ReactFlow/Wrapper.tsx +++ b/packages/core/src/container/ReactFlow/Wrapper.tsx @@ -1,16 +1,11 @@ +import { useContext } from 'react'; import type { FC, PropsWithChildren } from 'react'; -import { useStoreApi } from '../../hooks/useStore'; +import StoreContext from '../../contexts/RFStoreContext'; import ReactFlowProvider from '../../components/ReactFlowProvider'; const Wrapper: FC = ({ children }) => { - let isWrapped = true; - - try { - useStoreApi(); - } catch (e) { - isWrapped = false; - } + const isWrapped = useContext(StoreContext); if (isWrapped) { // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 1ececcad..3ab1d0bb 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -96,6 +96,7 @@ function useDrag({ onSelectionDrag, snapGrid, snapToGrid, + nodeOrigin, } = store.getState(); const pointerPos = getPointerPosition(event); // skip events without movement @@ -115,7 +116,7 @@ function useDrag({ nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]); } - const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent); + const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin); n.position = updatedPos.position; n.positionAbsolute = updatedPos.positionAbsolute; diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts index be25f3c1..61b0d3f9 100644 --- a/packages/core/src/hooks/useDrag/utils.ts +++ b/packages/core/src/hooks/useDrag/utils.ts @@ -1,7 +1,8 @@ import type { RefObject } from 'react'; import { clampPosition, devWarn } from '../../utils'; -import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, XYPosition } from '../../types'; +import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types'; +import { getNodePositionWithOrigin } from '../../utils/graph'; export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean { if (!node.parentNode) { @@ -60,20 +61,25 @@ export function calcNextPosition( node: NodeDragItem | Node, nextPosition: XYPosition, nodeInternals: NodeInternals, - nodeExtent?: CoordinateExtent + nodeExtent?: CoordinateExtent, + nodeOrigin: NodeOrigin = [0, 0] ): { position: XYPosition; positionAbsolute: XYPosition } { let currentExtent = node.extent || nodeExtent; if (node.extent === 'parent') { if (node.parentNode && node.width && node.height) { const parent = nodeInternals.get(node.parentNode); + const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); currentExtent = - parent?.positionAbsolute && parent?.width && parent?.height + parentPosition.positionAbsolute && parent?.width && parent?.height ? [ - [parent.positionAbsolute.x, parent.positionAbsolute.y], [ - parent.positionAbsolute.x + parent.width - node.width, - parent.positionAbsolute.y + parent.height - node.height, + parentPosition.positionAbsolute.x + node.width * nodeOrigin[0], + parentPosition.positionAbsolute.y + node.height * nodeOrigin[1], + ], + [ + parentPosition.positionAbsolute.x + parent.width - node.width + node.width * nodeOrigin[0], + parentPosition.positionAbsolute.y + parent.height - node.height + node.height * nodeOrigin[1], ], ] : currentExtent; @@ -84,8 +90,8 @@ export function calcNextPosition( } } else if (node.extent && node.parentNode) { const parent = nodeInternals.get(node.parentNode); - const parentX = parent?.positionAbsolute?.x ?? 0; - const parentY = parent?.positionAbsolute?.y ?? 0; + const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); + const { x: parentX, y: parentY } = parentPosition.positionAbsolute; currentExtent = [ [node.extent[0][0] + parentX, node.extent[0][1] + parentY], [node.extent[1][0] + parentX, node.extent[1][1] + parentY], @@ -96,7 +102,7 @@ export function calcNextPosition( if (node.parentNode) { const parentNode = nodeInternals.get(node.parentNode); - parentPosition = { x: parentNode?.positionAbsolute?.x ?? 0, y: parentNode?.positionAbsolute?.y ?? 0 }; + parentPosition = getNodePositionWithOrigin(parentNode, nodeOrigin).positionAbsolute; } const positionAbsolute = currentExtent diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 53a0d9f1..0915a456 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,6 +19,7 @@ export { updateEdge, getTransformForBounds, getRectOfNodes, + getNodePositionWithOrigin, } from './utils/graph'; export { applyNodeChanges, applyEdgeChanges } from './utils/changes'; export { getMarkerEnd } from './components/Edges/utils'; diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index e09abbd7..5ffd64c3 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -23,7 +23,8 @@ const createRFStore = () => createStore((set, get) => ({ ...initialState, setNodes: (nodes: Node[]) => { - set({ nodeInternals: createNodeInternals(nodes, get().nodeInternals) }); + const { nodeInternals, nodeOrigin } = get(); + set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin) }); }, setEdges: (edges: Edge[]) => { const { defaultEdgeOptions = {} } = get(); @@ -33,7 +34,7 @@ const createRFStore = () => const hasDefaultNodes = typeof nodes !== 'undefined'; const hasDefaultEdges = typeof edges !== 'undefined'; - const nodeInternals = hasDefaultNodes ? createNodeInternals(nodes, new Map()) : new Map(); + const nodeInternals = hasDefaultNodes ? createNodeInternals(nodes, new Map(), get().nodeOrigin) : new Map(); const nextEdges = hasDefaultEdges ? edges : []; set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); @@ -102,7 +103,7 @@ const createRFStore = () => } }, updateNodePositions: (nodeDragItems: NodeDragItem[] | Node[], positionChanged = true, dragging = false) => { - const { onNodesChange, nodeInternals, hasDefaultNodes } = get(); + const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get(); if (hasDefaultNodes || onNodesChange) { const changes = nodeDragItems.map((node) => { @@ -123,7 +124,7 @@ const createRFStore = () => if (changes?.length) { if (hasDefaultNodes) { const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); - const nextNodeInternals = createNodeInternals(nodes, nodeInternals); + const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); set({ nodeInternals: nextNodeInternals }); } diff --git a/packages/core/src/store/utils.ts b/packages/core/src/store/utils.ts index 2af09d46..02a4516b 100644 --- a/packages/core/src/store/utils.ts +++ b/packages/core/src/store/utils.ts @@ -2,7 +2,7 @@ import { zoomIdentity } from 'd3-zoom'; import type { StoreApi } from 'zustand'; import { internalsSymbol, isNumeric } from '../utils'; -import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph'; +import { getD3Transition, getRectOfNodes, getTransformForBounds, getNodePositionWithOrigin } from '../utils/graph'; import type { Edge, EdgeSelectionChange, @@ -12,6 +12,7 @@ import type { ReactFlowState, XYZPosition, FitViewOptions, + NodeOrigin, } from '../types'; type ParentNodes = Record; @@ -20,21 +21,33 @@ function calculateXYZPosition( node: Node, nodeInternals: NodeInternals, parentNodes: ParentNodes, - result: XYZPosition + result: XYZPosition, + nodeOrigin: NodeOrigin ): XYZPosition { if (!node.parentNode) { return result; } const parentNode = nodeInternals.get(node.parentNode)!; + const parentNodePosition = getNodePositionWithOrigin(parentNode, nodeOrigin); - return calculateXYZPosition(parentNode, nodeInternals, parentNodes, { - x: (result.x ?? 0) + (parentNode.position?.x ?? 0), - y: (result.y ?? 0) + (parentNode.position?.y ?? 0), - z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0, - }); + return calculateXYZPosition( + parentNode, + nodeInternals, + parentNodes, + { + x: (result.x ?? 0) + parentNodePosition.x, + y: (result.y ?? 0) + parentNodePosition.y, + z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0, + }, + nodeOrigin + ); } -export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals): NodeInternals { +export function createNodeInternals( + nodes: Node[], + nodeInternals: NodeInternals, + nodeOrigin: NodeOrigin +): NodeInternals { const nextNodeInternals = new Map(); const parentNodes: ParentNodes = {}; @@ -74,10 +87,16 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) } if (node.parentNode || parentNodes[node.id]) { - const { x, y, z } = calculateXYZPosition(node, nextNodeInternals, parentNodes, { - ...node.position, - z: node[internalsSymbol]?.z ?? 0, - }); + const { x, y, z } = calculateXYZPosition( + node, + nextNodeInternals, + parentNodes, + { + ...node.position, + z: node[internalsSymbol]?.z ?? 0, + }, + nodeOrigin + ); node.positionAbsolute = { x, diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 0408603f..ea390967 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -141,23 +141,54 @@ export const pointToRendererPoint = ( return position; }; +export const getNodePositionWithOrigin = ( + node: Node | undefined, + nodeOrigin: NodeOrigin = [0, 0] +): XYPosition & { positionAbsolute: XYPosition } => { + if (!node) { + return { + x: 0, + y: 0, + positionAbsolute: { + x: 0, + y: 0, + }, + }; + } + + const offset: XYPosition = { + x: (node.width ?? 0) * nodeOrigin[0], + y: (node.height ?? 0) * nodeOrigin[1], + }; + + return { + x: node.position.x - offset.x, + y: node.position.y - offset.y, + positionAbsolute: { + x: (node.positionAbsolute?.x ?? 0) - offset.x, + y: (node.positionAbsolute?.y ?? 0) - offset.y, + }, + }; +}; + export const getRectOfNodes = (nodes: Node[], nodeOrigin: NodeOrigin = [0, 0]): Rect => { if (nodes.length === 0) { return { x: 0, y: 0, width: 0, height: 0 }; } const box = nodes.reduce( - (currBox, { positionAbsolute, position, width, height }) => { + (currBox, node) => { + const { positionAbsolute, ...position } = getNodePositionWithOrigin(node, nodeOrigin); const nodeX = positionAbsolute ? positionAbsolute.x : position.x; const nodeY = positionAbsolute ? positionAbsolute.y : position.y; return getBoundsOfBoxes( currBox, rectToBox({ - x: nodeX - nodeOrigin[0] * (width || 0), - y: nodeY - nodeOrigin[1] * (height || 0), - width: width || 0, - height: height || 0, + x: nodeX, + y: nodeY, + width: node.width || 0, + height: node.height || 0, }) ); }, @@ -186,15 +217,17 @@ export const getNodesInside = ( const visibleNodes: Node[] = []; nodeInternals.forEach((node) => { - const { width, height, selectable = true, positionAbsolute = { x: 0, y: 0 } } = node; + const { width, height, selectable = true } = node; if (excludeNonSelectableNodes && !selectable) { return false; } + const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin); + const nodeRect = { - x: positionAbsolute.x - nodeOrigin[0] * (width || 0), - y: positionAbsolute.y - nodeOrigin[1] * (height || 0), + x: positionAbsolute.x, + y: positionAbsolute.y, width: width || 0, height: height || 0, }; @@ -243,4 +276,3 @@ export const getTransformForBounds = ( export const getD3Transition = (selection: D3Selection, duration = 0) => { return selection.transition().duration(duration); }; - diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 02cff5a0..d869a81a 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -53,7 +53,8 @@ export const getOverlappingArea = (rectA: Rect, rectB: Rect): number => { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isRectObject = (obj: any): obj is Rect => !!obj.width && !!obj.height && !!obj.x && !!obj.y; +export const isRectObject = (obj: any): obj is Rect => + isNumeric(obj.width) && isNumeric(obj.height) && isNumeric(obj.x) && isNumeric(obj.y); /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n); diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index e23e9f1d..b9cbb0be 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,20 @@ # @reactflow/minimap +## 11.2.2 + +### Patch Changes + +- [`7ece618d`](https://github.com/wbkd/react-flow/commit/7ece618d94b76183c1ecd45b16f6ab168168351b) Thanks [@lounsbrough](https://github.com/lounsbrough)! - Fix minimap node position + +## 11.2.1 + +### Patch Changes + +- [#2595](https://github.com/wbkd/react-flow/pull/2595) [`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4) Thanks [@chrtze](https://github.com/chrtze)! - Fix and improve the behaviour when using nodeOrigin in combination with subflows + +- Updated dependencies [[`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4), [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f), [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af)]: + - @reactflow/core@11.3.1 + ## 11.2.0 ### Minor Changes diff --git a/packages/minimap/package.json b/packages/minimap/package.json index f2fc7d20..d6dc1331 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.2.0", + "version": "11.2.2", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 4764a987..722f1423 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -7,7 +7,14 @@ import shallow from 'zustand/shallow'; import { zoom, zoomIdentity } from 'd3-zoom'; import type { D3ZoomEvent } from 'd3-zoom'; import { select, pointer } from 'd3-selection'; -import { useStore, getRectOfNodes, Panel, getBoundsOfRects, useStoreApi } from '@reactflow/core'; +import { + useStore, + getRectOfNodes, + Panel, + getBoundsOfRects, + useStoreApi, + getNodePositionWithOrigin, +} from '@reactflow/core'; import type { ReactFlowState, Rect } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; @@ -159,24 +166,28 @@ function MiniMap({ onClick={onSvgClick} > {ariaLabel && {ariaLabel}} - {nodes.map((node) => ( - - ))} + {nodes.map((node) => { + const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin); + + return ( + + ); + })} +const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) => a?.positionAbsolute?.x === b?.positionAbsolute?.x && a?.positionAbsolute?.y === b?.positionAbsolute?.y && a?.width === b?.width && @@ -25,6 +23,10 @@ const nodeEqualityFn = (a: SelectedNode, b: SelectedNode) => a?.selected === b?.selected && a?.[internalsSymbol]?.z === b?.[internalsSymbol]?.z; +const nodesEqualityFn = (a: Node[], b: Node[]) => { + return a.length === b.length && a.every((node, i) => nodeEqualityFn(node, b[i])); +}; + const storeSelector = (state: ReactFlowState) => ({ transform: state.transform, nodeOrigin: state.nodeOrigin, @@ -70,21 +72,36 @@ function NodeToolbar({ offset = 10, ...rest }: NodeToolbarProps) { - const nodeSelector = useCallback((state: ReactFlowState): SelectedNode => state.nodeInternals.get(nodeId), [nodeId]); - const node = useStore(nodeSelector, nodeEqualityFn); - const { transform, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow); - const isActive = typeof isVisible === 'boolean' ? isVisible : node?.selected && selectedNodesCount === 1; + const nodesSelector = useCallback( + (state: ReactFlowState): Node[] => { + const nodeIds: string[] = typeof nodeId === 'string' ? [nodeId] : nodeId; - if (!isActive || !node) { + return nodeIds.reduce((acc, id) => { + const node = state.nodeInternals.get(id); + if (node) { + acc.push(node); + } + return acc; + }, [] as Node[]); + }, + [nodeId] + ); + const nodes = useStore(nodesSelector, nodesEqualityFn); + const { transform, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow); + const isActive = + typeof isVisible === 'boolean' ? isVisible : nodes.length === 1 && nodes[0].selected && selectedNodesCount === 1; + + if (!isActive || !nodes.length) { return null; } - const nodeRect: Rect = getRectOfNodes([node], nodeOrigin); + const nodeRect: Rect = getRectOfNodes(nodes, nodeOrigin); + const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1)); const wrapperStyle: CSSProperties = { position: 'absolute', transform: getTransform(nodeRect, transform, position, offset), - zIndex: (node[internalsSymbol]?.z || 1) + 1, + zIndex, ...style, }; diff --git a/packages/node-toolbar/src/types.ts b/packages/node-toolbar/src/types.ts index 1e6576b6..a9f547ad 100644 --- a/packages/node-toolbar/src/types.ts +++ b/packages/node-toolbar/src/types.ts @@ -2,7 +2,7 @@ import { Position } from '@reactflow/core'; import type { HTMLAttributes } from 'react'; export type NodeToolbarProps = HTMLAttributes & { - nodeId: string; + nodeId: string | string[]; isVisible?: boolean; position?: Position; offset?: number; diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index 10c2affd..f3141ce1 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,5 +1,30 @@ # reactflow +## 11.3.2 + +### Patch Changes + +- [`7ece618d`](https://github.com/wbkd/react-flow/commit/7ece618d94b76183c1ecd45b16f6ab168168351b) Thanks [@lounsbrough](https://github.com/lounsbrough)! - Fix minimap node position + +- Updated dependencies [[`7ece618d`](https://github.com/wbkd/react-flow/commit/7ece618d94b76183c1ecd45b16f6ab168168351b)]: + - @reactflow/minimap@11.2.2 + +## 11.3.1 + +### Patch Changes + +- [#2595](https://github.com/wbkd/react-flow/pull/2595) [`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4) Thanks [@chrtze](https://github.com/chrtze)! - Fix and improve the behaviour when using nodeOrigin in combination with subflows +- [#2602](https://github.com/wbkd/react-flow/pull/2602) [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f) Thanks [@sdegueldre](https://github.com/sdegueldre)! - Don't use try catch in wrapper for checking if provider is available +- [#2601](https://github.com/wbkd/react-flow/pull/2601) [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af) Thanks [@hoondeveloper](https://github.com/hoondeveloper)! - fix isRectObject function +- [#2594](https://github.com/wbkd/react-flow/pull/2594) [`ec94d9ec`](https://github.com/wbkd/react-flow/commit/ec94d9ecdc964d6d66c04e9242f195614bbfdbbe) Thanks [@chrtze](https://github.com/chrtze)! - Allow multiple node ids to be passed for enabling multi selection toolbars + +- Updated dependencies [[`c828bfda`](https://github.com/wbkd/react-flow/commit/c828bfda0a8c4774bc43588640c7cca0cfdcb3f4), [`b0302ce4`](https://github.com/wbkd/react-flow/commit/b0302ce4261a992bee841bae84af347d03be690f), [`b2c72813`](https://github.com/wbkd/react-flow/commit/b2c728137d1b53e38883f044fa447585c377a6af), [`ec94d9ec`](https://github.com/wbkd/react-flow/commit/ec94d9ecdc964d6d66c04e9242f195614bbfdbbe)]: + - @reactflow/core@11.3.1 + - @reactflow/minimap@11.2.1 + - @reactflow/node-toolbar@1.0.1 + - @reactflow/background@11.0.6 + - @reactflow/controls@11.0.6 + ## 11.3.0 ### Minor Changes diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index 1178425d..f84f948a 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.3.0", + "version": "11.3.2", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react",