From 49636be0e96133044ca575b04ac6d6de7fda0082 Mon Sep 17 00:00:00 2001 From: Fabian Bieler Date: Thu, 28 Jul 2022 11:22:47 +0200 Subject: [PATCH 1/6] fix(validation): Remove css-classes if mouse moves directly to different handle. If two handles are next to each other or overlap it is possible for the mouse-cursor to move from one handle to the other without a mouse-move-event being fired where isHoveringHandle is false. Thus, the valid and connecting css-classes were not removed from the handle. This commit fixes this. --- src/components/Handle/handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Handle/handler.ts b/src/components/Handle/handler.ts index 6e03da96..ca0fe8d7 100644 --- a/src/components/Handle/handler.ts +++ b/src/components/Handle/handler.ts @@ -153,6 +153,7 @@ export function handleMouseDown({ } if (connection.source !== connection.target && elementBelow) { + resetRecentHandle(recentHoveredHandle); recentHoveredHandle = elementBelow; elementBelow.classList.add('react-flow__handle-connecting'); elementBelow.classList.toggle('react-flow__handle-valid', isValid); From d07e76018fad52f0eb1c004c39365bdf51face5e Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Aug 2022 18:12:02 +0200 Subject: [PATCH 2/6] fix(minZoom,maxZoom): pass min- maxZoom for initial view closes #2343 --- example/src/CustomNode/index.tsx | 7 ++++--- src/container/FlowRenderer/index.tsx | 10 ++++++++-- src/container/GraphView/index.tsx | 20 ++++++++++++++++++-- src/container/ReactFlow/index.tsx | 10 +++++++--- src/container/ZoomPane/index.tsx | 10 +++++++--- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/example/src/CustomNode/index.tsx b/example/src/CustomNode/index.tsx index 8169dc52..d867a9c4 100644 --- a/example/src/CustomNode/index.tsx +++ b/example/src/CustomNode/index.tsx @@ -18,8 +18,8 @@ import ColorSelectorNode from './ColorSelectorNode'; const onInit = (reactFlowInstance: ReactFlowInstance) => { console.log('flow loaded:', reactFlowInstance); - reactFlowInstance.fitView(); }; + const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); @@ -117,8 +117,9 @@ const CustomNodeFlow = () => { connectionLineStyle={connectionLineStyle} snapToGrid={true} snapGrid={snapGrid} - defaultZoom={1.5} - fitView + defaultZoom={0.3} + minZoom={0.3} + maxZoom={2} > { diff --git a/src/container/FlowRenderer/index.tsx b/src/container/FlowRenderer/index.tsx index 1eae6797..6b48feea 100644 --- a/src/container/FlowRenderer/index.tsx +++ b/src/container/FlowRenderer/index.tsx @@ -48,8 +48,11 @@ const FlowRenderer = ({ panOnScrollMode, zoomOnDoubleClick, panOnDrag, - defaultPosition, + translateExtent, + minZoom, + maxZoom, defaultZoom, + defaultPosition, preventScrolling, onSelectionContextMenu, noWheelClassName, @@ -84,8 +87,11 @@ const FlowRenderer = ({ panOnScrollMode={panOnScrollMode} zoomOnDoubleClick={zoomOnDoubleClick} panOnDrag={panOnDrag} - defaultPosition={defaultPosition} + translateExtent={translateExtent} + minZoom={minZoom} + maxZoom={maxZoom} defaultZoom={defaultZoom} + defaultPosition={defaultPosition} zoomActivationKeyCode={zoomActivationKeyCode} preventScrolling={preventScrolling} noWheelClassName={noWheelClassName} diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index f7774444..9b55e973 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -5,7 +5,14 @@ import NodeRenderer from '../NodeRenderer'; import EdgeRenderer from '../EdgeRenderer'; import Viewport from '../Viewport'; import useOnInitHandler from '../../hooks/useOnInitHandler'; -import { NodeTypesWrapped, EdgeTypesWrapped, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types'; +import { + NodeTypesWrapped, + EdgeTypesWrapped, + ConnectionLineType, + KeyCode, + ReactFlowProps, + CoordinateExtent, +} from '../../types'; export interface GraphViewProps extends Omit { @@ -16,6 +23,9 @@ export interface GraphViewProps multiSelectionKeyCode: KeyCode | null; connectionLineType: ConnectionLineType; onlyRenderVisibleElements: boolean; + translateExtent: CoordinateExtent; + minZoom: number; + maxZoom: number; defaultZoom: number; defaultPosition: [number, number]; defaultMarkerColor: string; @@ -52,6 +62,9 @@ const GraphView = ({ onlyRenderVisibleElements, elementsSelectable, selectNodesOnDrag, + translateExtent, + minZoom, + maxZoom, defaultZoom, defaultPosition, preventScrolling, @@ -102,8 +115,11 @@ const GraphView = ({ panOnScrollSpeed={panOnScrollSpeed} panOnScrollMode={panOnScrollMode} panOnDrag={panOnDrag} - defaultPosition={defaultPosition} + translateExtent={translateExtent} + minZoom={minZoom} + maxZoom={maxZoom} defaultZoom={defaultZoom} + defaultPosition={defaultPosition} onSelectionContextMenu={onSelectionContextMenu} preventScrolling={preventScrolling} noDragClassName={noDragClassName} diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 257d37f4..debdd3ae 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -26,6 +26,7 @@ import GraphView from '../GraphView'; import { createNodeTypes } from '../NodeRenderer/utils'; import { injectStyle, useNodeOrEdgeTypes } from './utils'; import Wrapper from './Wrapper'; +import { infiniteExtent } from '../../store/initialState'; if (__INJECT_STYLES__) { injectStyle(css as unknown as string); @@ -103,11 +104,11 @@ const ReactFlow = forwardRef( nodesDraggable, nodesConnectable, elementsSelectable, - minZoom, - maxZoom, + minZoom = 0.5, + maxZoom = 2, defaultZoom = 1, defaultPosition = initDefaultPosition, - translateExtent, + translateExtent = infiniteExtent, preventScrolling = true, nodeExtent, defaultMarkerColor = '#b1b1b7', @@ -177,6 +178,9 @@ const ReactFlow = forwardRef( zoomActivationKeyCode={zoomActivationKeyCode} onlyRenderVisibleElements={onlyRenderVisibleElements} selectNodesOnDrag={selectNodesOnDrag} + translateExtent={translateExtent} + minZoom={minZoom} + maxZoom={maxZoom} defaultZoom={defaultZoom} defaultPosition={defaultPosition} preventScrolling={preventScrolling} diff --git a/src/container/ZoomPane/index.tsx b/src/container/ZoomPane/index.tsx index 9c6075d9..fd309741 100644 --- a/src/container/ZoomPane/index.tsx +++ b/src/container/ZoomPane/index.tsx @@ -45,8 +45,11 @@ const ZoomPane = ({ selectionKeyPressed, elementsSelectable, panOnDrag = true, - defaultPosition = [0, 0], + translateExtent, + minZoom, + maxZoom, defaultZoom = 1, + defaultPosition = [0, 0], zoomActivationKeyCode, preventScrolling = true, children, @@ -56,6 +59,7 @@ const ZoomPane = ({ const store = useStoreApi(); const isZoomingOrPanning = useRef(false); const zoomPane = useRef(null); + const initialized = useRef(false); const prevTransform = useRef({ x: 0, y: 0, zoom: 0 }); const { d3Zoom, d3Selection, d3ZoomHandler } = useStore(selector, shallow); const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode); @@ -63,8 +67,8 @@ const ZoomPane = ({ useResizeHandler(zoomPane); useEffect(() => { - if (zoomPane.current) { - const { minZoom, maxZoom, translateExtent } = store.getState(); + if (zoomPane.current && !initialized.current) { + initialized.current = true; const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent); const selection = select(zoomPane.current as Element).call(d3ZoomInstance); From 46ba0dee9da49a564570840d3ae2775340c9d233 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Aug 2022 18:13:32 +0200 Subject: [PATCH 3/6] chore: release v10.3.13 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d355d1c..e5e44b7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-flow-renderer", - "version": "10.3.12", + "version": "10.3.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-flow-renderer", - "version": "10.3.12", + "version": "10.3.13", "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.9", diff --git a/package.json b/package.json index 7ab95ad5..e9961761 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-flow-renderer", - "version": "10.3.12", + "version": "10.3.13", "engines": { "node": ">=14" }, From 753f4424683a0579af921d402066b2d7c25740a3 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 6 Aug 2022 16:57:32 +0200 Subject: [PATCH 4/6] chore(zustand): upgrade to v4 --- packages/core/package.json | 2 +- .../src/components/ConnectionLine/index.tsx | 6 +-- .../core/src/components/Edges/wrapEdge.tsx | 2 +- packages/core/src/components/Handle/index.tsx | 2 +- .../core/src/components/Nodes/wrapNode.tsx | 2 +- .../src/components/NodesSelection/index.tsx | 2 +- .../components/ReactFlowProvider/index.tsx | 19 ++++++--- .../components/SelectionListener/index.tsx | 2 +- .../src/components/StoreUpdater/index.tsx | 6 +-- .../src/components/UserSelection/index.tsx | 2 +- .../EdgeRenderer/MarkerDefinitions.tsx | 2 +- .../core/src/container/EdgeRenderer/index.tsx | 2 +- .../core/src/container/FlowRenderer/index.tsx | 2 +- .../core/src/container/NodeRenderer/index.tsx | 4 +- .../core/src/container/ReactFlow/Wrapper.tsx | 5 ++- .../core/src/container/Viewport/index.tsx | 2 +- .../core/src/container/ZoomPane/index.tsx | 4 +- packages/core/src/contexts/RFStoreContext.ts | 8 ++++ packages/core/src/hooks/useDrag/index.ts | 2 +- packages/core/src/hooks/useEdges.ts | 2 +- .../core/src/hooks/useGlobalKeyHandler.ts | 2 +- packages/core/src/hooks/useNodes.ts | 2 +- packages/core/src/hooks/useReactFlow.ts | 2 +- packages/core/src/hooks/useResizeHandler.ts | 2 +- packages/core/src/hooks/useStore.ts | 42 +++++++++++++++++++ .../core/src/hooks/useUpdateNodeInternals.ts | 2 +- .../core/src/hooks/useUpdateNodePositions.ts | 2 +- packages/core/src/hooks/useViewport.ts | 2 +- packages/core/src/hooks/useViewportHelper.ts | 2 +- packages/core/src/hooks/useVisibleEdges.ts | 2 +- packages/core/src/hooks/useVisibleNodes.ts | 2 +- packages/core/src/index.ts | 2 +- packages/core/src/store/index.ts | 11 ++--- packages/minimap/package.json | 2 +- yarn.lock | 26 +++++++++--- 35 files changed, 126 insertions(+), 55 deletions(-) create mode 100644 packages/core/src/contexts/RFStoreContext.ts create mode 100644 packages/core/src/hooks/useStore.ts diff --git a/packages/core/package.json b/packages/core/package.json index 025abc98..a5622a09 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,7 +22,7 @@ "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", - "zustand": "^3.7.2" + "zustand": "^4.0.0" }, "peerDependencies": { "react": ">=18", diff --git a/packages/core/src/components/ConnectionLine/index.tsx b/packages/core/src/components/ConnectionLine/index.tsx index 8858a197..3f6b96ab 100644 --- a/packages/core/src/components/ConnectionLine/index.tsx +++ b/packages/core/src/components/ConnectionLine/index.tsx @@ -1,10 +1,10 @@ import React, { CSSProperties, useCallback } from 'react'; import shallow from 'zustand/shallow'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; import { getBezierPath } from '../Edges/BezierEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; -import { ConnectionLineType, ConnectionLineComponent, HandleType, Position } from '../../types'; +import { ConnectionLineType, ConnectionLineComponent, HandleType, Position, ReactFlowStore } from '../../types'; import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge'; import { internalsSymbol } from '../../utils'; @@ -34,7 +34,7 @@ export default ({ }: ConnectionLineProps) => { const { fromNode, handleId, toX, toY } = useStore( useCallback( - (s) => ({ + (s: ReactFlowStore) => ({ fromNode: s.nodeInternals.get(connectionNodeId), handleId: s.connectionHandleId, toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2], diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 7c394f11..f1ed3285 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -1,7 +1,7 @@ import React, { memo, ComponentType, useState, useMemo, KeyboardEvent, useRef } from 'react'; import cc from 'classcat'; -import { useStoreApi } from '../../store'; +import { useStoreApi } from '../../hooks/useStore'; import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; import { handleMouseDown } from '../Handle/handler'; import { EdgeAnchor } from './EdgeAnchor'; diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index 357eac76..7f514843 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -2,7 +2,7 @@ import React, { memo, useContext, HTMLAttributes, forwardRef } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; -import { useStore, useStoreApi } from '../../store'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import NodeIdContext from '../../contexts/NodeIdContext'; import { HandleProps, Connection, ReactFlowState, Position } from '../../types'; import { checkElementBelowIsValid, handleMouseDown } from './handler'; diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx index d8a67c23..0b7d619c 100644 --- a/packages/core/src/components/Nodes/wrapNode.tsx +++ b/packages/core/src/components/Nodes/wrapNode.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, memo, ComponentType, MouseEvent, KeyboardEvent } from 'react'; import cc from 'classcat'; -import { useStoreApi } from '../../store'; +import { useStoreApi } from '../../hooks/useStore'; import { Provider } from '../../contexts/NodeIdContext'; import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions'; import useDrag from '../../hooks/useDrag'; diff --git a/packages/core/src/components/NodesSelection/index.tsx b/packages/core/src/components/NodesSelection/index.tsx index 0febc5c2..80a07288 100644 --- a/packages/core/src/components/NodesSelection/index.tsx +++ b/packages/core/src/components/NodesSelection/index.tsx @@ -7,7 +7,7 @@ import React, { memo, useRef, MouseEvent, KeyboardEvent, useEffect } from 'react import cc from 'classcat'; import shallow from 'zustand/shallow'; -import { useStore, useStoreApi } from '../../store'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import { Node, ReactFlowState } from '../../types'; import { getRectOfNodes } from '../../utils/graph'; import useDrag from '../../hooks/useDrag'; diff --git a/packages/core/src/components/ReactFlowProvider/index.tsx b/packages/core/src/components/ReactFlowProvider/index.tsx index 3f765eed..7c4862a4 100644 --- a/packages/core/src/components/ReactFlowProvider/index.tsx +++ b/packages/core/src/components/ReactFlowProvider/index.tsx @@ -1,10 +1,19 @@ -import React, { FC, PropsWithChildren } from 'react'; +import React, { FC, PropsWithChildren, useRef } from 'react'; +import { StoreApi } from 'zustand'; -import { Provider, createStore } from '../../store'; +import { Provider } from '../../contexts/RFStoreContext'; +import { createRFStore } from '../../store'; +import { ReactFlowState } from '../../types'; -const ReactFlowProvider: FC> = ({ children }) => ( - {children} -); +const ReactFlowProvider: FC> = ({ children }) => { + const storeRef = useRef | null>(null); + + if (!storeRef.current) { + storeRef.current = createRFStore(); + } + + return {children}; +}; ReactFlowProvider.displayName = 'ReactFlowProvider'; diff --git a/packages/core/src/components/SelectionListener/index.tsx b/packages/core/src/components/SelectionListener/index.tsx index 6f052a3a..be00831a 100644 --- a/packages/core/src/components/SelectionListener/index.tsx +++ b/packages/core/src/components/SelectionListener/index.tsx @@ -2,7 +2,7 @@ import { memo, useEffect } from 'react'; import shallow from 'zustand/shallow'; import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; interface SelectionListenerProps { onSelectionChange: OnSelectionChangeFunc; diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 2ccc56cc..43ae7ea6 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -1,8 +1,8 @@ import { useEffect } from 'react'; -import { SetState } from 'zustand'; +import { StoreApi } from 'zustand'; import shallow from 'zustand/shallow'; -import { useStore, useStoreApi } from '../../store'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps } from '../../types'; type StoreUpdaterProps = Pick< @@ -61,7 +61,7 @@ function useStoreUpdater(value: T | undefined, setStoreState: (param: T) => v }, [value]); } -function useDirectStoreUpdater(key: keyof ReactFlowState, value: any, setState: SetState) { +function useDirectStoreUpdater(key: keyof ReactFlowState, value: any, setState: StoreApi['setState']) { useEffect(() => { if (typeof value !== 'undefined') { // @ts-ignore diff --git a/packages/core/src/components/UserSelection/index.tsx b/packages/core/src/components/UserSelection/index.tsx index 76ee7085..6a0e8a5a 100644 --- a/packages/core/src/components/UserSelection/index.tsx +++ b/packages/core/src/components/UserSelection/index.tsx @@ -5,7 +5,7 @@ import React, { memo, useState, useRef } from 'react'; import shallow from 'zustand/shallow'; -import { useStore, useStoreApi } from '../../store'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import { getSelectionChanges } from '../../utils/changes'; import { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types'; import { getConnectedEdges, getNodesInside } from '../../utils/graph'; diff --git a/packages/core/src/container/EdgeRenderer/MarkerDefinitions.tsx b/packages/core/src/container/EdgeRenderer/MarkerDefinitions.tsx index 7ce597ef..d887df90 100644 --- a/packages/core/src/container/EdgeRenderer/MarkerDefinitions.tsx +++ b/packages/core/src/container/EdgeRenderer/MarkerDefinitions.tsx @@ -1,6 +1,6 @@ import React, { memo, useCallback } from 'react'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; import { EdgeMarker, ReactFlowState } from '../../types'; import { getMarkerId } from '../../utils/graph'; import { useMarkerSymbol } from './MarkerSymbols'; diff --git a/packages/core/src/container/EdgeRenderer/index.tsx b/packages/core/src/container/EdgeRenderer/index.tsx index cc293d70..8830a400 100644 --- a/packages/core/src/container/EdgeRenderer/index.tsx +++ b/packages/core/src/container/EdgeRenderer/index.tsx @@ -2,7 +2,7 @@ import React, { memo, CSSProperties } from 'react'; import shallow from 'zustand/shallow'; import cc from 'classcat'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; import ConnectionLine from '../../components/ConnectionLine/index'; import MarkerDefinitions from './MarkerDefinitions'; import { getEdgePositions, getHandle, getNodeData } from './utils'; diff --git a/packages/core/src/container/FlowRenderer/index.tsx b/packages/core/src/container/FlowRenderer/index.tsx index ba55e988..7666d5ff 100644 --- a/packages/core/src/container/FlowRenderer/index.tsx +++ b/packages/core/src/container/FlowRenderer/index.tsx @@ -1,6 +1,6 @@ import React, { memo, ReactNode, WheelEvent, MouseEvent } from 'react'; -import { useStore, useStoreApi } from '../../store'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useKeyPress from '../../hooks/useKeyPress'; import { GraphViewProps } from '../GraphView'; diff --git a/packages/core/src/container/NodeRenderer/index.tsx b/packages/core/src/container/NodeRenderer/index.tsx index 302bb3d9..a465e195 100644 --- a/packages/core/src/container/NodeRenderer/index.tsx +++ b/packages/core/src/container/NodeRenderer/index.tsx @@ -2,10 +2,10 @@ import React, { memo, useMemo, ComponentType, useEffect, useRef } from 'react'; import shallow from 'zustand/shallow'; import useVisibleNodes from '../../hooks/useVisibleNodes'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; +import { internalsSymbol } from '../../utils'; import { containerStyle } from '../../styles'; import { NodeMouseHandler, NodeTypesWrapped, Position, ReactFlowState, WrapNodeProps } from '../../types'; -import { internalsSymbol } from '../../utils'; interface NodeRendererProps { nodeTypes: NodeTypesWrapped; diff --git a/packages/core/src/container/ReactFlow/Wrapper.tsx b/packages/core/src/container/ReactFlow/Wrapper.tsx index 23414ec4..e7bd27f3 100644 --- a/packages/core/src/container/ReactFlow/Wrapper.tsx +++ b/packages/core/src/container/ReactFlow/Wrapper.tsx @@ -1,6 +1,7 @@ import React, { FC, PropsWithChildren } from 'react'; -import { Provider, createStore, useStoreApi } from '../../store'; +import { useStoreApi } from '../../hooks/useStore'; +import ReactFlowProvider from '../../components/ReactFlowProvider'; const Wrapper: FC> = ({ children }) => { let isWrapped = true; @@ -17,7 +18,7 @@ const Wrapper: FC> = ({ children }) => { return <>{children}; } - return {children}; + return {children}; }; Wrapper.displayName = 'ReactFlowWrapper'; diff --git a/packages/core/src/container/Viewport/index.tsx b/packages/core/src/container/Viewport/index.tsx index ec6877bd..32880b10 100644 --- a/packages/core/src/container/Viewport/index.tsx +++ b/packages/core/src/container/Viewport/index.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; -import { useStore } from '../../store'; +import { useStore } from '../../hooks/useStore'; import { ReactFlowState } from '../../types'; const selector = (s: ReactFlowState) => `translate(${s.transform[0]}px,${s.transform[1]}px) scale(${s.transform[2]})`; diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index 4d0dd67f..3f5be28e 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -6,10 +6,10 @@ import shallow from 'zustand/shallow'; import { clamp } from '../../utils'; import useKeyPress from '../../hooks/useKeyPress'; import useResizeHandler from '../../hooks/useResizeHandler'; -import { useStore, useStoreApi } from '../../store'; -import { Viewport, PanOnScrollMode, ReactFlowState } from '../../types'; +import { useStore, useStoreApi } from '../../hooks/useStore'; import { FlowRendererProps } from '../FlowRenderer'; import { containerStyle } from '../../styles'; +import { Viewport, PanOnScrollMode, ReactFlowState } from '../../types'; type ZoomPaneProps = Omit< FlowRendererProps, diff --git a/packages/core/src/contexts/RFStoreContext.ts b/packages/core/src/contexts/RFStoreContext.ts new file mode 100644 index 00000000..7f0d8be8 --- /dev/null +++ b/packages/core/src/contexts/RFStoreContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +import { createRFStore } from '../store'; + +const StoreContext = createContext | null>(null); + +export const Provider = StoreContext.Provider; +export default StoreContext; diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index a43c43b9..025621d4 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -2,7 +2,7 @@ import { RefObject, useEffect, useRef, MouseEvent, useState, useCallback } from import { D3DragEvent, drag, SubjectPosition } from 'd3-drag'; import { select } from 'd3-selection'; -import { useStoreApi } from '../../store'; +import { useStoreApi } from '../../hooks/useStore'; import { pointToRendererPoint } from '../../utils/graph'; import { NodeDragItem, Node, SelectionDragHandler } from '../../types'; import { getDragItems, getEventHandlerParams, hasSelector, calcNextPosition } from './utils'; diff --git a/packages/core/src/hooks/useEdges.ts b/packages/core/src/hooks/useEdges.ts index eb2fa464..eee73eb0 100644 --- a/packages/core/src/hooks/useEdges.ts +++ b/packages/core/src/hooks/useEdges.ts @@ -1,4 +1,4 @@ -import { useStore } from '../store'; +import { useStore } from '../hooks/useStore'; import { Edge, ReactFlowState } from '../types'; const edgesSelector = (state: ReactFlowState) => state.edges; diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index 208d893b..742c9cee 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { useStoreApi } from '../store'; +import { useStoreApi } from '../hooks/useStore'; import useKeyPress from './useKeyPress'; import { getConnectedEdges } from '../utils/graph'; import { KeyCode, NodeChange, Node } from '../types'; diff --git a/packages/core/src/hooks/useNodes.ts b/packages/core/src/hooks/useNodes.ts index 8a728a2d..684becea 100644 --- a/packages/core/src/hooks/useNodes.ts +++ b/packages/core/src/hooks/useNodes.ts @@ -1,4 +1,4 @@ -import { useStore } from '../store'; +import { useStore } from '../hooks/useStore'; import { Node, ReactFlowState } from '../types'; const nodesSelector = (state: ReactFlowState) => Array.from(state.nodeInternals.values()); diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 69d25cb9..0925c7ce 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import useViewportHelper from './useViewportHelper'; -import { useStoreApi } from '../store'; +import { useStoreApi } from '../hooks/useStore'; import { ReactFlowInstance, Instance, diff --git a/packages/core/src/hooks/useResizeHandler.ts b/packages/core/src/hooks/useResizeHandler.ts index 6ce1848b..7d148db9 100644 --- a/packages/core/src/hooks/useResizeHandler.ts +++ b/packages/core/src/hooks/useResizeHandler.ts @@ -1,6 +1,6 @@ import { useEffect, MutableRefObject } from 'react'; -import { useStoreApi } from '../store'; +import { useStoreApi } from '../hooks/useStore'; import { getDimensions } from '../utils'; function useResizeHandler(rendererNode: MutableRefObject): void { diff --git a/packages/core/src/hooks/useStore.ts b/packages/core/src/hooks/useStore.ts new file mode 100644 index 00000000..87ae9304 --- /dev/null +++ b/packages/core/src/hooks/useStore.ts @@ -0,0 +1,42 @@ +import { useContext, useMemo } from 'react'; +import { StoreApi, useStore as useZustandStore } from 'zustand'; + +import StoreContext from '../contexts/RFStoreContext'; +import { ReactFlowState } from '../types'; + +const errorMessage = 'Seems like you have not used zustand provider as an ancestor.'; + +type ExtractState = StoreApi extends { getState: () => infer T } ? T : never; + +function useStore( + selector: (state: ExtractState) => StateSlice, + equalityFn?: (a: StateSlice, b: StateSlice) => boolean +) { + const store = useContext(StoreContext); + + if (store === null) { + throw new Error(errorMessage); + } + + return useZustandStore(store, selector, equalityFn); +} + +const useStoreApi = () => { + const store = useContext(StoreContext); + + if (store === null) { + throw new Error(errorMessage); + } + + return useMemo( + () => ({ + getState: store.getState, + setState: store.setState, + subscribe: store.subscribe, + destroy: store.destroy, + }), + [store] + ); +}; + +export { useStore, useStoreApi }; diff --git a/packages/core/src/hooks/useUpdateNodeInternals.ts b/packages/core/src/hooks/useUpdateNodeInternals.ts index 71861036..8165eb77 100644 --- a/packages/core/src/hooks/useUpdateNodeInternals.ts +++ b/packages/core/src/hooks/useUpdateNodeInternals.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { useStore, useStoreApi } from '../store'; +import { useStore, useStoreApi } from '../hooks/useStore'; import { UpdateNodeInternals, ReactFlowState } from '../types'; const selector = (state: ReactFlowState) => state.updateNodeDimensions; diff --git a/packages/core/src/hooks/useUpdateNodePositions.ts b/packages/core/src/hooks/useUpdateNodePositions.ts index 4bc7c0aa..a77d50a0 100644 --- a/packages/core/src/hooks/useUpdateNodePositions.ts +++ b/packages/core/src/hooks/useUpdateNodePositions.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { useStoreApi } from '../store'; +import { useStoreApi } from '../hooks/useStore'; import { calcNextPosition } from './useDrag/utils'; import { XYPosition } from '../types'; diff --git a/packages/core/src/hooks/useViewport.ts b/packages/core/src/hooks/useViewport.ts index f4e61587..025040bd 100644 --- a/packages/core/src/hooks/useViewport.ts +++ b/packages/core/src/hooks/useViewport.ts @@ -1,6 +1,6 @@ import shallow from 'zustand/shallow'; -import { useStore } from '../store'; +import { useStore } from '../hooks/useStore'; import { Viewport, ReactFlowState } from '../types'; const viewportSelector = (state: ReactFlowState) => ({ diff --git a/packages/core/src/hooks/useViewportHelper.ts b/packages/core/src/hooks/useViewportHelper.ts index 10e21193..e1fc08a3 100644 --- a/packages/core/src/hooks/useViewportHelper.ts +++ b/packages/core/src/hooks/useViewportHelper.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { zoomIdentity } from 'd3-zoom'; import shallow from 'zustand/shallow'; -import { useStoreApi, useStore } from '../store'; +import { useStoreApi, useStore } from '../hooks/useStore'; import { pointToRendererPoint, getTransformForBounds, getD3Transition } from '../utils/graph'; import { FitViewOptions, Viewport, ViewportHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types'; import { fitView as fitViewStore } from '../store/utils'; diff --git a/packages/core/src/hooks/useVisibleEdges.ts b/packages/core/src/hooks/useVisibleEdges.ts index f7b8534a..4f061a4b 100644 --- a/packages/core/src/hooks/useVisibleEdges.ts +++ b/packages/core/src/hooks/useVisibleEdges.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { useStore } from '../store'; +import { useStore } from '../hooks/useStore'; import { isEdgeVisible } from '../container/EdgeRenderer/utils'; import { ReactFlowState, NodeInternals, Edge } from '../types'; import { internalsSymbol, isNumeric } from '../utils'; diff --git a/packages/core/src/hooks/useVisibleNodes.ts b/packages/core/src/hooks/useVisibleNodes.ts index 8c8b7190..ad81e3be 100644 --- a/packages/core/src/hooks/useVisibleNodes.ts +++ b/packages/core/src/hooks/useVisibleNodes.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { useStore } from '../store'; +import { useStore } from '../hooks/useStore'; import { getNodesInside } from '../utils/graph'; import { ReactFlowState } from '../types'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3e4dc8c2..0edee1cc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -39,6 +39,6 @@ export { default as useEdges } from './hooks/useEdges'; export { default as useViewport } from './hooks/useViewport'; export { default as useKeyPress } from './hooks/useKeyPress'; export * from './hooks/useNodesEdgesState'; -export { useStore, useStoreApi } from './store'; +export { useStore, useStoreApi } from './hooks/useStore'; export * from './types'; diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index 5a54116e..ed83e72b 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -1,5 +1,4 @@ -import create from 'zustand'; -import createContext from 'zustand/context'; +import { createStore } from 'zustand'; import { clampPosition, getDimensions, internalsSymbol } from '../utils'; import { applyNodeChanges } from '../utils/changes'; @@ -21,10 +20,8 @@ import { createSelectionChange, getSelectionChanges } from '../utils/changes'; import { createNodeInternals, fitView, updateNodesAndEdgesSelections } from './utils'; import initialState from './initialState'; -const { Provider, useStore, useStoreApi } = createContext(); - -const createStore = () => - create((set, get) => ({ +const createRFStore = () => + createStore((set, get) => ({ ...initialState, setNodes: (nodes: Node[]) => { set({ nodeInternals: createNodeInternals(nodes, get().nodeInternals) }); @@ -236,4 +233,4 @@ const createStore = () => reset: () => set({ ...initialState }), })); -export { Provider, useStore, createStore, useStoreApi }; +export { createRFStore }; diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 8d474845..e6f0803f 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -9,7 +9,7 @@ "@react-flow/core": "^11.0.0", "@react-flow/css-utils": "^11.0.0", "classcat": "^5.0.3", - "zustand": "^3.7.2" + "zustand": "^4.0.0" }, "peerDependencies": { "react": "^18.1.0", diff --git a/yarn.lock b/yarn.lock index c479a6d5..e97c7f3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1938,7 +1938,7 @@ __metadata: postcss-cli: ^10.0.0 postcss-import: ^14.1.0 postcss-nested: ^5.0.6 - zustand: ^3.7.2 + zustand: ^4.0.0 peerDependencies: react: ">=18" react-dom: ">=18" @@ -1958,7 +1958,7 @@ __metadata: "@react-flow/core": ^11.0.0 "@react-flow/css-utils": ^11.0.0 classcat: ^5.0.3 - zustand: ^3.7.2 + zustand: ^4.0.0 peerDependencies: react: ^18.1.0 react-dom: ">=18" @@ -8216,6 +8216,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -8414,14 +8423,19 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^3.7.2": - version: 3.7.2 - resolution: "zustand@npm:3.7.2" +"zustand@npm:^4.0.0": + version: 4.0.0 + resolution: "zustand@npm:4.0.0" + dependencies: + use-sync-external-store: 1.2.0 peerDependencies: + immer: ">=9.0" react: ">=16.8" peerDependenciesMeta: + immer: + optional: true react: optional: true - checksum: 18f025b1b666a311121d3855303ff58e6a21fd107920ca474307e86984c13338d6c4cfa5cdf13382a9e0f76821f2554a12d4d200a98a66b58637e729f149797b + checksum: b8ae5971dff79a1007d46f9a6bfa39ed5cc5b101a877eeec069ebdb6c8285ec8e3463e40cfa7c0f1507a41f43fe26ffb44b556063cb70ac03bccf1ea6e0f69d6 languageName: node linkType: hard From 5cf45209924ef2be0b7594dd11f660d08a4cd88c Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 6 Aug 2022 17:19:54 +0200 Subject: [PATCH 5/6] refactor(useStore): show troubleshooting link --- packages/core/src/hooks/useStore.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/hooks/useStore.ts b/packages/core/src/hooks/useStore.ts index 87ae9304..3cdccb8a 100644 --- a/packages/core/src/hooks/useStore.ts +++ b/packages/core/src/hooks/useStore.ts @@ -4,12 +4,13 @@ import { StoreApi, useStore as useZustandStore } from 'zustand'; import StoreContext from '../contexts/RFStoreContext'; import { ReactFlowState } from '../types'; -const errorMessage = 'Seems like you have not used zustand provider as an ancestor.'; +const errorMessage = + '[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#100'; type ExtractState = StoreApi extends { getState: () => infer T } ? T : never; function useStore( - selector: (state: ExtractState) => StateSlice, + selector: (state: ReactFlowState) => StateSlice, equalityFn?: (a: StateSlice, b: StateSlice) => boolean ) { const store = useContext(StoreContext); From 29013155fdf243d3b7c5dbf27300307d31839555 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 6 Aug 2022 18:00:07 +0200 Subject: [PATCH 6/6] chore(store-updater): cleanup --- packages/core/src/components/StoreUpdater/index.tsx | 12 +++++++----- packages/core/src/container/ZoomPane/index.tsx | 2 +- packages/core/src/hooks/useResizeHandler.ts | 4 ++-- packages/core/src/types/general.ts | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 43ae7ea6..a5bc61e7 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -3,7 +3,7 @@ import { StoreApi } from 'zustand'; import shallow from 'zustand/shallow'; import { useStore, useStoreApi } from '../../hooks/useStore'; -import { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps } from '../../types'; +import { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps, ReactFlowStore } from '../../types'; type StoreUpdaterProps = Pick< ReactFlowProps, @@ -61,10 +61,14 @@ function useStoreUpdater(value: T | undefined, setStoreState: (param: T) => v }, [value]); } -function useDirectStoreUpdater(key: keyof ReactFlowState, value: any, setState: StoreApi['setState']) { +// updates with values in store that don't have a dedicated setter function +function useDirectStoreUpdater( + key: keyof ReactFlowStore, + value: unknown, + setState: StoreApi['setState'] +) { useEffect(() => { if (typeof value !== 'undefined') { - // @ts-ignore setState({ [key]: value }); } }, [value]); @@ -153,8 +157,6 @@ const StoreUpdater = ({ useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); - useStoreUpdater(defaultNodes, setNodes); - useStoreUpdater(defaultEdges, setEdges); useStoreUpdater(minZoom, setMinZoom); useStoreUpdater(maxZoom, setMaxZoom); useStoreUpdater(translateExtent, setTranslateExtent); diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index 3f5be28e..e700c731 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -83,7 +83,7 @@ const ZoomPane = ({ d3ZoomHandler: selection.on('wheel.zoom'), // we need to pass transform because zoom handler is not registered when we set the initial transform transform: [clampedX, clampedY, clampedZoom], - domNode: selection.node()?.closest('.react-flow') as HTMLElement, + domNode: zoomPane.current.closest('.react-flow') as HTMLDivElement, }); } }, []); diff --git a/packages/core/src/hooks/useResizeHandler.ts b/packages/core/src/hooks/useResizeHandler.ts index 7d148db9..adbe3dac 100644 --- a/packages/core/src/hooks/useResizeHandler.ts +++ b/packages/core/src/hooks/useResizeHandler.ts @@ -29,7 +29,7 @@ function useResizeHandler(rendererNode: MutableRefObject) }; updateDimensions(); - window.onresize = updateDimensions; + window.addEventListener('resize', updateDimensions); if (rendererNode.current) { resizeObserver = new ResizeObserver(() => updateDimensions()); @@ -37,7 +37,7 @@ function useResizeHandler(rendererNode: MutableRefObject) } return () => { - window.onresize = null; + window.removeEventListener('resize', updateDimensions); if (resizeObserver && rendererNode.current) { resizeObserver.unobserve(rendererNode.current!); diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index d17fc323..3615b05e 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -135,7 +135,7 @@ export type ReactFlowStore = { onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; hasDefaultEdges: boolean; - domNode: HTMLElement | null; + domNode: HTMLDivElement | null; d3Zoom: ZoomBehavior | null; d3Selection: D3Selection | null;