From a345b2670bcbe66d12f45f7a0bc2704a01b3f696 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 24 Jan 2024 18:25:31 +0100 Subject: [PATCH 01/44] feat(react): infer node types from passed nodes --- .../react/src/examples/CustomNode/index.tsx | 2 +- .../react/src/examples/UseNodesData/index.tsx | 2 +- .../src/components/NodeWrapper/index.tsx | 8 +- .../src/components/NodesSelection/index.tsx | 12 +- .../src/components/StoreUpdater/index.tsx | 8 +- .../src/container/FlowRenderer/index.tsx | 14 +- .../react/src/container/GraphView/index.tsx | 18 +- .../src/container/NodeRenderer/index.tsx | 14 +- .../react/src/container/ReactFlow/index.tsx | 540 +++++++++--------- .../react/src/hooks/useNodesEdgesState.ts | 16 +- packages/react/src/hooks/useOnInitHandler.ts | 6 +- packages/react/src/hooks/useStore.ts | 4 +- packages/react/src/types/component-props.ts | 34 +- packages/react/src/types/nodes.ts | 14 +- packages/react/src/types/store.ts | 26 +- 15 files changed, 364 insertions(+), 354 deletions(-) diff --git a/examples/react/src/examples/CustomNode/index.tsx b/examples/react/src/examples/CustomNode/index.tsx index a8c0f55d..772d4ddd 100644 --- a/examples/react/src/examples/CustomNode/index.tsx +++ b/examples/react/src/examples/CustomNode/index.tsx @@ -44,7 +44,7 @@ const nodeTypes = { const CustomNodeFlow = () => { const [nodes, setNodes] = useState([]); - const onNodesChange: OnNodesChange = useCallback( + const onNodesChange: OnNodesChange = useCallback( (changes) => setNodes((nds) => { const nextNodes = applyNodeChanges(changes, nds); diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index d6b697a9..b9bc68f1 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -18,7 +18,7 @@ import UppercaseNode from './UppercaseNode'; export type TextNode = Node<{ text: string }, 'text'>; export type ResultNode = Node<{}, 'result'>; export type UppercaseNode = Node<{}, 'uppercase'>; -export type MyNode = Node<{ text: string }, 'text'> | Node<{}, 'result'> | Node<{}, 'uppercase'>; +export type MyNode = TextNode | ResultNode | UppercaseNode; const nodeTypes = { text: TextNode, diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 1e796de1..017dd0f9 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -17,9 +17,9 @@ import { useDrag } from '../../hooks/useDrag'; import { useUpdateNodePositions } from '../../hooks/useUpdateNodePositions'; import { handleNodeClick } from '../Nodes/utils'; import { arrowKeyDiffs, builtinNodeTypes } from './utils'; -import type { NodeWrapperProps } from '../../types'; +import type { Node, NodeWrapperProps } from '../../types'; -export function NodeWrapper({ +export function NodeWrapper({ id, onClick, onMouseEnter, @@ -40,9 +40,9 @@ export function NodeWrapper({ nodeExtent, nodeOrigin, onError, -}: NodeWrapperProps) { +}: NodeWrapperProps) { const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => { - const node = s.nodeLookup.get(id)!; + const node = s.nodeLookup.get(id)! as NodeType; const positionAbsolute = nodeExtent ? clampPosition(node.computed?.positionAbsolute, nodeExtent) diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index f8aeb77c..8ba01979 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -14,8 +14,8 @@ import { useUpdateNodePositions } from '../../hooks/useUpdateNodePositions'; import { arrowKeyDiffs } from '../NodeWrapper/utils'; import type { Node, ReactFlowState } from '../../types'; -export type NodesSelectionProps = { - onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void; +export type NodesSelectionProps = { + onSelectionContextMenu?: (event: MouseEvent, nodes: NodeType[]) => void; noPanClassName?: string; disableKeyboardA11y: boolean; }; @@ -32,7 +32,11 @@ const selector = (s: ReactFlowState) => { }; }; -export function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboardA11y }: NodesSelectionProps) { +export function NodesSelection({ + onSelectionContextMenu, + noPanClassName, + disableKeyboardA11y, +}: NodesSelectionProps) { const store = useStoreApi(); const { width, height, transformString, userSelectionActive } = useStore(selector, shallow); const updatePositions = useUpdateNodePositions(); @@ -58,7 +62,7 @@ export function NodesSelection({ onSelectionContextMenu, noPanClassName, disable const onContextMenu = onSelectionContextMenu ? (event: MouseEvent) => { const selectedNodes = store.getState().nodes.filter((n) => n.selected); - onSelectionContextMenu(event, selectedNodes); + onSelectionContextMenu(event, selectedNodes as NodeType[]); } : undefined; diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 3a12594c..6211ea59 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -68,7 +68,9 @@ const reactFlowFieldsToTrack = [ ] as const; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; -type StoreUpdaterProps = Pick & { rfId: string }; +type StoreUpdaterProps = Pick, ReactFlowFieldsToTrack> & { + rfId: string; +}; // rfId doesn't exist in ReactFlowProps, but it's one of the fields we want to update const fieldsToTrack = [...reactFlowFieldsToTrack, 'rfId'] as const; @@ -84,7 +86,7 @@ const selector = (s: ReactFlowState) => ({ reset: s.reset, }); -export function StoreUpdater(props: StoreUpdaterProps) { +export function StoreUpdater(props: StoreUpdaterProps) { const { setNodes, setEdges, @@ -106,7 +108,7 @@ export function StoreUpdater(props: StoreUpdaterProps) { }; }, []); - const previousFields = useRef>({ + const previousFields = useRef>>({ // these are values that are also passed directly to other components // than the StoreUpdater. We can reduce the number of setStore calls // by setting the same values here as prev fields. diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 2670be70..6aa49a2a 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -7,10 +7,10 @@ import { GraphViewProps } from '../GraphView'; import { ZoomPane } from '../ZoomPane'; import { Pane } from '../Pane'; import { NodesSelection } from '../../components/NodesSelection'; -import type { ReactFlowState } from '../../types'; +import type { ReactFlowState, Node } from '../../types'; -export type FlowRendererProps = Omit< - GraphViewProps, +export type FlowRendererProps = Omit< + GraphViewProps, | 'snapToGrid' | 'nodeTypes' | 'edgeTypes' @@ -32,7 +32,7 @@ const selector = (s: ReactFlowState) => { return { nodesSelectionActive: s.nodesSelectionActive, userSelectionActive: s.userSelectionActive }; }; -const FlowRendererComponent = ({ +function FlowRendererComponent({ children, onPaneClick, onPaneMouseEnter, @@ -68,7 +68,7 @@ const FlowRendererComponent = ({ disableKeyboardA11y, onViewportChange, isControlledViewport, -}: FlowRendererProps) => { +}: FlowRendererProps) { const { nodesSelectionActive, userSelectionActive } = useStore(selector); const selectionKeyPressed = useKeyPress(selectionKeyCode); const panActivationKeyPressed = useKeyPress(panActivationKeyCode); @@ -125,8 +125,8 @@ const FlowRendererComponent = ({ ); -}; +} FlowRendererComponent.displayName = 'FlowRenderer'; -export const FlowRenderer = memo(FlowRendererComponent); +export const FlowRenderer = memo(FlowRendererComponent) as typeof FlowRendererComponent; diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index fd7ac277..0615258c 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -8,15 +8,15 @@ import { useOnInitHandler } from '../../hooks/useOnInitHandler'; import { useViewportSync } from '../../hooks/useViewportSync'; import { ConnectionLineWrapper } from '../../components/ConnectionLine'; import { useNodeOrEdgeTypesWarning } from './useNodeOrEdgeTypesWarning'; -import type { ReactFlowProps } from '../../types'; +import type { Node, ReactFlowProps } from '../../types'; -export type GraphViewProps = Omit< - ReactFlowProps, +export type GraphViewProps = Omit< + ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'onMove' | 'onMoveStart' | 'onMoveEnd' | 'elevateEdgesOnSelect' > & Required< Pick< - ReactFlowProps, + ReactFlowProps, | 'selectionKeyCode' | 'deleteKeyCode' | 'multiSelectionKeyCode' @@ -38,7 +38,7 @@ export type GraphViewProps = Omit< rfId: string; }; -function GraphViewComponent({ +function GraphViewComponent({ nodeTypes, edgeTypes, onInit, @@ -102,7 +102,7 @@ function GraphViewComponent({ rfId, viewport, onViewportChange, -}: GraphViewProps) { +}: GraphViewProps) { useNodeOrEdgeTypesWarning(nodeTypes); useNodeOrEdgeTypesWarning(edgeTypes); @@ -110,7 +110,7 @@ function GraphViewComponent({ useViewportSync(viewport); return ( - onPaneClick={onPaneClick} onPaneMouseEnter={onPaneMouseEnter} onPaneMouseMove={onPaneMouseMove} @@ -174,7 +174,7 @@ function GraphViewComponent({ />
- nodeTypes={nodeTypes} onNodeClick={onNodeClick} onNodeDoubleClick={onNodeDoubleClick} @@ -197,4 +197,4 @@ function GraphViewComponent({ GraphViewComponent.displayName = 'GraphView'; -export const GraphView = memo(GraphViewComponent); +export const GraphView = memo(GraphViewComponent) as typeof GraphViewComponent; diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 11cab66f..39b20323 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -7,10 +7,10 @@ import { containerStyle } from '../../styles/utils'; import { GraphViewProps } from '../GraphView'; import { useResizeObserver } from './useResizeObserver'; import { NodeWrapper } from '../../components/NodeWrapper'; -import type { ReactFlowState } from '../../types'; +import type { Node, ReactFlowState } from '../../types'; -export type NodeRendererProps = Pick< - GraphViewProps, +export type NodeRendererProps = Pick< + GraphViewProps, | 'onNodeClick' | 'onNodeDoubleClick' | 'onNodeMouseEnter' @@ -35,7 +35,7 @@ const selector = (s: ReactFlowState) => ({ onError: s.onError, }); -const NodeRendererComponent = (props: NodeRendererProps) => { +function NodeRendererComponent(props: NodeRendererProps) { const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, onError } = useStore(selector, shallow); const nodeIds = useVisibleNodeIds(props.onlyRenderVisibleElements); const resizeObserver = useResizeObserver(); @@ -67,7 +67,7 @@ const NodeRendererComponent = (props: NodeRendererProps) => { // moved into `NodeComponentWrapper`. This ensures they are // memorized – so if `NodeRenderer` *has* to rerender, it only // needs to regenerate the list of nodes, nothing else. - key={nodeId} id={nodeId} nodeTypes={props.nodeTypes} @@ -94,8 +94,8 @@ const NodeRendererComponent = (props: NodeRendererProps) => { })}
); -}; +} NodeRendererComponent.displayName = 'NodeRenderer'; -export const NodeRenderer = memo(NodeRendererComponent); +export const NodeRenderer = memo(NodeRendererComponent) as typeof NodeRendererComponent; diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index d64af5ec..2516b4e7 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -1,4 +1,4 @@ -import { forwardRef, type CSSProperties } from 'react'; +import { ForwardedRef, forwardRef, type CSSProperties } from 'react'; import cc from 'classcat'; import { ConnectionLineType, @@ -17,7 +17,7 @@ import { StoreUpdater } from '../../components/StoreUpdater'; import { useColorModeClass } from '../../hooks/useColorModeClass'; import { GraphView } from '../GraphView'; import { Wrapper } from './Wrapper'; -import type { ReactFlowProps, ReactFlowRefType } from '../../types'; +import type { ReactFlowProps, ReactFlowRefType, Node } from '../../types'; export const initNodeOrigin: NodeOrigin = [0, 0]; const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 }; @@ -30,272 +30,276 @@ const wrapperStyle: CSSProperties = { zIndex: 0, }; -const ReactFlow = forwardRef( - ( - { - nodes, - edges, - defaultNodes, - defaultEdges, - className, - nodeTypes, - edgeTypes, - onNodeClick, - onEdgeClick, - onInit, - onMove, - onMoveStart, - onMoveEnd, - onConnect, - onConnectStart, - onConnectEnd, - onClickConnectStart, - onClickConnectEnd, - onNodeMouseEnter, - onNodeMouseMove, - onNodeMouseLeave, - onNodeContextMenu, - onNodeDoubleClick, - onNodeDragStart, - onNodeDrag, - onNodeDragStop, - onNodesDelete, - onEdgesDelete, - onDelete, - onSelectionChange, - onSelectionDragStart, - onSelectionDrag, - onSelectionDragStop, - onSelectionContextMenu, - onSelectionStart, - onSelectionEnd, - onBeforeDelete, - connectionMode, - connectionLineType = ConnectionLineType.Bezier, - connectionLineStyle, - connectionLineComponent, - connectionLineContainerStyle, - deleteKeyCode = 'Backspace', - selectionKeyCode = 'Shift', - selectionOnDrag = false, - selectionMode = SelectionMode.Full, - panActivationKeyCode = 'Space', - multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', - zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', - snapToGrid, - snapGrid, - onlyRenderVisibleElements = false, - selectNodesOnDrag, - nodesDraggable, - nodesConnectable, - nodesFocusable, - nodeOrigin = initNodeOrigin, - edgesFocusable, - edgesUpdatable, - elementsSelectable = true, - defaultViewport = initDefaultViewport, - minZoom = 0.5, - maxZoom = 2, - translateExtent = infiniteExtent, - preventScrolling = true, - nodeExtent, - defaultMarkerColor = '#b1b1b7', - zoomOnScroll = true, - zoomOnPinch = true, - panOnScroll = false, - panOnScrollSpeed = 0.5, - panOnScrollMode = PanOnScrollMode.Free, - zoomOnDoubleClick = true, - panOnDrag = true, - onPaneClick, - onPaneMouseEnter, - onPaneMouseMove, - onPaneMouseLeave, - onPaneScroll, - onPaneContextMenu, - children, - onEdgeUpdate, - onEdgeContextMenu, - onEdgeDoubleClick, - onEdgeMouseEnter, - onEdgeMouseMove, - onEdgeMouseLeave, - onEdgeUpdateStart, - onEdgeUpdateEnd, - edgeUpdaterRadius = 10, - onNodesChange, - onEdgesChange, - noDragClassName = 'nodrag', - noWheelClassName = 'nowheel', - noPanClassName = 'nopan', - fitView, - fitViewOptions, - connectOnClick, - attributionPosition, - proOptions, - defaultEdgeOptions, - elevateNodesOnSelect, - elevateEdgesOnSelect, - disableKeyboardA11y = false, - autoPanOnConnect, - autoPanOnNodeDrag, - connectionRadius, - isValidConnection, - onError, - style, - id, - nodeDragThreshold, - viewport, - onViewportChange, - width, - height, - colorMode = 'light', - ...rest - }, - ref - ) => { - const rfId = id || '1'; - const colorModeClassName = useColorModeClass(colorMode); +declare module 'react' { + function forwardRef>( + render: (props: P, ref: React.Ref) => React.ReactNode | null + ): (props: P & React.RefAttributes) => React.ReactNode | null; +} - return ( -
- - - - - {children} - - - -
- ); - } -); +function ReactFlow( + { + nodes, + edges, + defaultNodes, + defaultEdges, + className, + nodeTypes, + edgeTypes, + onNodeClick, + onEdgeClick, + onInit, + onMove, + onMoveStart, + onMoveEnd, + onConnect, + onConnectStart, + onConnectEnd, + onClickConnectStart, + onClickConnectEnd, + onNodeMouseEnter, + onNodeMouseMove, + onNodeMouseLeave, + onNodeContextMenu, + onNodeDoubleClick, + onNodeDragStart, + onNodeDrag, + onNodeDragStop, + onNodesDelete, + onEdgesDelete, + onDelete, + onSelectionChange, + onSelectionDragStart, + onSelectionDrag, + onSelectionDragStop, + onSelectionContextMenu, + onSelectionStart, + onSelectionEnd, + onBeforeDelete, + connectionMode, + connectionLineType = ConnectionLineType.Bezier, + connectionLineStyle, + connectionLineComponent, + connectionLineContainerStyle, + deleteKeyCode = 'Backspace', + selectionKeyCode = 'Shift', + selectionOnDrag = false, + selectionMode = SelectionMode.Full, + panActivationKeyCode = 'Space', + multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', + zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', + snapToGrid, + snapGrid, + onlyRenderVisibleElements = false, + selectNodesOnDrag, + nodesDraggable, + nodesConnectable, + nodesFocusable, + nodeOrigin = initNodeOrigin, + edgesFocusable, + edgesUpdatable, + elementsSelectable = true, + defaultViewport = initDefaultViewport, + minZoom = 0.5, + maxZoom = 2, + translateExtent = infiniteExtent, + preventScrolling = true, + nodeExtent, + defaultMarkerColor = '#b1b1b7', + zoomOnScroll = true, + zoomOnPinch = true, + panOnScroll = false, + panOnScrollSpeed = 0.5, + panOnScrollMode = PanOnScrollMode.Free, + zoomOnDoubleClick = true, + panOnDrag = true, + onPaneClick, + onPaneMouseEnter, + onPaneMouseMove, + onPaneMouseLeave, + onPaneScroll, + onPaneContextMenu, + children, + onEdgeUpdate, + onEdgeContextMenu, + onEdgeDoubleClick, + onEdgeMouseEnter, + onEdgeMouseMove, + onEdgeMouseLeave, + onEdgeUpdateStart, + onEdgeUpdateEnd, + edgeUpdaterRadius = 10, + onNodesChange, + onEdgesChange, + noDragClassName = 'nodrag', + noWheelClassName = 'nowheel', + noPanClassName = 'nopan', + fitView, + fitViewOptions, + connectOnClick, + attributionPosition, + proOptions, + defaultEdgeOptions, + elevateNodesOnSelect, + elevateEdgesOnSelect, + disableKeyboardA11y = false, + autoPanOnConnect, + autoPanOnNodeDrag, + connectionRadius, + isValidConnection, + onError, + style, + id, + nodeDragThreshold, + viewport, + onViewportChange, + width, + height, + colorMode = 'light', + ...rest + }: ReactFlowProps, + ref: ForwardedRef +) { + const rfId = id || '1'; + const colorModeClassName = useColorModeClass(colorMode); -ReactFlow.displayName = 'ReactFlow'; + return ( +
+ + + onInit={onInit} + onNodeClick={onNodeClick} + onEdgeClick={onEdgeClick} + onNodeMouseEnter={onNodeMouseEnter} + onNodeMouseMove={onNodeMouseMove} + onNodeMouseLeave={onNodeMouseLeave} + onNodeContextMenu={onNodeContextMenu} + onNodeDoubleClick={onNodeDoubleClick} + nodeTypes={nodeTypes} + edgeTypes={edgeTypes} + connectionLineType={connectionLineType} + connectionLineStyle={connectionLineStyle} + connectionLineComponent={connectionLineComponent} + connectionLineContainerStyle={connectionLineContainerStyle} + selectionKeyCode={selectionKeyCode} + selectionOnDrag={selectionOnDrag} + selectionMode={selectionMode} + deleteKeyCode={deleteKeyCode} + multiSelectionKeyCode={multiSelectionKeyCode} + panActivationKeyCode={panActivationKeyCode} + zoomActivationKeyCode={zoomActivationKeyCode} + onlyRenderVisibleElements={onlyRenderVisibleElements} + defaultViewport={defaultViewport} + translateExtent={translateExtent} + minZoom={minZoom} + maxZoom={maxZoom} + preventScrolling={preventScrolling} + zoomOnScroll={zoomOnScroll} + zoomOnPinch={zoomOnPinch} + zoomOnDoubleClick={zoomOnDoubleClick} + panOnScroll={panOnScroll} + panOnScrollSpeed={panOnScrollSpeed} + panOnScrollMode={panOnScrollMode} + panOnDrag={panOnDrag} + onPaneClick={onPaneClick} + onPaneMouseEnter={onPaneMouseEnter} + onPaneMouseMove={onPaneMouseMove} + onPaneMouseLeave={onPaneMouseLeave} + onPaneScroll={onPaneScroll} + onPaneContextMenu={onPaneContextMenu} + onSelectionContextMenu={onSelectionContextMenu} + onSelectionStart={onSelectionStart} + onSelectionEnd={onSelectionEnd} + onEdgeUpdate={onEdgeUpdate} + onEdgeContextMenu={onEdgeContextMenu} + onEdgeDoubleClick={onEdgeDoubleClick} + onEdgeMouseEnter={onEdgeMouseEnter} + onEdgeMouseMove={onEdgeMouseMove} + onEdgeMouseLeave={onEdgeMouseLeave} + onEdgeUpdateStart={onEdgeUpdateStart} + onEdgeUpdateEnd={onEdgeUpdateEnd} + edgeUpdaterRadius={edgeUpdaterRadius} + defaultMarkerColor={defaultMarkerColor} + noDragClassName={noDragClassName} + noWheelClassName={noWheelClassName} + noPanClassName={noPanClassName} + rfId={rfId} + disableKeyboardA11y={disableKeyboardA11y} + nodeOrigin={nodeOrigin} + nodeExtent={nodeExtent} + viewport={viewport} + onViewportChange={onViewportChange} + /> + + + {children} + + + +
+ ); +} -export default ReactFlow; +export default forwardRef(ReactFlow) as ( + props: ReactFlowProps & { ref?: React.ForwardedRef } +) => ReturnType; diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts index 766ee049..fae87c02 100644 --- a/packages/react/src/hooks/useNodesEdgesState.ts +++ b/packages/react/src/hooks/useNodesEdgesState.ts @@ -1,7 +1,7 @@ import { useState, useCallback, type Dispatch, type SetStateAction } from 'react'; import { applyNodeChanges, applyEdgeChanges } from '../utils/changes'; -import type { Node, NodeChange, Edge, EdgeChange } from '../types'; +import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types'; /** * Hook for managing the state of nodes - should only be used for prototyping / simple use cases. @@ -10,12 +10,12 @@ import type { Node, NodeChange, Edge, EdgeChange } from '../types'; * @param initialNodes * @returns an array [nodes, setNodes, onNodesChange] */ -export function useNodesState( +export function useNodesState( initialNodes: NodeType[] -): [NodeType[], Dispatch>, (changes: NodeChange[]) => void] { +): [NodeType[], Dispatch>, OnNodesChange] { const [nodes, setNodes] = useState(initialNodes); - const onNodesChange = useCallback( - (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), + const onNodesChange: OnNodesChange = useCallback( + (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), [] ); @@ -31,10 +31,10 @@ export function useNodesState( */ export function useEdgesState( initialEdges: EdgeType[] -): [EdgeType[], Dispatch>, (changes: EdgeChange[]) => void] { +): [EdgeType[], Dispatch>, OnEdgesChange] { const [edges, setEdges] = useState(initialEdges); - const onEdgesChange = useCallback( - (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), + const onEdgesChange: OnEdgesChange = useCallback( + (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), [] ); diff --git a/packages/react/src/hooks/useOnInitHandler.ts b/packages/react/src/hooks/useOnInitHandler.ts index 5b677c1a..782b0379 100644 --- a/packages/react/src/hooks/useOnInitHandler.ts +++ b/packages/react/src/hooks/useOnInitHandler.ts @@ -1,15 +1,15 @@ import { useEffect, useRef } from 'react'; import { useReactFlow } from './useReactFlow'; -import type { OnInit } from '../types'; +import type { OnInit, Node } from '../types'; /** * Hook for calling onInit handler. * * @internal */ -export function useOnInitHandler(onInit: OnInit | undefined) { - const rfInstance = useReactFlow(); +export function useOnInitHandler(onInit: OnInit | undefined) { + const rfInstance = useReactFlow(); const isInitialized = useRef(false); useEffect(() => { diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index 96587161..9f7f8255 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -31,7 +31,7 @@ function useStore( return useZustandStore(store, selector, equalityFn); } -const useStoreApi = () => { +function useStoreApi() { const store = useContext(StoreContext); if (store === null) { @@ -47,6 +47,6 @@ const useStoreApi = () => { }), [store] ); -}; +} export { useStore, useStoreApi }; diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 6018e1f5..f5f854ab 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -50,7 +50,7 @@ import type { * ReactFlow component props. * @public */ -export interface ReactFlowProps extends Omit, 'onError'> { +export interface ReactFlowProps extends Omit, 'onError'> { /** An array of nodes to render in a controlled flow. * @example * const nodes = [ @@ -62,7 +62,7 @@ export interface ReactFlowProps extends Omit, 'on * } * ]; */ - nodes?: Node[]; + nodes?: NodeType[]; /** An array of edges to render in a controlled flow. * @example * const edges = [ @@ -75,7 +75,7 @@ export interface ReactFlowProps extends Omit, 'on */ edges?: Edge[]; /** The initial nodes to render in an uncontrolled flow. */ - defaultNodes?: Node[]; + defaultNodes?: NodeType[]; /** The initial edges to render in an uncontrolled flow. */ defaultEdges?: Edge[]; /** Defaults to be applied to all new edges that are added to the flow. @@ -99,23 +99,23 @@ export interface ReactFlowProps extends Omit, 'on */ defaultEdgeOptions?: DefaultEdgeOptions; /** This event handler is called when a user clicks on a node */ - onNodeClick?: NodeMouseHandler; + onNodeClick?: NodeMouseHandler; /** This event handler is called when a user double clicks on a node */ - onNodeDoubleClick?: NodeMouseHandler; + onNodeDoubleClick?: NodeMouseHandler; /** This event handler is called when mouse of a user enters a node */ - onNodeMouseEnter?: NodeMouseHandler; + onNodeMouseEnter?: NodeMouseHandler; /** This event handler is called when mouse of a user moves over a node */ - onNodeMouseMove?: NodeMouseHandler; + onNodeMouseMove?: NodeMouseHandler; /** This event handler is called when mouse of a user leaves a node */ - onNodeMouseLeave?: NodeMouseHandler; + onNodeMouseLeave?: NodeMouseHandler; /** This event handler is called when a user right clicks on a node */ - onNodeContextMenu?: NodeMouseHandler; + onNodeContextMenu?: NodeMouseHandler; /** This event handler is called when a user starts to drag a node */ - onNodeDragStart?: OnNodeDrag; + onNodeDragStart?: OnNodeDrag; /** This event handler is called when a user drags a node */ - onNodeDrag?: OnNodeDrag; + onNodeDrag?: OnNodeDrag; /** This event handler is called when a user stops dragging a node */ - onNodeDragStop?: OnNodeDrag; + onNodeDragStop?: OnNodeDrag; /** This event handler is called when a user clicks on an edge */ onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void; /** This event handler is called when a user right clicks on an edge */ @@ -147,7 +147,7 @@ export interface ReactFlowProps extends Omit, 'on * * return () */ - onNodesChange?: OnNodesChange; + onNodesChange?: OnNodesChange; /** This event handler is called when a Edge is updated * @example // Use EdgesState hook to create edges and get onEdgesChange handler * import ReactFlow, { useEdgesState } from '@xyflow/react'; @@ -166,7 +166,7 @@ export interface ReactFlowProps extends Omit, 'on */ onEdgesChange?: OnEdgesChange; /** This event handler gets called when a Node is deleted */ - onNodesDelete?: OnNodesDelete; + onNodesDelete?: OnNodesDelete; /** This event handler gets called when a Edge is deleted */ onEdgesDelete?: OnEdgesDelete; /** This event handler gets called when a Node or Edge is deleted */ @@ -179,7 +179,7 @@ export interface ReactFlowProps extends Omit, 'on onSelectionDragStop?: SelectionDragHandler; onSelectionStart?: (event: ReactMouseEvent) => void; onSelectionEnd?: (event: ReactMouseEvent) => void; - onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void; + onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void; /** When a connection line is completed and two nodes are connected by the user, this event fires with the new connection. * * You can use the addEdge utility to convert the connection to a complete edge. @@ -201,7 +201,7 @@ export interface ReactFlowProps extends Omit, 'on onClickConnectStart?: OnConnectStart; onClickConnectEnd?: OnConnectEnd; /** This event handler gets called when a flow has finished initializing */ - onInit?: OnInit; + onInit?: OnInit; /** This event handler is called while the user is either panning or zooming the viewport. */ onMove?: OnMove; /** This event handler gets called when a user starts to pan or zoom the viewport */ @@ -223,7 +223,7 @@ export interface ReactFlowProps extends Omit, 'on /** This event handler gets called when mouse leaves the pane */ onPaneMouseLeave?: (event: ReactMouseEvent) => void; /** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */ - onBeforeDelete?: OnBeforeDelete; + onBeforeDelete?: OnBeforeDelete; /** Custom node types to be available in a flow. * * React Flow matches a node's type to a component in the nodeTypes object. diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 612c1d50..9b28c78f 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -26,18 +26,18 @@ export type OnNodeDrag = ( nodes: NodeType[] ) => void; -export type NodeWrapperProps = { +export type NodeWrapperProps = { id: string; nodesConnectable: boolean; elementsSelectable: boolean; nodesDraggable: boolean; nodesFocusable: boolean; - onClick?: NodeMouseHandler; - onDoubleClick?: NodeMouseHandler; - onMouseEnter?: NodeMouseHandler; - onMouseMove?: NodeMouseHandler; - onMouseLeave?: NodeMouseHandler; - onContextMenu?: NodeMouseHandler; + onClick?: NodeMouseHandler; + onDoubleClick?: NodeMouseHandler; + onMouseEnter?: NodeMouseHandler; + onMouseMove?: NodeMouseHandler; + onMouseLeave?: NodeMouseHandler; + onContextMenu?: NodeMouseHandler; resizeObserver: ResizeObserver | null; noDragClassName: string; noPanClassName: string; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index b3e0449e..27482dba 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -45,17 +45,17 @@ import type { OnBeforeDelete, } from '.'; -export type ReactFlowStore = { +export type ReactFlowStore = { rfId: string; width: number; height: number; transform: Transform; - nodes: Node[]; - nodeLookup: NodeLookup; + nodes: NodeType[]; + nodeLookup: NodeLookup; edges: Edge[]; edgeLookup: EdgeLookup; connectionLookup: ConnectionLookup; - onNodesChange: OnNodesChange | null; + onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; hasDefaultEdges: boolean; @@ -98,9 +98,9 @@ export type ReactFlowStore = { connectionEndHandle: ConnectingHandle | null; connectionClickStartHandle: ConnectingHandle | null; - onNodeDragStart?: OnNodeDrag; - onNodeDrag?: OnNodeDrag; - onNodeDragStop?: OnNodeDrag; + onNodeDragStart?: OnNodeDrag; + onNodeDrag?: OnNodeDrag; + onNodeDragStop?: OnNodeDrag; onSelectionDragStart?: OnSelectionDrag; onSelectionDrag?: OnSelectionDrag; @@ -147,10 +147,10 @@ export type ReactFlowStore = { lib: string; }; -export type ReactFlowActions = { - setNodes: (nodes: Node[]) => void; +export type ReactFlowActions = { + setNodes: (nodes: NodeType[]) => void; setEdges: (edges: Edge[]) => void; - setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void; + setDefaultNodesAndEdges: (nodes?: NodeType[], edges?: Edge[]) => void; updateNodeDimensions: (updates: Map) => void; updateNodePositions: UpdateNodePositions; resetSelectedElements: () => void; @@ -164,9 +164,9 @@ export type ReactFlowActions = { cancelConnection: () => void; updateConnection: UpdateConnection; reset: () => void; - triggerNodeChanges: (changes: NodeChange[]) => void; + triggerNodeChanges: (changes: NodeChange[]) => void; panBy: PanBy; - fitView: (nodes: Node[], options?: FitViewOptions) => boolean; + fitView: (nodes: NodeType[], options?: FitViewOptions) => boolean; }; -export type ReactFlowState = ReactFlowStore & ReactFlowActions; +export type ReactFlowState = ReactFlowStore & ReactFlowActions; From cccfc21114a43917c5b825dc2d649903ba4994c0 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 26 Jan 2024 09:57:19 +0100 Subject: [PATCH 02/44] refactor(react): minimap generic node type --- .../react/src/examples/CustomNode/index.tsx | 7 ++-- .../react/src/examples/UseNodesData/index.tsx | 2 +- .../additional-components/MiniMap/MiniMap.tsx | 12 +++---- .../MiniMap/MiniMapNodes.tsx | 33 ++++++++++--------- packages/react/src/types/nodes.ts | 2 ++ 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/examples/react/src/examples/CustomNode/index.tsx b/examples/react/src/examples/CustomNode/index.tsx index 772d4ddd..32bf34a0 100644 --- a/examples/react/src/examples/CustomNode/index.tsx +++ b/examples/react/src/examples/CustomNode/index.tsx @@ -16,6 +16,7 @@ import { OnNodesChange, OnConnect, OnBeforeDelete, + BuiltInNode, } from '@xyflow/react'; import ColorSelectorNode from './ColorSelectorNode'; @@ -24,7 +25,7 @@ export type ColorSelectorNode = Node< { color: string; onChange: (event: ChangeEvent) => void }, 'selectorNode' >; -export type MyNode = Node | ColorSelectorNode; +export type MyNode = BuiltInNode | ColorSelectorNode; const onInit: OnInit = (reactFlowInstance) => { console.log('flow loaded:', reactFlowInstance); @@ -61,7 +62,7 @@ const CustomNodeFlow = () => { const onChange = (event: ChangeEvent) => { setNodes((nds) => nds.map((node) => { - if (node.id !== '2') { + if (node.id !== '2' || node.type !== 'selectorNode') { return node; } @@ -164,7 +165,7 @@ const CustomNodeFlow = () => { maxZoom={2} onBeforeDelete={onBeforeDelete} > - nodeStrokeColor={(n: MyNode): string => { if (n.type === 'input') return '#0041d0'; if (n.type === 'selectorNode') return bgColor; diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index d99ddb43..b9bc68f1 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -18,7 +18,7 @@ import UppercaseNode from './UppercaseNode'; export type TextNode = Node<{ text: string }, 'text'>; export type ResultNode = Node<{}, 'result'>; export type UppercaseNode = Node<{}, 'uppercase'>; -export type MyNode = Node | TextNode | ResultNode | UppercaseNode; +export type MyNode = TextNode | ResultNode | UppercaseNode; const nodeTypes = { text: TextNode, diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index cd3f64ae..60caca1b 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -7,7 +7,7 @@ import { getNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapI import { useStore, useStoreApi } from '../../hooks/useStore'; import { Panel } from '../../components/Panel'; -import type { ReactFlowState } from '../../types'; +import type { ReactFlowState, Node } from '../../types'; import MiniMapNodes from './MiniMapNodes'; import type { MiniMapProps } from './types'; @@ -38,7 +38,7 @@ const selector = (s: ReactFlowState) => { const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; -function MiniMapComponent({ +function MiniMapComponent({ style, className, nodeStrokeColor, @@ -61,7 +61,7 @@ function MiniMapComponent({ inversePan, zoomStep = 10, offsetScale = 5, -}: MiniMapProps) { +}: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight } = useStore(selector, shallow); @@ -119,7 +119,7 @@ function MiniMapComponent({ const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)!; + const node = store.getState().nodeLookup.get(nodeId)! as NodeType; onNodeClick(event, node); }, []) : undefined; @@ -149,7 +149,7 @@ function MiniMapComponent({ onClick={onSvgClick} > {ariaLabel && {ariaLabel}} - onClick={onSvgNodeClick} nodeColor={nodeColor} nodeStrokeColor={nodeStrokeColor} @@ -174,4 +174,4 @@ function MiniMapComponent({ MiniMapComponent.displayName = 'MiniMap'; -export const MiniMap = memo(MiniMapComponent); +export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent; diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index 63851aad..0dfb8fff 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -6,16 +6,17 @@ import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; import { MiniMapNode } from './MiniMapNode'; -import type { ReactFlowState } from '../../types'; +import type { ReactFlowState, Node } from '../../types'; import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types'; declare const window: any; const selector = (s: ReactFlowState) => s.nodeOrigin; const selectorNodeIds = (s: ReactFlowState) => s.nodes.map((node) => node.id); -const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); +const getAttrFunction = (func: any): GetMiniMapNodeAttribute => + func instanceof Function ? func : () => func; -function MiniMapNodes({ +function MiniMapNodes({ nodeStrokeColor, nodeColor, nodeClassName = '', @@ -25,12 +26,12 @@ function MiniMapNodes({ // a component properly. nodeComponent: NodeComponent = MiniMapNode, onClick, -}: MiniMapNodesProps) { +}: MiniMapNodesProps) { const nodeIds = useStore(selectorNodeIds, shallow); const nodeOrigin = useStore(selector); - const nodeColorFunc = getAttrFunction(nodeColor); - const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); - const nodeClassNameFunc = getAttrFunction(nodeClassName); + const nodeColorFunc = getAttrFunction(nodeColor); + const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); + const nodeClassNameFunc = getAttrFunction(nodeClassName); const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; @@ -42,7 +43,7 @@ function MiniMapNodes({ // minimize the cost of updates when individual nodes change. // // For more details, see a similar commit in `NodeRenderer/index.tsx`. - key={nodeId} id={nodeId} nodeOrigin={nodeOrigin} @@ -60,7 +61,7 @@ function MiniMapNodes({ ); } -const NodeComponentWrapper = memo(function NodeComponentWrapper({ +function NodeComponentWrapperInner({ id, nodeOrigin, nodeColorFunc, @@ -74,9 +75,9 @@ const NodeComponentWrapper = memo(function NodeComponentWrapper({ }: { id: string; nodeOrigin: NodeOrigin; - nodeColorFunc: GetMiniMapNodeAttribute; - nodeStrokeColorFunc: GetMiniMapNodeAttribute; - nodeClassNameFunc: GetMiniMapNodeAttribute; + nodeColorFunc: GetMiniMapNodeAttribute; + nodeStrokeColorFunc: GetMiniMapNodeAttribute; + nodeClassNameFunc: GetMiniMapNodeAttribute; nodeBorderRadius: number; nodeStrokeWidth?: number; NodeComponent: ComponentType; @@ -84,7 +85,7 @@ const NodeComponentWrapper = memo(function NodeComponentWrapper({ shapeRendering: string; }) { const { node, x, y } = useStore((s) => { - const node = s.nodeLookup.get(id); + const node = s.nodeLookup.get(id) as NodeType; const { x, y } = getNodePositionWithOrigin(node, node?.origin || nodeOrigin).positionAbsolute; return { @@ -115,6 +116,8 @@ const NodeComponentWrapper = memo(function NodeComponentWrapper({ id={node.id} /> ); -}); +} -export default memo(MiniMapNodes); +const NodeComponentWrapper = memo(NodeComponentWrapperInner) as typeof NodeComponentWrapperInner; + +export default memo(MiniMapNodes) as typeof MiniMapNodes; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 9b28c78f..9ef7b9c3 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -48,3 +48,5 @@ export type NodeWrapperProps = { nodeOrigin: NodeOrigin; onError?: OnError; }; + +export type BuiltInNode = Node<{ label: string }, 'input' | 'output' | 'default'>; From f14480dc89922b4e31fe17847780b4921df009b4 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 17 Feb 2024 12:24:38 +0100 Subject: [PATCH 03/44] chore(useStore): cleanup --- packages/react/src/hooks/useStore.ts | 6 ++---- packages/react/src/types/store.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index 9f7f8255..a80217a2 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -1,6 +1,5 @@ import { useContext, useMemo } from 'react'; import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'; -import type { StoreApi } from 'zustand'; import { errorMessages } from '@xyflow/system'; import StoreContext from '../contexts/RFStoreContext'; @@ -8,8 +7,6 @@ import type { ReactFlowState } from '../types'; const zustandErrorMessage = errorMessages['error001'](); -type ExtractState = StoreApi extends { getState: () => infer T } ? T : never; - /** * Hook for accessing the internal store. Should only be used in rare cases. * @@ -18,7 +15,8 @@ type ExtractState = StoreApi extends { getState: () => infer T } * @param equalityFn * @returns The selected state slice */ -function useStore( + +function useStore( selector: (state: ReactFlowState) => StateSlice, equalityFn?: (a: StateSlice, b: StateSlice) => boolean ) { diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 27482dba..38e3fbe9 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -45,7 +45,7 @@ import type { OnBeforeDelete, } from '.'; -export type ReactFlowStore = { +export type ReactFlowStore = { rfId: string; width: number; height: number; From 776f6ce0b784973db0a35dd4f13ffc71c5a90a21 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 17 Feb 2024 14:35:20 +0100 Subject: [PATCH 04/44] chore(useStore): add tsdoc example --- packages/react/src/hooks/useStore.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index a80217a2..9a3a5e6b 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -14,8 +14,11 @@ const zustandErrorMessage = errorMessages['error001'](); * @param selector * @param equalityFn * @returns The selected state slice + * + * @example + * const nodes = useStore((state: ReactFlowState) => state.nodes); + * */ - function useStore( selector: (state: ReactFlowState) => StateSlice, equalityFn?: (a: StateSlice, b: StateSlice) => boolean From ad7403cc75ac5884830599f6be7ae66b73132d51 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 17 Feb 2024 15:45:10 +0100 Subject: [PATCH 05/44] refactor(types): use same structure for nodes and edges --- .../EdgeWrapper/EdgeUpdateAnchors.tsx | 14 ++--- .../src/components/EdgeWrapper/index.tsx | 10 ++-- .../src/components/StoreUpdater/index.tsx | 11 ++-- .../src/container/EdgeRenderer/index.tsx | 14 ++--- .../src/container/FlowRenderer/index.tsx | 4 +- .../react/src/container/GraphView/index.tsx | 14 ++--- .../react/src/container/ReactFlow/index.tsx | 18 ++----- packages/react/src/hooks/useEdges.ts | 4 +- packages/react/src/hooks/useOnInitHandler.ts | 8 +-- packages/react/src/index.ts | 1 - packages/react/src/types/component-props.ts | 43 +++++++-------- packages/react/src/types/edges.ts | 53 ++++++++++--------- packages/react/src/types/general.ts | 10 +++- packages/react/src/types/nodes.ts | 9 ++-- packages/react/src/types/store.ts | 28 +++++----- .../src/lib/container/SvelteFlow/types.ts | 8 +-- packages/svelte/src/lib/hooks/useNodesData.ts | 2 +- packages/svelte/src/lib/index.ts | 1 - .../svelte/src/lib/store/initial-store.ts | 4 +- packages/svelte/src/lib/types/edges.ts | 41 ++++++++------ packages/svelte/src/lib/types/general.ts | 4 ++ packages/svelte/src/lib/types/nodes.ts | 4 +- packages/system/src/types/edges.ts | 7 ++- packages/system/src/types/nodes.ts | 11 ++-- 24 files changed, 176 insertions(+), 147 deletions(-) diff --git a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx index db56017c..c84c2d3b 100644 --- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx +++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx @@ -5,20 +5,20 @@ import { EdgeAnchor } from '../Edges/EdgeAnchor'; import type { EdgeWrapperProps, Edge } from '../../types/edges'; import { useStoreApi } from '../../hooks/useStore'; -type EdgeUpdateAnchorsProps = { - edge: Edge; +type EdgeUpdateAnchorsProps = { + edge: EdgeType; isUpdatable: boolean | 'source' | 'target'; edgeUpdaterRadius: EdgeWrapperProps['edgeUpdaterRadius']; sourceHandleId: Edge['sourceHandle']; targetHandleId: Edge['targetHandle']; - onEdgeUpdate: EdgeWrapperProps['onEdgeUpdate']; - onEdgeUpdateStart: EdgeWrapperProps['onEdgeUpdateStart']; - onEdgeUpdateEnd: EdgeWrapperProps['onEdgeUpdateEnd']; + onEdgeUpdate: EdgeWrapperProps['onEdgeUpdate']; + onEdgeUpdateStart: EdgeWrapperProps['onEdgeUpdateStart']; + onEdgeUpdateEnd: EdgeWrapperProps['onEdgeUpdateEnd']; setUpdateHover: (hover: boolean) => void; setUpdating: (updating: boolean) => void; } & EdgePosition; -export function EdgeUpdateAnchors({ +export function EdgeUpdateAnchors({ isUpdatable, edgeUpdaterRadius, edge, @@ -35,7 +35,7 @@ export function EdgeUpdateAnchors({ onEdgeUpdateEnd, setUpdating, setUpdateHover, -}: EdgeUpdateAnchorsProps) { +}: EdgeUpdateAnchorsProps) { const store = useStoreApi(); const handleEdgeUpdater = (event: React.MouseEvent, isSourceHandle: boolean) => { diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx index 28323a19..b1fdb4a8 100644 --- a/packages/react/src/components/EdgeWrapper/index.tsx +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -13,9 +13,9 @@ import { useStoreApi, useStore } from '../../hooks/useStore'; import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; import { builtinEdgeTypes, nullPosition } from './utils'; import { EdgeUpdateAnchors } from './EdgeUpdateAnchors'; -import type { EdgeWrapperProps } from '../../types'; +import type { Edge, EdgeWrapperProps } from '../../types'; -export function EdgeWrapper({ +export function EdgeWrapper({ id, edgesFocusable, edgesUpdatable, @@ -34,8 +34,8 @@ export function EdgeWrapper({ edgeTypes, noPanClassName, onError, -}: EdgeWrapperProps): JSX.Element | null { - let edge = useStore((s) => s.edgeLookup.get(id)!); +}: EdgeWrapperProps): JSX.Element | null { + let edge = useStore((s) => s.edgeLookup.get(id)!) as EdgeType; const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions); edge = defaultEdgeOptions ? { ...defaultEdgeOptions, ...edge } : edge; @@ -236,7 +236,7 @@ export function EdgeWrapper({ /> )} {isUpdatable && ( - edge={edge} isUpdatable={isUpdatable} edgeUpdaterRadius={edgeUpdaterRadius} diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 6211ea59..9c77f5e0 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -68,7 +68,10 @@ const reactFlowFieldsToTrack = [ ] as const; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; -type StoreUpdaterProps = Pick, ReactFlowFieldsToTrack> & { +type StoreUpdaterProps = Pick< + ReactFlowProps, + ReactFlowFieldsToTrack +> & { rfId: string; }; @@ -86,7 +89,9 @@ const selector = (s: ReactFlowState) => ({ reset: s.reset, }); -export function StoreUpdater(props: StoreUpdaterProps) { +export function StoreUpdater( + props: StoreUpdaterProps +) { const { setNodes, setEdges, @@ -108,7 +113,7 @@ export function StoreUpdater(props: StoreUpdaterPr }; }, []); - const previousFields = useRef>>({ + const previousFields = useRef>>({ // these are values that are also passed directly to other components // than the StoreUpdater. We can reduce the number of setStore calls // by setting the same values here as prev fields. diff --git a/packages/react/src/container/EdgeRenderer/index.tsx b/packages/react/src/container/EdgeRenderer/index.tsx index 5fdaa1af..3112179e 100644 --- a/packages/react/src/container/EdgeRenderer/index.tsx +++ b/packages/react/src/container/EdgeRenderer/index.tsx @@ -6,10 +6,10 @@ import { useVisibleEdgeIds } from '../../hooks/useVisibleEdgeIds'; import MarkerDefinitions from './MarkerDefinitions'; import { GraphViewProps } from '../GraphView'; import { EdgeWrapper } from '../../components/EdgeWrapper'; -import type { ReactFlowState } from '../../types'; +import type { Edge, ReactFlowState, Node } from '../../types'; -type EdgeRendererProps = Pick< - GraphViewProps, +type EdgeRendererProps = Pick< + GraphViewProps, | 'onEdgeClick' | 'onEdgeDoubleClick' | 'defaultMarkerColor' @@ -40,7 +40,7 @@ const selector = (s: ReactFlowState) => ({ onError: s.onError, }); -function EdgeRendererComponent({ +function EdgeRendererComponent({ defaultMarkerColor, onlyRenderVisibleElements, rfId, @@ -56,7 +56,7 @@ function EdgeRendererComponent({ onEdgeDoubleClick, onEdgeUpdateStart, onEdgeUpdateEnd, -}: EdgeRendererProps) { +}: EdgeRendererProps) { const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); const edgeIds = useVisibleEdgeIds(onlyRenderVisibleElements); @@ -66,7 +66,7 @@ function EdgeRendererComponent({ {edgeIds.map((id) => { return ( - key={id} id={id} edgesFocusable={edgesFocusable} @@ -95,4 +95,4 @@ function EdgeRendererComponent({ EdgeRendererComponent.displayName = 'EdgeRenderer'; -export const EdgeRenderer = memo(EdgeRendererComponent); +export const EdgeRenderer = memo(EdgeRendererComponent) as typeof EdgeRendererComponent; diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 6aa49a2a..14216ac2 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -9,7 +9,7 @@ import { Pane } from '../Pane'; import { NodesSelection } from '../../components/NodesSelection'; import type { ReactFlowState, Node } from '../../types'; -export type FlowRendererProps = Omit< +export type FlowRendererProps = Omit< GraphViewProps, | 'snapToGrid' | 'nodeTypes' @@ -32,7 +32,7 @@ const selector = (s: ReactFlowState) => { return { nodesSelectionActive: s.nodesSelectionActive, userSelectionActive: s.userSelectionActive }; }; -function FlowRendererComponent({ +function FlowRendererComponent({ children, onPaneClick, onPaneMouseEnter, diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index 0615258c..f1965b04 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -8,15 +8,15 @@ import { useOnInitHandler } from '../../hooks/useOnInitHandler'; import { useViewportSync } from '../../hooks/useViewportSync'; import { ConnectionLineWrapper } from '../../components/ConnectionLine'; import { useNodeOrEdgeTypesWarning } from './useNodeOrEdgeTypesWarning'; -import type { Node, ReactFlowProps } from '../../types'; +import type { Edge, Node, ReactFlowProps } from '../../types'; -export type GraphViewProps = Omit< - ReactFlowProps, +export type GraphViewProps = Omit< + ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'onMove' | 'onMoveStart' | 'onMoveEnd' | 'elevateEdgesOnSelect' > & Required< Pick< - ReactFlowProps, + ReactFlowProps, | 'selectionKeyCode' | 'deleteKeyCode' | 'multiSelectionKeyCode' @@ -38,7 +38,7 @@ export type GraphViewProps = Omit< rfId: string; }; -function GraphViewComponent({ +function GraphViewComponent({ nodeTypes, edgeTypes, onInit, @@ -102,7 +102,7 @@ function GraphViewComponent({ rfId, viewport, onViewportChange, -}: GraphViewProps) { +}: GraphViewProps) { useNodeOrEdgeTypesWarning(nodeTypes); useNodeOrEdgeTypesWarning(edgeTypes); @@ -148,7 +148,7 @@ function GraphViewComponent({ isControlledViewport={!!viewport} > - edgeTypes={edgeTypes} onEdgeClick={onEdgeClick} onEdgeDoubleClick={onEdgeDoubleClick} diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 2516b4e7..d4875419 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -17,7 +17,7 @@ import { StoreUpdater } from '../../components/StoreUpdater'; import { useColorModeClass } from '../../hooks/useColorModeClass'; import { GraphView } from '../GraphView'; import { Wrapper } from './Wrapper'; -import type { ReactFlowProps, ReactFlowRefType, Node } from '../../types'; +import type { ReactFlowProps, ReactFlowRefType, Node, Edge } from '../../types'; export const initNodeOrigin: NodeOrigin = [0, 0]; const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 }; @@ -30,13 +30,7 @@ const wrapperStyle: CSSProperties = { zIndex: 0, }; -declare module 'react' { - function forwardRef>( - render: (props: P, ref: React.Ref) => React.ReactNode | null - ): (props: P & React.RefAttributes) => React.ReactNode | null; -} - -function ReactFlow( +function ReactFlow( { nodes, edges, @@ -156,7 +150,7 @@ function ReactFlow( height, colorMode = 'light', ...rest - }: ReactFlowProps, + }: ReactFlowProps, ref: ForwardedRef ) { const rfId = id || '1'; @@ -172,7 +166,7 @@ function ReactFlow( id={id} > - + onInit={onInit} onNodeClick={onNodeClick} onEdgeClick={onEdgeClick} @@ -300,6 +294,4 @@ function ReactFlow( ); } -export default forwardRef(ReactFlow) as ( - props: ReactFlowProps & { ref?: React.ForwardedRef } -) => ReturnType; +export default forwardRef(ReactFlow) as typeof ReactFlow; diff --git a/packages/react/src/hooks/useEdges.ts b/packages/react/src/hooks/useEdges.ts index bd748abb..3ed9de39 100644 --- a/packages/react/src/hooks/useEdges.ts +++ b/packages/react/src/hooks/useEdges.ts @@ -11,8 +11,8 @@ const edgesSelector = (state: ReactFlowState) => state.edges; * @public * @returns An array of edges */ -export function useEdges(): Edge[] { - const edges = useStore(edgesSelector, shallow); +export function useEdges(): EdgeType[] { + const edges = useStore(edgesSelector, shallow) as EdgeType[]; return edges; } diff --git a/packages/react/src/hooks/useOnInitHandler.ts b/packages/react/src/hooks/useOnInitHandler.ts index 782b0379..5d43731b 100644 --- a/packages/react/src/hooks/useOnInitHandler.ts +++ b/packages/react/src/hooks/useOnInitHandler.ts @@ -1,15 +1,17 @@ import { useEffect, useRef } from 'react'; import { useReactFlow } from './useReactFlow'; -import type { OnInit, Node } from '../types'; +import type { OnInit, Node, Edge } from '../types'; /** * Hook for calling onInit handler. * * @internal */ -export function useOnInitHandler(onInit: OnInit | undefined) { - const rfInstance = useReactFlow(); +export function useOnInitHandler( + onInit: OnInit | undefined +) { + const rfInstance = useReactFlow(); const isInitialized = useRef(false); useEffect(() => { diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index bcb12cdf..a757ec23 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -53,7 +53,6 @@ export { type OnConnectStart, type OnConnect, type OnConnectEnd, - type IsValidConnection, type Viewport, type SnapGrid, PanOnScrollMode, diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index f5f854ab..ce6e94ad 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -18,7 +18,6 @@ import type { HandleType, SelectionMode, OnError, - IsValidConnection, ColorMode, SnapGrid, } from '@xyflow/system'; @@ -44,13 +43,15 @@ import type { EdgeMouseHandler, OnNodeDrag, OnBeforeDelete, + IsValidConnection, } from '.'; /** * ReactFlow component props. * @public */ -export interface ReactFlowProps extends Omit, 'onError'> { +export interface ReactFlowProps + extends Omit, 'onError'> { /** An array of nodes to render in a controlled flow. * @example * const nodes = [ @@ -73,11 +74,11 @@ export interface ReactFlowProps extends Omit extends Omit; /** This event handler is called when a user clicks on an edge */ - onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void; + onEdgeClick?: (event: ReactMouseEvent, edge: EdgeType) => void; /** This event handler is called when a user right clicks on an edge */ - onEdgeContextMenu?: EdgeMouseHandler; + onEdgeContextMenu?: EdgeMouseHandler; /** This event handler is called when mouse of a user enters an edge */ - onEdgeMouseEnter?: EdgeMouseHandler; + onEdgeMouseEnter?: EdgeMouseHandler; /** This event handler is called when mouse of a user moves over an edge */ - onEdgeMouseMove?: EdgeMouseHandler; + onEdgeMouseMove?: EdgeMouseHandler; /** This event handler is called when mouse of a user leaves an edge */ - onEdgeMouseLeave?: EdgeMouseHandler; + onEdgeMouseLeave?: EdgeMouseHandler; /** This event handler is called when a user double clicks on an edge */ - onEdgeDoubleClick?: EdgeMouseHandler; - onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void; - onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void; - onEdgeUpdate?: OnEdgeUpdateFunc; + onEdgeDoubleClick?: EdgeMouseHandler; + onEdgeUpdateStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void; + onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void; + onEdgeUpdate?: OnEdgeUpdateFunc; /** This event handler is called when a Node is updated * @example // Use NodesState hook to create edges and get onNodesChange handler * import ReactFlow, { useNodesState } from '@xyflow/react'; @@ -164,19 +165,19 @@ export interface ReactFlowProps extends Omit) */ - onEdgesChange?: OnEdgesChange; + onEdgesChange?: OnEdgesChange; /** This event handler gets called when a Node is deleted */ onNodesDelete?: OnNodesDelete; /** This event handler gets called when a Edge is deleted */ - onEdgesDelete?: OnEdgesDelete; + onEdgesDelete?: OnEdgesDelete; /** This event handler gets called when a Node or Edge is deleted */ - onDelete?: OnDelete; + onDelete?: OnDelete; /** This event handler gets called when a user starts to drag a selection box */ - onSelectionDragStart?: SelectionDragHandler; + onSelectionDragStart?: SelectionDragHandler; /** This event handler gets called when a user drags a selection box */ - onSelectionDrag?: SelectionDragHandler; + onSelectionDrag?: SelectionDragHandler; /** This event handler gets called when a user stops dragging a selection box */ - onSelectionDragStop?: SelectionDragHandler; + onSelectionDragStop?: SelectionDragHandler; onSelectionStart?: (event: ReactMouseEvent) => void; onSelectionEnd?: (event: ReactMouseEvent) => void; onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void; @@ -201,7 +202,7 @@ export interface ReactFlowProps extends Omit; + onInit?: OnInit; /** This event handler is called while the user is either panning or zooming the viewport. */ onMove?: OnMove; /** This event handler gets called when a user starts to pan or zoom the viewport */ @@ -223,7 +224,7 @@ export interface ReactFlowProps extends Omit void; /** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */ - onBeforeDelete?: OnBeforeDelete; + onBeforeDelete?: OnBeforeDelete; /** Custom node types to be available in a flow. * * React Flow matches a node's type to a component in the nodeTypes object. diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 2b514c96..4de6472e 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -30,7 +30,14 @@ export type EdgeLabelOptions = { export type EdgeUpdatable = boolean | HandleType; -export type DefaultEdge = EdgeBase & +/** + * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component + * @public + */ +export type Edge< + EdgeData extends Record = Record, + EdgeType extends string | undefined = string | undefined +> = EdgeBase & EdgeLabelOptions & { style?: CSSProperties; className?: string; @@ -38,45 +45,41 @@ export type DefaultEdge = EdgeBase & focusable?: boolean; }; -type SmoothStepEdgeType = DefaultEdge & { +type SmoothStepEdgeType = Record> = Edge & { type: 'smoothstep'; pathOptions?: SmoothStepPathOptions; }; -type BezierEdgeType = DefaultEdge & { +type BezierEdgeType = Record> = Edge & { type: 'default'; pathOptions?: BezierPathOptions; }; -type StepEdgeType = DefaultEdge & { +type StepEdgeType = Record> = Edge & { type: 'step'; pathOptions?: StepPathOptions; }; -/** - * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component - * @public - */ -export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeType | StepEdgeType; +export type BuiltInEdge = SmoothStepEdgeType | BezierEdgeType | StepEdgeType; -export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void; +export type EdgeMouseHandler = (event: ReactMouseEvent, edge: EdgeType) => void; -export type EdgeWrapperProps = { +export type EdgeWrapperProps = { id: string; edgesFocusable: boolean; edgesUpdatable: boolean; elementsSelectable: boolean; noPanClassName: string; - onClick?: EdgeMouseHandler; - onDoubleClick?: EdgeMouseHandler; - onEdgeUpdate?: OnEdgeUpdateFunc; - onContextMenu?: EdgeMouseHandler; - onMouseEnter?: EdgeMouseHandler; - onMouseMove?: EdgeMouseHandler; - onMouseLeave?: EdgeMouseHandler; + onClick?: EdgeMouseHandler; + onDoubleClick?: EdgeMouseHandler; + onEdgeUpdate?: OnEdgeUpdateFunc; + onContextMenu?: EdgeMouseHandler; + onMouseEnter?: EdgeMouseHandler; + onMouseMove?: EdgeMouseHandler; + onMouseLeave?: EdgeMouseHandler; edgeUpdaterRadius?: number; - onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void; - onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void; + onEdgeUpdateStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void; + onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void; rfId?: string; edgeTypes?: EdgeTypes; onError?: OnError; @@ -94,10 +97,10 @@ export type EdgeTextProps = HTMLAttributes & * Custom edge component props * @public */ -export type EdgeProps = Pick< - Edge, - 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' -> & +export type EdgeProps< + EdgeData extends Record = Record, + EdgeType extends string | undefined = string | undefined +> = Pick, 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'> & EdgePosition & EdgeLabelOptions & { sourceHandleId?: string | null; @@ -185,7 +188,7 @@ export type StraightEdgeProps = Omit = (oldEdge: Edge, newConnection: Connection) => void; +export type OnEdgeUpdateFunc = (oldEdge: EdgeType, newConnection: Connection) => void; export type ConnectionLineComponentProps = { connectionLineStyle?: CSSProperties; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 4f3f127a..549921c2 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -12,6 +12,7 @@ import { XYPosition, NodeProps, OnBeforeDeleteBase, + Connection, } from '@xyflow/system'; import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.'; @@ -21,8 +22,11 @@ export type OnNodesChange = (changes: NodeChange = (changes: EdgeChange[]) => void; export type OnNodesDelete = (nodes: NodeType[]) => void; -export type OnEdgesDelete = (edges: Edge[]) => void; -export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void; +export type OnEdgesDelete = (edges: EdgeType[]) => void; +export type OnDelete = (params: { + nodes: NodeType[]; + edges: EdgeType[]; +}) => void; export type NodeTypes = { [key: string]: ComponentType }; export type EdgeTypes = { [key: string]: ComponentType }; @@ -139,3 +143,5 @@ export type OnBeforeDelete; + +export type IsValidConnection = (edge: EdgeType | Connection) => boolean; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 9ef7b9c3..c3c0ea94 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -7,11 +7,10 @@ import { NodeTypes } from './general'; * The node data structure that gets used for the nodes prop. * @public */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Node = NodeBase< - NodeData, - NodeType -> & { +export type Node< + NodeData extends Record = Record, + NodeType extends string | undefined = string | undefined +> = NodeBase & { style?: CSSProperties; className?: string; resizing?: boolean; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 38e3fbe9..30ac002a 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -21,7 +21,6 @@ import { type OnMoveStart, type OnMove, type OnMoveEnd, - type IsValidConnection, type UpdateConnection, type EdgeLookup, type ConnectionLookup, @@ -43,9 +42,10 @@ import type { OnDelete, OnNodeDrag, OnBeforeDelete, + IsValidConnection, } from '.'; -export type ReactFlowStore = { +export type ReactFlowStore = { rfId: string; width: number; height: number; @@ -53,10 +53,10 @@ export type ReactFlowStore = { nodes: NodeType[]; nodeLookup: NodeLookup; edges: Edge[]; - edgeLookup: EdgeLookup; + edgeLookup: EdgeLookup; connectionLookup: ConnectionLookup; onNodesChange: OnNodesChange | null; - onEdgesChange: OnEdgesChange | null; + onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; hasDefaultEdges: boolean; domNode: HTMLDivElement | null; @@ -124,8 +124,8 @@ export type ReactFlowStore = { fitViewDone: boolean; fitViewOnInitOptions: FitViewOptions | undefined; - onNodesDelete?: OnNodesDelete; - onEdgesDelete?: OnEdgesDelete; + onNodesDelete?: OnNodesDelete; + onEdgesDelete?: OnEdgesDelete; onDelete?: OnDelete; onError?: OnError; @@ -133,7 +133,7 @@ export type ReactFlowStore = { onViewportChangeStart?: OnViewportChange; onViewportChange?: OnViewportChange; onViewportChangeEnd?: OnViewportChange; - onBeforeDelete?: OnBeforeDelete; + onBeforeDelete?: OnBeforeDelete; onSelectionChangeHandlers: OnSelectionChangeFunc[]; @@ -142,15 +142,15 @@ export type ReactFlowStore = { autoPanOnNodeDrag: boolean; connectionRadius: number; - isValidConnection?: IsValidConnection; + isValidConnection?: IsValidConnection; lib: string; }; -export type ReactFlowActions = { +export type ReactFlowActions = { setNodes: (nodes: NodeType[]) => void; - setEdges: (edges: Edge[]) => void; - setDefaultNodesAndEdges: (nodes?: NodeType[], edges?: Edge[]) => void; + setEdges: (edges: EdgeType[]) => void; + setDefaultNodesAndEdges: (nodes?: NodeType[], edges?: EdgeType[]) => void; updateNodeDimensions: (updates: Map) => void; updateNodePositions: UpdateNodePositions; resetSelectedElements: () => void; @@ -169,4 +169,8 @@ export type ReactFlowActions = { fitView: (nodes: NodeType[], options?: FitViewOptions) => boolean; }; -export type ReactFlowState = ReactFlowStore & ReactFlowActions; +export type ReactFlowState = ReactFlowStore< + NodeType, + EdgeType +> & + ReactFlowActions; diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts index aefa6f6e..b7dd2852 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/types.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts @@ -10,7 +10,6 @@ import type { OnMoveEnd, CoordinateExtent, PanOnScrollMode, - IsValidConnection, OnError, ConnectionMode, PanelPosition, @@ -18,8 +17,7 @@ import type { ColorMode, OnConnect, OnConnectStart, - OnConnectEnd, - OnBeforeDelete + OnConnectEnd } from '@xyflow/system'; import type { @@ -31,7 +29,9 @@ import type { DefaultEdgeOptions, FitViewOptions, OnDelete, - OnEdgeCreate + OnEdgeCreate, + OnBeforeDelete, + IsValidConnection } from '$lib/types'; import type { Writable } from 'svelte/store'; diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts index 8e24645c..d45c8c9e 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -3,7 +3,7 @@ import { derived, type Readable } from 'svelte/store'; import type { Node } from '$lib/types'; import { useStore } from '$lib/store'; -function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) { +function areNodesDataEqual(a: (Node['data'] | null)[] | null, b: (Node['data'] | null)[] | null) { if ((!a && !b) || (!a?.length && !b?.length)) { true; } diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 76394872..c484ec7e 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -66,7 +66,6 @@ export { type OnConnectStart, type OnConnect, type OnConnectEnd, - type IsValidConnection, type Viewport, type SnapGrid, PanOnScrollMode, diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 6c18c877..40020aba 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -14,7 +14,6 @@ import { type MarkerProps, type PanZoomInstance, type CoordinateExtent, - type IsValidConnection, type NodeOrigin, type OnError, type Viewport, @@ -47,7 +46,8 @@ import type { FitViewOptions, OnDelete, OnEdgeCreate, - OnBeforeDelete + OnBeforeDelete, + IsValidConnection } from '$lib/types'; import { createNodesStore, createEdgesStore } from './utils'; import { initConnectionProps, type ConnectionProps } from './derived-connection-props'; diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index c99b201a..21bcdaa5 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import type { SvelteComponent, ComponentType } from 'svelte'; import type { EdgeBase, @@ -11,41 +10,49 @@ import type { import type { Node } from '$lib/types'; -export type DefaultEdge = EdgeBase & { +/** + * The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component. + */ +export type Edge< + EdgeData extends Record = Record, + EdgeType extends string | undefined = string | undefined +> = EdgeBase & { label?: string; labelStyle?: string; style?: string; class?: string; }; -type SmoothStepEdgeType = DefaultEdge & { - type: 'smoothstep'; +type SmoothStepEdge = Record> = Edge< + EdgeData, + 'smoothstep' +> & { pathOptions?: SmoothStepPathOptions; }; -type BezierEdgeType = DefaultEdge & { - type: 'default'; +type BezierEdge = Record> = Edge< + EdgeData, + 'default' +> & { pathOptions?: BezierPathOptions; }; -type StepEdgeType = DefaultEdge & { - type: 'step'; +type StepEdge = Record> = Edge< + EdgeData, + 'step' +> & { pathOptions?: StepPathOptions; }; -/** - * The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component. - */ -export type Edge = - | DefaultEdge - | SmoothStepEdgeType - | BezierEdgeType - | StepEdgeType; +export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge; /** * Custom edge component props. */ -export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle' | 'type'> & +export type EdgeProps< + EdgeData extends Record = Record, + EdgeType extends string | undefined = string | undefined +> = Omit, 'sourceHandle' | 'targetHandle' | 'type'> & EdgePosition & { markerStart?: string; markerEnd?: string; diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index e67ad88c..fe728fae 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -57,3 +57,7 @@ export type OnBeforeDelete< NodeType extends Node = Node, EdgeType extends Edge = Edge > = OnBeforeDeleteBase; + +export type IsValidConnection = ( + edge: EdgeType | Connection +) => boolean; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index 4121754a..7a0fe082 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -7,7 +7,7 @@ import type { NodeBase, NodeProps } from '@xyflow/system'; * @public */ export type Node< - NodeData = any, + NodeData extends Record = Record, NodeType extends string | undefined = string | undefined > = NodeBase & { class?: string; @@ -17,3 +17,5 @@ export type Node< export type NodeTypes = Record>>; export type DefaultNodeOptions = Partial>; + +export type BuiltInNode = Node<{ label: string }, 'input' | 'output' | 'default'>; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index e28ad058..37c299e8 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -1,11 +1,14 @@ import { Position } from './utils'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type EdgeBase = { +export type EdgeBase< + EdgeData extends Record = Record, + EdgeType extends string | undefined = string | undefined +> = { /** Unique id of an edge */ id: string; /** Type of an edge defined in edgeTypes */ - type?: string; + type?: EdgeType; /** Id of source node */ source: string; /** Id of target node */ diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index b75e6d1b..4ac854ed 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -9,7 +9,10 @@ import { Optional } from '../utils/types'; * @typeParam T - type of the node data * @typeParam U - type of the node */ -export type NodeBase = { +export type NodeBase< + NodeData extends Record = Record, + NodeType extends string | undefined = string | undefined +> = { /** Unique id of a node */ id: string; /** Position of a node on the pane @@ -17,9 +20,9 @@ export type NodeBase */ position: XYPosition; /** Arbitrary data passed to a node */ - data: T; + data: NodeData; /** Type of node defined in nodeTypes */ - type?: U; + type?: NodeType; /** Only relevant for default, source, target nodeType. controls source position * @example 'right', 'left', 'top', 'bottom' */ @@ -70,7 +73,7 @@ export type NodeBase /** 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: NodeBase; + userProvidedNode: NodeBase; }; }; From a16c5c9bd5e6729b4aaa1c0a9e6ed4dd04bf9309 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 17 Feb 2024 16:32:59 +0100 Subject: [PATCH 06/44] chore(types): cleanup --- examples/react/src/examples/DragHandle/index.tsx | 2 +- packages/react/src/types/changes.ts | 1 - packages/react/src/types/edges.ts | 14 +++++++------- packages/react/src/types/general.ts | 1 - packages/react/src/types/instance.ts | 1 - packages/svelte/src/lib/types/nodes.ts | 1 - packages/system/src/types/edges.ts | 1 - 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/react/src/examples/DragHandle/index.tsx b/examples/react/src/examples/DragHandle/index.tsx index b76d8ae7..a7a75fcf 100644 --- a/examples/react/src/examples/DragHandle/index.tsx +++ b/examples/react/src/examples/DragHandle/index.tsx @@ -14,7 +14,7 @@ const initialNodes: Node[] = [ dragHandle: '.custom-drag-handle', style: { border: '1px solid #ddd', padding: '20px 40px' }, position: { x: 200, y: 200 }, - data: null, + data: {}, }, ]; diff --git a/packages/react/src/types/changes.ts b/packages/react/src/types/changes.ts index 9a5e8fe5..adcb484d 100644 --- a/packages/react/src/types/changes.ts +++ b/packages/react/src/types/changes.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import type { XYPosition, Dimensions } from '@xyflow/system'; import type { Node, Edge } from '.'; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 4de6472e..dac682db 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -45,22 +45,22 @@ export type Edge< focusable?: boolean; }; -type SmoothStepEdgeType = Record> = Edge & { - type: 'smoothstep'; +type SmoothStepEdge = Record> = Edge< + EdgeData, + 'smoothstep' +> & { pathOptions?: SmoothStepPathOptions; }; -type BezierEdgeType = Record> = Edge & { - type: 'default'; +type BezierEdge = Record> = Edge & { pathOptions?: BezierPathOptions; }; -type StepEdgeType = Record> = Edge & { - type: 'step'; +type StepEdge = Record> = Edge & { pathOptions?: StepPathOptions; }; -export type BuiltInEdge = SmoothStepEdgeType | BezierEdgeType | StepEdgeType; +export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge; export type EdgeMouseHandler = (event: ReactMouseEvent, edge: EdgeType) => void; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 549921c2..0e1e1dc4 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { FitViewParamsBase, FitViewOptionsBase, diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index d34c23dc..1991580c 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-namespace */ import type { Rect, Viewport } from '@xyflow/system'; import type { Node, Edge, ViewportHelperFunctions } from '.'; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index 7a0fe082..b3c572c7 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import type { ComponentType, SvelteComponent } from 'svelte'; import type { NodeBase, NodeProps } from '@xyflow/system'; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 37c299e8..39dc97bd 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -1,6 +1,5 @@ import { Position } from './utils'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any export type EdgeBase< EdgeData extends Record = Record, EdgeType extends string | undefined = string | undefined From 5e56dd58c01d61e955f3564e17e7d423dcbc51f9 Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 18 Feb 2024 19:54:55 +0100 Subject: [PATCH 07/44] chore(types): cleanup --- packages/svelte/src/lib/store/initial-store.ts | 2 +- packages/system/src/types/nodes.ts | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 40020aba..9c077ba0 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -79,7 +79,7 @@ export const getInitialStore = ({ height?: number; fitView?: boolean; }) => { - const nodeLookup = new Map(); + const nodeLookup: NodeLookup = new Map(); const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 4ac854ed..aadd4a47 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { internalsSymbol } from '../constants'; import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.'; import { Optional } from '../utils/types'; @@ -6,8 +5,8 @@ import { Optional } from '../utils/types'; /** * Framework independent node data structure. * - * @typeParam T - type of the node data - * @typeParam U - type of the node + * @typeParam NodeData - type of the node data + * @typeParam NodeType - type of the node */ export type NodeBase< NodeData extends Record = Record, @@ -77,17 +76,16 @@ export type NodeBase< }; }; -// props that get passed to a custom node /** * The node data structure that gets used for the nodes prop. * * @public * @param id - The id of the node. */ -export type NodeProps = { - /** Id of the node */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type NodeProps = { id: NodeBase['id']; - data: T; + data: NodeData; dragHandle: NodeBase['dragHandle']; type: NodeBase['type']; selected: NodeBase['selected']; From 07ddd97650283064cf7b706cfb94fdf68693c4fe Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 19 Feb 2024 10:45:48 +0100 Subject: [PATCH 08/44] refactor(react): add generics to useStoreApi --- .../src/additional-components/MiniMap/MiniMap.tsx | 4 ++-- .../react/src/components/NodesSelection/index.tsx | 5 ++--- packages/react/src/components/StoreUpdater/index.tsx | 2 +- packages/react/src/hooks/useStore.ts | 11 +++++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index c4bbf13b..17a3f7f2 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -63,7 +63,7 @@ function MiniMapComponent({ zoomStep = 10, offsetScale = 5, }: MiniMapProps) { - const store = useStoreApi(); + const store = useStoreApi(); const svg = useRef(null); const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; @@ -120,7 +120,7 @@ function MiniMapComponent({ const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)! as NodeType; + const node = store.getState().nodeLookup.get(nodeId)!; onNodeClick(event, node); }, []) : undefined; diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index c0423827..6c037ba5 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -2,7 +2,6 @@ * The nodes selection rectangle gets displayed when a user * made a selection with on or several nodes */ - import { useRef, useEffect, type MouseEvent, type KeyboardEvent } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; @@ -37,7 +36,7 @@ export function NodesSelection({ noPanClassName, disableKeyboardA11y, }: NodesSelectionProps) { - const store = useStoreApi(); + const store = useStoreApi(); const { width, height, transformString, userSelectionActive } = useStore(selector, shallow); const moveSelectedNodes = useMoveSelectedNodes(); @@ -62,7 +61,7 @@ export function NodesSelection({ const onContextMenu = onSelectionContextMenu ? (event: MouseEvent) => { const selectedNodes = store.getState().nodes.filter((n) => n.selected); - onSelectionContextMenu(event, selectedNodes as NodeType[]); + onSelectionContextMenu(event, selectedNodes); } : undefined; diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 85159721..7917e3d6 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -115,7 +115,7 @@ export function StoreUpdater(); useEffect(() => { setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges); diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index 9a3a5e6b..8a600706 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -1,9 +1,10 @@ import { useContext, useMemo } from 'react'; -import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'; +import { UseBoundStoreWithEqualityFn, useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'; import { errorMessages } from '@xyflow/system'; import StoreContext from '../contexts/RFStoreContext'; -import type { ReactFlowState } from '../types'; +import type { Edge, Node, ReactFlowState } from '../types'; +import { StoreApi } from 'zustand'; const zustandErrorMessage = errorMessages['error001'](); @@ -32,8 +33,10 @@ function useStore( return useZustandStore(store, selector, equalityFn); } -function useStoreApi() { - const store = useContext(StoreContext); +function useStoreApi() { + const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn< + StoreApi> + > | null; if (store === null) { throw new Error(zustandErrorMessage); From ea0e72aa0966878b800a7bcd2b1bd37f9bfb9c6d Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 19 Feb 2024 12:02:58 +0100 Subject: [PATCH 09/44] chore(svelte): export BuiltInNode type --- packages/svelte/src/lib/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index c484ec7e..20f0f284 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -45,7 +45,7 @@ export type { DefaultEdgeOptions } from '$lib/types/edges'; export type { HandleComponentProps, FitViewOptions } from '$lib/types/general'; -export type { Node, NodeTypes, DefaultNodeOptions } from '$lib/types/nodes'; +export type { Node, NodeTypes, DefaultNodeOptions, BuiltInNode } from '$lib/types/nodes'; export type { SvelteFlowStore } from '$lib/store/types'; // system types From b15b7dbd6b8e451f949cb6a33e7fcf249c324158 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 19 Feb 2024 16:37:35 +0100 Subject: [PATCH 10/44] Fire node drag events also when dragging selection --- examples/react/src/examples/Basic/index.tsx | 11 ++++-- packages/system/src/xydrag/XYDrag.ts | 39 ++++++++++++--------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index f454b313..a41f50db 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -13,10 +13,13 @@ import { OnNodeDrag, } from '@xyflow/react'; -const onNodeDrag: OnNodeDrag = (_, node) => console.log('drag', node); -const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes); +const onNodeDragStart = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes); +const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); +const printSelectionEvent = (name: string) => (_: MouseEvent, nodes: Node[]) => console.log(name, nodes); + const initialNodes: Node[] = [ { id: '1', @@ -113,7 +116,11 @@ const BasicFlow = () => { defaultEdges={initialEdges} onNodeClick={onNodeClick} onNodeDragStop={onNodeDragStop} + onNodeDragStart={onNodeDragStart} onNodeDrag={onNodeDrag} + onSelectionDragStart={printSelectionEvent('selection drag start')} + onSelectionDrag={printSelectionEvent('selection drag')} + onSelectionDragStop={printSelectionEvent('selection drag stop')} className="react-flow-basic-example" minZoom={0.2} maxZoom={4} diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 91ed7fb8..e0a2f983 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -27,6 +27,7 @@ import type { UpdateNodePositions, Box, } from '../types'; +import { on } from 'events'; export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void; @@ -171,16 +172,19 @@ export function XYDrag voi } updateNodePositions(dragItems, true); - const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag); - if (dragEvent && (onDrag || onNodeOrSelectionDrag)) { + if (dragEvent) { const [currentNode, currentNodes] = getEventHandlerParams({ nodeId, dragItems, nodeLookup, }); onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes); - onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); + onNodeDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); + if (!nodeId) { + const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag); + _onSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); + } } } @@ -236,16 +240,18 @@ export function XYDrag voi lastPos = pointerPos; dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId); - const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart); - - if (dragItems.length > 0 && (onDragStart || onNodeOrSelectionDragStart)) { + if (dragItems.length > 0) { const [currentNode, currentNodes] = getEventHandlerParams({ nodeId, dragItems, nodeLookup, }); onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); - onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + onNodeDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + if (!nodeId) { + const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart); + _onSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + } } } @@ -300,18 +306,19 @@ export function XYDrag voi if (dragItems.length > 0) { const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems(); - const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop); updateNodePositions(dragItems, false); - if (onDragStop || onNodeOrSelectionDragStop) { - const [currentNode, currentNodes] = getEventHandlerParams({ - nodeId, - dragItems, - nodeLookup, - }); - onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); - onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + const [currentNode, currentNodes] = getEventHandlerParams({ + nodeId, + dragItems, + nodeLookup, + }); + onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); + onNodeDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + if (!nodeId) { + const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop); + _onSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); } } }) From 86ccf6807eb6786db2f44d13ac0fc9854a34b87e Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 19 Feb 2024 16:51:59 +0100 Subject: [PATCH 11/44] Removed accidental import --- packages/system/src/xydrag/XYDrag.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index e0a2f983..eaae123e 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -27,7 +27,6 @@ import type { UpdateNodePositions, Box, } from '../types'; -import { on } from 'events'; export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void; From 67fe7952f8966fe73192de5ae8225c96befbbfff Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 20 Feb 2024 00:04:20 +0100 Subject: [PATCH 12/44] chore(custom-nodes): use custom edge type --- examples/react/src/examples/CustomNode/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/react/src/examples/CustomNode/index.tsx b/examples/react/src/examples/CustomNode/index.tsx index 32bf34a0..16a99803 100644 --- a/examples/react/src/examples/CustomNode/index.tsx +++ b/examples/react/src/examples/CustomNode/index.tsx @@ -9,7 +9,6 @@ import { SnapGrid, useEdgesState, Background, - Edge, OnNodeDrag, OnInit, applyNodeChanges, @@ -17,6 +16,7 @@ import { OnConnect, OnBeforeDelete, BuiltInNode, + BuiltInEdge, } from '@xyflow/react'; import ColorSelectorNode from './ColorSelectorNode'; @@ -26,8 +26,9 @@ export type ColorSelectorNode = Node< 'selectorNode' >; export type MyNode = BuiltInNode | ColorSelectorNode; +export type MyEdge = BuiltInEdge; -const onInit: OnInit = (reactFlowInstance) => { +const onInit: OnInit = (reactFlowInstance) => { console.log('flow loaded:', reactFlowInstance); }; @@ -54,7 +55,7 @@ const CustomNodeFlow = () => { [setNodes] ); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [bgColor, setBgColor] = useState(initBgColor); @@ -144,7 +145,7 @@ const CustomNodeFlow = () => { [setEdges] ); - const onBeforeDelete: OnBeforeDelete = useCallback(async (params) => true, []); + const onBeforeDelete: OnBeforeDelete = useCallback(async (params) => true, []); return ( Date: Tue, 20 Feb 2024 14:57:06 +0100 Subject: [PATCH 13/44] chore(xydrag): cleanup --- packages/system/src/xydrag/XYDrag.ts | 49 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index eaae123e..6bd85eb9 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -93,7 +93,6 @@ export function XYDrag voi let dragItems: NodeDragItem[] = []; let autoPanStarted = false; let mousePosition: XYPosition = { x: 0, y: 0 }; - let dragEvent: MouseEvent | null = null; let containerBounds: DOMRect | null = null; let dragStarted = false; @@ -101,7 +100,7 @@ export function XYDrag voi // public functions function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) { - function updateNodes({ x, y }: XYPosition) { + function updateNodes({ x, y }: XYPosition, dragEvent: MouseEvent | null) { const { nodeLookup, nodeExtent, @@ -172,17 +171,19 @@ export function XYDrag voi updateNodePositions(dragItems, true); - if (dragEvent) { + if (dragEvent && (onDrag || onNodeDrag || (!nodeId && onSelectionDrag))) { const [currentNode, currentNodes] = getEventHandlerParams({ nodeId, dragItems, nodeLookup, }); - onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes); - onNodeDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); + + onDrag?.(dragEvent, dragItems, currentNode, currentNodes); + onNodeDrag?.(dragEvent, currentNode, currentNodes); + if (!nodeId) { const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag); - _onSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); + _onSelectionDrag(dragEvent, currentNode, currentNodes); } } } @@ -201,7 +202,7 @@ export function XYDrag voi lastPos.y = (lastPos.y ?? 0) - yMovement / transform[2]; if (panBy({ x: xMovement, y: yMovement })) { - updateNodes(lastPos as XYPosition); + updateNodes(lastPos as XYPosition, null); } } autoPanId = requestAnimationFrame(autoPan); @@ -239,17 +240,19 @@ export function XYDrag voi lastPos = pointerPos; dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId); - if (dragItems.length > 0) { + if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) { const [currentNode, currentNodes] = getEventHandlerParams({ nodeId, dragItems, nodeLookup, }); + onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); onNodeDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + if (!nodeId) { const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart); - _onSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + _onSelectionDragStart(event.sourceEvent as MouseEvent, currentNode, currentNodes); } } } @@ -288,10 +291,10 @@ export function XYDrag voi // skip events without movement if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems && dragStarted) { - dragEvent = event.sourceEvent as MouseEvent; + // dragEvent = event.sourceEvent as MouseEvent; mousePosition = getEventPosition(event.sourceEvent, containerBounds!); - updateNodes(pointerPos); + updateNodes(pointerPos, event.sourceEvent as MouseEvent); } }) .on('end', (event: UseDragEvent) => { @@ -308,16 +311,20 @@ export function XYDrag voi updateNodePositions(dragItems, false); - const [currentNode, currentNodes] = getEventHandlerParams({ - nodeId, - dragItems, - nodeLookup, - }); - onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); - onNodeDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); - if (!nodeId) { - const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop); - _onSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + if (onDragStop || onNodeDragStop || (!nodeId && onSelectionDragStop)) { + const [currentNode, currentNodes] = getEventHandlerParams({ + nodeId, + dragItems, + nodeLookup, + }); + + onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); + onNodeDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); + + if (!nodeId) { + const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop); + _onSelectionDragStop(event.sourceEvent as MouseEvent, currentNode, currentNodes); + } } } }) From c1beb6b58a4388b93187f434a730429847f8b19c Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 20 Feb 2024 16:14:39 +0100 Subject: [PATCH 14/44] chore(react): use useIsomorphicLayoutEffect hook to prevent SSR warning --- packages/react/src/hooks/useIsomorphicLayoutEffect.ts | 4 ++++ packages/react/src/hooks/useReactFlow.ts | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 packages/react/src/hooks/useIsomorphicLayoutEffect.ts diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts new file mode 100644 index 00000000..be54c9fe --- /dev/null +++ b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts @@ -0,0 +1,4 @@ +import { useEffect, useLayoutEffect } from 'react'; + +// we need this hook to prevent a warning when using react-flow in SSR +export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 02080731..5fd605fd 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -1,10 +1,11 @@ -import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system'; import useViewportHelper from './useViewportHelper'; import { useStoreApi } from './useStore'; import type { ReactFlowInstance, Instance, Node, Edge } from '../types'; import { getElementsDiffChanges, isNode } from '../utils'; +import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; /** * Hook for accessing the ReactFlow instance. @@ -54,7 +55,7 @@ export function useReactFlow { + useIsomorphicLayoutEffect(() => { // Because we need to flip the state back to false after flushing, this should // trigger the hook again (!). If the hook is being run again we know that any // updates should have been processed by now and we can safely clear the queue From 89a90cb848dd3af929315b7bce36ecf1d82bada6 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 20 Feb 2024 18:59:34 +0100 Subject: [PATCH 15/44] added id, type to result from useNodesData --- .../src/examples/UseNodesData/ResultNode.tsx | 12 ++-- .../examples/UseNodesData/UppercaseNode.tsx | 8 ++- .../react/src/examples/UseNodesData/index.tsx | 8 ++- .../routes/examples/usenodesdata/+page.svelte | 20 +++++-- .../examples/usenodesdata/ResultNode.svelte | 6 +- .../usenodesdata/UppercaseNode.svelte | 6 +- packages/react/src/hooks/useNodesData.ts | 47 +++++++++------ packages/svelte/src/lib/hooks/useNodesData.ts | 57 +++++++++---------- 8 files changed, 99 insertions(+), 65 deletions(-) diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx index ac9777dd..006af8b7 100644 --- a/examples/react/src/examples/UseNodesData/ResultNode.tsx +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -1,20 +1,20 @@ import { memo } from 'react'; import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; +import { TextNode, isTextNode, type MyNode } from '.'; function ResultNode() { const connections = useHandleConnections({ type: 'target', }); - const nodesData = useNodesData(connections.map((connection) => connection.source)); + const nodesData = useNodesData(connections.map((connection) => connection.source)); + const textNodes = nodesData.filter(isTextNode) as TextNode[]; + + textNodes[0].data; return (
-
- incoming texts:{' '} - {nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) =>
{text}
) || - 'none'} -
+
incoming texts: {textNodes.map(({ data }, i) =>
{data.text}
) || 'none'}
); } diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 699dc37e..70c6079d 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -1,16 +1,18 @@ import { memo, useEffect } from 'react'; import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; +import { isTextNode, type TextNode, type MyNode } from '.'; function UppercaseNode({ id }: NodeProps) { const { updateNodeData } = useReactFlow(); const connections = useHandleConnections({ type: 'target', }); - const nodeData = useNodesData(connections[0]?.source); + const nodesData = useNodesData(connections[0]?.source); + const textNode = isTextNode(nodesData) ? nodesData : null; useEffect(() => { - updateNodeData(id, { text: nodeData?.text.toUpperCase() }); - }, [nodeData]); + updateNodeData(id, { text: textNode?.data.text.toUpperCase() }); + }, [textNode]); return (
diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index 41c537ab..dc0b43dd 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -17,9 +17,13 @@ import UppercaseNode from './UppercaseNode'; export type TextNode = Node<{ text: string }, 'text'>; export type ResultNode = Node<{}, 'result'>; -export type UppercaseNode = Node<{}, 'uppercase'>; +export type UppercaseNode = Node<{ text: string }, 'uppercase'>; export type MyNode = TextNode | ResultNode | UppercaseNode; +export function isTextNode(node: any): node is TextNode | UppercaseNode { + return node.type === 'text' || node.type === 'uppercase'; +} + const nodeTypes = { text: TextNode, result: ResultNode, @@ -38,7 +42,7 @@ const initNodes: MyNode[] = [ { id: '1a', type: 'uppercase', - data: {}, + data: { text: '' }, position: { x: 100, y: 0 }, }, { diff --git a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte index 7b2d2204..a92acbe4 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte @@ -1,3 +1,15 @@ + +
incoming texts:
- {#each $nodeData as data} -
{data.text}
+ {#each textNodes as textNode} +
{textNode.data.text}
{/each}
diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index 2dcf3bde..be15ac9a 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -7,6 +7,7 @@ useSvelteFlow, type NodeProps } from '@xyflow/svelte'; + import { isTextNode, type MyNode } from './+page.svelte'; type $$Props = NodeProps; @@ -18,10 +19,11 @@ type: 'target' }); - $: nodeData = useNodesData($connections[0]?.source); + $: nodeData = useNodesData($connections[0]?.source); + $: textNode = isTextNode($nodeData) ? $nodeData : null; $: { - updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); + updateNodeData(id, { text: textNode?.data.text.toUpperCase() || '' }); } diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 10480caa..6d9586ed 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -4,40 +4,55 @@ import { shallow } from 'zustand/shallow'; import { useStore } from '../hooks/useStore'; import type { Node } from '../types'; +export interface NodeDataReturn { + id: string; + type: NodeType['type']; + data: NodeType['data']; +} + /** * Hook for receiving data of one or multiple nodes * * @public * @param nodeId - The id (or ids) of the node to get the data from * @param guard - Optional guard function to narrow down the node type - * @returns An array od data objects + * @returns An object (or array of object) with {id, type, data} representing each node */ -export function useNodesData(nodeId: string): NodeType['data'] | null; -export function useNodesData(nodeIds: string[]): NodeType['data'][]; export function useNodesData( - nodeIds: string[], - guard: (node: Node) => node is NodeType -): NodeType['data'][]; + nodeId: string +): { + id: string; + type: NodeType['type']; + data: NodeType['data']; +} | null; +export function useNodesData( + nodeIds: string[] +): { + id: string; + type: NodeType['type']; + data: NodeType['data']; +}[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const nodesData = useStore( useCallback( (s) => { - if (!Array.isArray(nodeIds)) { - return s.nodeLookup.get(nodeIds)?.data || null; - } - const data = []; + const isArrayOfIds = Array.isArray(nodeIds); + const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds]; - for (const nodeId of nodeIds) { - const nodeData = s.nodeLookup.get(nodeId)?.data; - - if (nodeData) { - data.push(nodeData); + for (const nodeId of _nodeIds) { + const node = s.nodeLookup.get(nodeId); + if (node) { + data.push({ + id: node.id, + type: node.type, + data: node.data, + }); } } - return data; + return isArrayOfIds ? data : data[0] ?? null; }, [nodeIds] ), diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts index d45c8c9e..1ec53f87 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -3,17 +3,13 @@ import { derived, type Readable } from 'svelte/store'; import type { Node } from '$lib/types'; import { useStore } from '$lib/store'; -function areNodesDataEqual(a: (Node['data'] | null)[] | null, b: (Node['data'] | null)[] | null) { - if ((!a && !b) || (!a?.length && !b?.length)) { - true; - } - - if (!a || !b || a.length !== b.length) { +function areNodesDataEqual(a: any[], b: any[]) { + if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { + if (a[i].data !== b[i].data) { return false; } } @@ -31,42 +27,45 @@ function areNodesDataEqual(a: (Node['data'] | null)[] | null, b: (Node['data'] | */ export function useNodesData( nodeId: string -): Readable; +): Readable<{ + id: string; + type: NodeType['type']; + data: NodeType['data']; +} | null>; export function useNodesData( nodeIds: string[] -): Readable; -export function useNodesData( - nodeIds: string[], - guard: (node: Node) => node is NodeType -): Readable; +): Readable< + { + id: string; + type: NodeType['type']; + data: NodeType['data']; + }[] +>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const { nodes, nodeLookup } = useStore(); - let prevNodesData: (Node['data'] | null)[] | null = null; + let prevNodesData: any = []; return derived([nodes, nodeLookup], ([, nodeLookup], set) => { - let nextNodesData: (Node['data'] | null)[] | null = null; - const nodeIdArray = Array.isArray(nodeIds); + let nextNodesData = []; - if (!nodeIdArray) { - nextNodesData = [nodeLookup.get(nodeIds)?.data || null]; - } else { - const data = []; + const isArrayOfIds = Array.isArray(nodeIds); + const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds]; - for (const nodeId of nodeIds) { - const nodeData = nodeLookup.get(nodeId)?.data; - - if (nodeData) { - data.push(nodeData); - } + for (const nodeId of _nodeIds) { + const node = nodeLookup.get(nodeId); + if (node) { + nextNodesData.push({ + id: node.id, + type: node.type, + data: node.data + }); } - - nextNodesData = data; } if (!areNodesDataEqual(nextNodesData, prevNodesData)) { prevNodesData = nextNodesData; - set(nodeIdArray ? nextNodesData : nextNodesData[0]); + set(isArrayOfIds ? nextNodesData : nextNodesData[0] ?? null); } }); } From 9fb49796311bcee8cad7c47cd00800b1813369bc Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 21 Feb 2024 07:41:56 +0100 Subject: [PATCH 16/44] Pane: only use cursor grab when panOnDrag=true --- packages/react/src/container/Pane/index.tsx | 2 +- packages/svelte/src/lib/container/Pane/Pane.svelte | 1 + packages/system/src/styles/init.css | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index af1ca28e..414a4745 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -220,7 +220,7 @@ export function Pane({ return (
Date: Wed, 21 Feb 2024 08:01:39 +0100 Subject: [PATCH 17/44] Node: only use cursor grab when draggable --- packages/react/src/components/NodeWrapper/index.tsx | 1 + packages/system/src/styles/init.css | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 4199d158..6d2231d9 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -220,6 +220,7 @@ export function NodeWrapper({ selected: node.selected, selectable: isSelectable, parent: isParent, + draggable: isDraggable, dragging, }, ])} diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 6d6a4eec..5c01aece 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -186,16 +186,15 @@ svg.xy-flow__connectionline { pointer-events: all; transform-origin: 0 0; box-sizing: border-box; - cursor: grab; + cursor: default; - &.dragging { - cursor: grabbing; - } - - /* only used in Svelte Flow, should we remove it here? */ &.draggable { cursor: grab; pointer-events: all; + + &.dragging { + cursor: grabbing; + } } } From 62bfc7acc6f80b5c85346c4ff2f2417e8f6b2428 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 21 Feb 2024 08:27:50 +0100 Subject: [PATCH 18/44] disableKeyboardA11y now correctly prevents escape key presses --- packages/react/src/components/EdgeWrapper/index.tsx | 3 ++- packages/react/src/components/NodeWrapper/index.tsx | 11 +++++------ packages/react/src/container/EdgeRenderer/index.tsx | 2 ++ packages/react/src/types/edges.ts | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx index cbe01b8c..3baea708 100644 --- a/packages/react/src/components/EdgeWrapper/index.tsx +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -34,6 +34,7 @@ export function EdgeWrapper({ edgeTypes, noPanClassName, onError, + disableKeyboardA11y, }: EdgeWrapperProps): JSX.Element | null { let edge = useStore((s) => s.edgeLookup.get(id)!) as EdgeType; const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions); @@ -160,7 +161,7 @@ export function EdgeWrapper({ : undefined; const onKeyDown = (event: KeyboardEvent) => { - if (elementSelectionKeys.includes(event.key) && isSelectable) { + if (!disableKeyboardA11y && elementSelectionKeys.includes(event.key) && isSelectable) { const { unselectNodesAndEdges, addSelectedEdges } = store.getState(); const unselect = event.key === 'Escape'; diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 4199d158..08a1f187 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -178,6 +178,10 @@ export function NodeWrapper({ return; } + if (disableKeyboardA11y) { + return; + } + if (elementSelectionKeys.includes(event.key) && isSelectable) { const unselect = event.key === 'Escape'; @@ -187,12 +191,7 @@ export function NodeWrapper({ unselect, nodeRef, }); - } else if ( - !disableKeyboardA11y && - isDraggable && - node.selected && - Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key) - ) { + } else if (isDraggable && node.selected && Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) { store.setState({ ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') diff --git a/packages/react/src/container/EdgeRenderer/index.tsx b/packages/react/src/container/EdgeRenderer/index.tsx index 3112179e..6636e833 100644 --- a/packages/react/src/container/EdgeRenderer/index.tsx +++ b/packages/react/src/container/EdgeRenderer/index.tsx @@ -56,6 +56,7 @@ function EdgeRendererComponent({ onEdgeDoubleClick, onEdgeUpdateStart, onEdgeUpdateEnd, + disableKeyboardA11y, }: EdgeRendererProps) { const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow); const edgeIds = useVisibleEdgeIds(onlyRenderVisibleElements); @@ -86,6 +87,7 @@ function EdgeRendererComponent({ rfId={rfId} onError={onError} edgeTypes={edgeTypes} + disableKeyboardA11y={disableKeyboardA11y} /> ); })} diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index dac682db..602f2c34 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -83,6 +83,7 @@ export type EdgeWrapperProps = { rfId?: string; edgeTypes?: EdgeTypes; onError?: OnError; + disableKeyboardA11y?: boolean; }; export type DefaultEdgeOptions = DefaultEdgeOptionsBase; From f0d1e85cf1856e9664910ab8c04a345bfcd4112e Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 22 Feb 2024 11:12:08 +0100 Subject: [PATCH 19/44] selectable correctly influences cursor types for nodes & edges --- .../src/lib/components/EdgeWrapper/EdgeWrapper.svelte | 5 ++++- .../src/lib/container/EdgeRenderer/EdgeRenderer.svelte | 1 + packages/system/src/styles/init.css | 9 ++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 0d4c126b..1dff49e7 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -22,6 +22,7 @@ export let animated: $$Props['animated'] = false; export let selected: $$Props['selected'] = false; + export let selectable: $$Props['selectable'] = undefined; export let hidden: $$Props['hidden'] = false; export let label: $$Props['label'] = undefined; export let labelStyle: $$Props['labelStyle'] = undefined; @@ -44,7 +45,7 @@ setContext('svelteflow__edge_id', id); - const { edgeLookup, edgeTypes, flowId } = useStore(); + const { edgeLookup, edgeTypes, flowId, elementsSelectable } = useStore(); const dispatch = createEventDispatcher<{ edgeclick: { edge: Edge; event: MouseEvent | TouchEvent }; edgecontextmenu: { edge: Edge; event: MouseEvent }; @@ -53,6 +54,7 @@ $: edgeComponent = $edgeTypes[type!] || BezierEdgeInternal; $: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined; $: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined; + $: isSelectable = selectable || ($elementsSelectable && typeof selectable === 'undefined'); const handleEdgeSelect = useHandleEdgeSelect(); @@ -82,6 +84,7 @@ class={cc(['svelte-flow__edge', className])} class:animated class:selected + class:selectable={isSelectable} data-id={id} on:click={onClick} on:contextmenu={onContextMenu} diff --git a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte index d74027e7..0fa2c273 100644 --- a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte +++ b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte @@ -33,6 +33,7 @@ style={edge.style} animated={edge.animated} selected={edge.selected} + selectable={edge.selectable} hidden={edge.hidden} label={edge.label} labelStyle={edge.labelStyle} diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 5c01aece..46628842 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -123,7 +123,10 @@ .xy-flow__edge { pointer-events: visibleStroke; - cursor: pointer; + + &.selectable { + cursor: pointer; + } &.animated path { stroke-dasharray: 5; @@ -188,6 +191,10 @@ svg.xy-flow__connectionline { box-sizing: border-box; cursor: default; + &.selectable { + cursor: pointer; + } + &.draggable { cursor: grab; pointer-events: all; From 3fc3b7e6038a19e6b8010161d2bffd322c13a928 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 26 Feb 2024 14:13:12 +0100 Subject: [PATCH 20/44] chore(useNodesData): cleanup return type --- .../src/examples/UseNodesData/ResultNode.tsx | 6 ++---- .../react/src/examples/UseNodesData/index.tsx | 4 ++-- .../routes/examples/usenodesdata/+page.svelte | 21 ++++++++++++------- .../examples/usenodesdata/ResultNode.svelte | 4 ++-- packages/react/src/hooks/useNodesData.ts | 14 ++----------- packages/svelte/src/lib/hooks/useNodesData.ts | 18 +++------------- 6 files changed, 24 insertions(+), 43 deletions(-) diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx index 006af8b7..bf8f7a35 100644 --- a/examples/react/src/examples/UseNodesData/ResultNode.tsx +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -1,15 +1,13 @@ import { memo } from 'react'; import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; -import { TextNode, isTextNode, type MyNode } from '.'; +import { isTextNode, type MyNode } from '.'; function ResultNode() { const connections = useHandleConnections({ type: 'target', }); const nodesData = useNodesData(connections.map((connection) => connection.source)); - const textNodes = nodesData.filter(isTextNode) as TextNode[]; - - textNodes[0].data; + const textNodes = nodesData.filter(isTextNode); return (
diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index dc0b43dd..91cc1f94 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -20,8 +20,8 @@ export type ResultNode = Node<{}, 'result'>; export type UppercaseNode = Node<{ text: string }, 'uppercase'>; export type MyNode = TextNode | ResultNode | UppercaseNode; -export function isTextNode(node: any): node is TextNode | UppercaseNode { - return node.type === 'text' || node.type === 'uppercase'; +export function isTextNode(node: any): node is TextNode { + return node.type === 'text'; } const nodeTypes = { diff --git a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte index a92acbe4..fc85ee26 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte @@ -1,13 +1,15 @@ diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 6d9586ed..c43a60cc 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -20,18 +20,8 @@ export interface NodeDataReturn { */ export function useNodesData( nodeId: string -): { - id: string; - type: NodeType['type']; - data: NodeType['data']; -} | null; -export function useNodesData( - nodeIds: string[] -): { - id: string; - type: NodeType['type']; - data: NodeType['data']; -}[]; +): Pick | null; +export function useNodesData(nodeIds: string[]): Pick[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const nodesData = useStore( diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts index 1ec53f87..c11ef308 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -22,33 +22,21 @@ function areNodesDataEqual(a: any[], b: any[]) { * * @public * @param nodeId - The id (or ids) of the node to get the data from - * @param guard - Optional guard function to narrow down the node type * @returns A readable store with an array of data objects */ export function useNodesData( nodeId: string -): Readable<{ - id: string; - type: NodeType['type']; - data: NodeType['data']; -} | null>; +): Readable | null>; export function useNodesData( nodeIds: string[] -): Readable< - { - id: string; - type: NodeType['type']; - data: NodeType['data']; - }[] ->; +): Readable[]>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const { nodes, nodeLookup } = useStore(); let prevNodesData: any = []; return derived([nodes, nodeLookup], ([, nodeLookup], set) => { - let nextNodesData = []; - + const nextNodesData = []; const isArrayOfIds = Array.isArray(nodeIds); const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds]; From 0ad5f2d16a53edc07b7a34d4675562854b279789 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 26 Feb 2024 14:22:22 +0100 Subject: [PATCH 21/44] chore(react): cleanup node wrapper --- packages/react/src/components/NodeWrapper/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 08a1f187..8d2aa097 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -174,11 +174,7 @@ export function NodeWrapper({ }; const onKeyDown = (event: KeyboardEvent) => { - if (isInputDOMNode(event.nativeEvent)) { - return; - } - - if (disableKeyboardA11y) { + if (isInputDOMNode(event.nativeEvent) || disableKeyboardA11y) { return; } From d88f72a6cca9a12c9b7a91bc330927f00b66b780 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 26 Feb 2024 14:35:43 +0100 Subject: [PATCH 22/44] chore(packages): update changelogs --- packages/react/CHANGELOG.md | 12 ++++++++++++ packages/svelte/CHANGELOG.md | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index e2e23ca4..593cd975 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,17 @@ # @xyflow/react +## 12.0.0-next.10 + +## ⚠️ Breaking changes + +- `useNodesData` not only returns data objects but also the type and the id of the node. + +## Patch changes + +- better cursor defaults for the pane, nodes and edges +- `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges + + ## 12.0.0-next.9 ### Patch changes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 2f34e1a3..0e8a5abf 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # @xyflow/svelte +## 0.0.37 + +## ⚠️ Breaking changes + +- `useNodesData` not only returns data objects but also the type and the id of the node. + +## Patch changes + +- better cursor defaults for the pane, nodes and edges. + ## 0.0.36 ## Patch changes From 14516ab061769f2eb79399f22479073cdc1ce25c Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 26 Feb 2024 15:01:17 +0100 Subject: [PATCH 23/44] fix(react): drag nodes after toggle hidden closes #3931 --- packages/react/CHANGELOG.md | 2 +- packages/react/src/hooks/useDrag.ts | 5 ++--- packages/svelte/src/lib/actions/drag/index.ts | 1 - packages/system/src/xydrag/XYDrag.ts | 10 ++++------ 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 593cd975..d9529c27 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -10,7 +10,7 @@ - better cursor defaults for the pane, nodes and edges - `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges - +- fix bug where users couldn't drag a node after toggle nodes `hidden` attribute ## 12.0.0-next.9 diff --git a/packages/react/src/hooks/useDrag.ts b/packages/react/src/hooks/useDrag.ts index 8363d393..52d3c6f6 100644 --- a/packages/react/src/hooks/useDrag.ts +++ b/packages/react/src/hooks/useDrag.ts @@ -33,7 +33,6 @@ export function useDrag({ useEffect(() => { if (nodeRef?.current) { xyDrag.current = XYDrag({ - domNode: nodeRef.current, getStoreItems: () => store.getState(), onNodeMouseDown: (id: string) => { handleNodeClick({ @@ -55,11 +54,11 @@ export function useDrag({ useEffect(() => { if (disabled) { xyDrag.current?.destroy(); - } else { + } else if (nodeRef.current) { xyDrag.current?.update({ noDragClassName, handleSelector, - domNode: nodeRef.current as Element, + domNode: nodeRef.current, isSelectable, nodeId, }); diff --git a/packages/svelte/src/lib/actions/drag/index.ts b/packages/svelte/src/lib/actions/drag/index.ts index d08a17be..1405ccd9 100644 --- a/packages/svelte/src/lib/actions/drag/index.ts +++ b/packages/svelte/src/lib/actions/drag/index.ts @@ -19,7 +19,6 @@ export type UseDragParams = { export default function drag(domNode: Element, params: UseDragParams) { const { store, onDrag, onDragStart, onDragStop, onNodeMouseDown } = params; const dragInstance = XYDrag({ - domNode, onDrag, onDragStart, onDragStop, diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 6bd85eb9..cfed09f9 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -1,5 +1,5 @@ import { drag } from 'd3-drag'; -import { select } from 'd3-selection'; +import { select, type Selection } from 'd3-selection'; import { calcAutoPan, @@ -58,7 +58,6 @@ type StoreItems = { }; export type XYDragParams = { - domNode: Element; getStoreItems: () => StoreItems; onDragStart?: OnDrag; onDrag?: OnDrag; @@ -81,7 +80,6 @@ export type DragUpdateParams = { // eslint-disable-next-line @typescript-eslint/no-explicit-any export function XYDrag void | undefined>({ - domNode, onNodeMouseDown, getStoreItems, onDragStart, @@ -95,11 +93,11 @@ export function XYDrag voi let mousePosition: XYPosition = { x: 0, y: 0 }; let containerBounds: DOMRect | null = null; let dragStarted = false; - - const d3Selection = select(domNode); + let d3Selection: Selection | null = null; // public functions function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) { + d3Selection = select(domNode); function updateNodes({ x, y }: XYPosition, dragEvent: MouseEvent | null) { const { nodeLookup, @@ -342,7 +340,7 @@ export function XYDrag voi } function destroy() { - d3Selection.on('.drag', null); + d3Selection?.on('.drag', null); } return { From e5c667d0683bb82b30634c244f1327e161218041 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 27 Feb 2024 15:21:35 +0100 Subject: [PATCH 24/44] Feat(nodes): add `initialWidth` and `initialHeight` (#3953) * feat(react/svelte): add initialWidth/initialHeight closes #3793 * chore(packages): update changelogs --- .../src/components/ReactFlowExample/index.tsx | 2 + .../ReactFlowInitialExample/CustomNode.tsx | 21 ++++++ .../ReactFlowInitialExample/index.tsx | 69 +++++++++++++++++++ .../CustomNode.svelte | 25 +++++++ .../SvelteFlowInitialExample/index.svelte | 39 +++++++++++ examples/astro-xyflow/src/pages/index.astro | 24 +++++-- packages/react/CHANGELOG.md | 3 +- .../MiniMap/MiniMapNodes.tsx | 11 +-- .../src/components/NodeWrapper/index.tsx | 21 +++--- .../src/components/NodeWrapper/utils.tsx | 21 +++++- .../src/components/StoreUpdater/index.tsx | 1 + .../react/src/container/ReactFlow/index.tsx | 2 + packages/react/src/store/index.ts | 16 ++++- packages/react/src/store/initialState.ts | 5 +- packages/react/src/types/component-props.ts | 5 ++ packages/react/src/types/store.ts | 1 + packages/svelte/CHANGELOG.md | 5 +- .../components/NodeWrapper/NodeWrapper.svelte | 18 ++++- .../src/lib/components/NodeWrapper/types.ts | 8 ++- .../src/lib/components/NodeWrapper/utils.ts | 33 +++++++++ .../NodeRenderer/NodeRenderer.svelte | 22 ++++-- .../src/lib/plugins/Minimap/Minimap.svelte | 14 ++-- packages/svelte/src/lib/store/index.ts | 2 - .../svelte/src/lib/store/initial-store.ts | 6 +- packages/system/src/types/nodes.ts | 6 +- packages/system/src/utils/edges/positions.ts | 13 ++-- packages/system/src/utils/general.ts | 16 +++++ packages/system/src/utils/graph.ts | 13 ++-- 28 files changed, 362 insertions(+), 60 deletions(-) create mode 100644 examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx create mode 100644 examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx create mode 100644 examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte create mode 100644 examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte create mode 100644 packages/svelte/src/lib/components/NodeWrapper/utils.ts diff --git a/examples/astro-xyflow/src/components/ReactFlowExample/index.tsx b/examples/astro-xyflow/src/components/ReactFlowExample/index.tsx index 4f8ef0ab..863458fb 100644 --- a/examples/astro-xyflow/src/components/ReactFlowExample/index.tsx +++ b/examples/astro-xyflow/src/components/ReactFlowExample/index.tsx @@ -81,6 +81,8 @@ const initialNodes: Node[] = [ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, + width: 200, + height: 50, type: 'custom', }, ]; diff --git a/examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx b/examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx new file mode 100644 index 00000000..c7fae3d8 --- /dev/null +++ b/examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx @@ -0,0 +1,21 @@ +import { memo, useState } from 'react'; +import { Handle, Position } from '@xyflow/react'; + +function CustomNode() { + const [text, setText] = useState('this is a pretty long text'); + + return ( + <> + +
+
+ setText(e.target.value)} /> +
text: {text}
+
+
+ + + ); +} + +export default memo(CustomNode); diff --git a/examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx b/examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx new file mode 100644 index 00000000..b3f99a6b --- /dev/null +++ b/examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx @@ -0,0 +1,69 @@ +import { useCallback } from 'react'; +import { + ReactFlow, + addEdge, + useEdgesState, + useNodesState, + Background, + Controls, + type Connection, + type Edge, + type Node, +} from '@xyflow/react'; + +import CustomNode from './CustomNode'; + +import '@xyflow/react/dist/style.css'; + +const initialNodes: Node[] = [ + { + id: '1', + data: {}, + position: { x: 0, y: 0 }, + initialWidth: 200, + initialHeight: 50, + type: 'custom', + }, + { + id: '2', + data: {}, + position: { x: 0, y: 200 }, + width: 200, + initialHeight: 50, + type: 'custom', + }, +]; + +const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }]; + +const nodeTypes = { + custom: CustomNode, +}; + +function Flow() { + const [nodes, , onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]); + + return ( +
+ + + + +
+ ); +} + +export default Flow; diff --git a/examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte b/examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte new file mode 100644 index 00000000..5bef87c9 --- /dev/null +++ b/examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte @@ -0,0 +1,25 @@ + + + +
+
+ text: {text} +
+ +
+ + + diff --git a/examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte b/examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte new file mode 100644 index 00000000..8fbe4905 --- /dev/null +++ b/examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte @@ -0,0 +1,39 @@ + + +
+ + + + +
diff --git a/examples/astro-xyflow/src/pages/index.astro b/examples/astro-xyflow/src/pages/index.astro index be9d1885..d7b14558 100644 --- a/examples/astro-xyflow/src/pages/index.astro +++ b/examples/astro-xyflow/src/pages/index.astro @@ -1,6 +1,8 @@ --- -import ReactFlowApp from '../components/ReactFlowExample' -import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte' +import ReactFlowApp from '../components/ReactFlowExample'; +import ReactFlowInitialApp from '../components/ReactFlowInitialExample'; +import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte'; +import SvelteFlowInitialApp from '../components/SvelteFlowInitialExample/index.svelte'; --- @@ -18,10 +20,24 @@ import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte' +

React Flow

+

no client hydration

+ + +

client hydration on load (client:load)

+ + +

client hydration on load (client:load) and initialWidth / initialHeight

+ +

Svelte Flow

-

React Flow

- +

client hydration on load (client:load)

+ + +

client hydration on load (client:load) and initialWidth / initialHeight

+ + diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index d9529c27..69b51bf7 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -4,13 +4,14 @@ ## ⚠️ Breaking changes -- `useNodesData` not only returns data objects but also the type and the id of the node. +- `useNodesData` not only returns data objects but also the type and the id of the node ## Patch changes - better cursor defaults for the pane, nodes and edges - `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges - fix bug where users couldn't drag a node after toggle nodes `hidden` attribute +- add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr ## 12.0.0-next.9 diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index 0dfb8fff..0abe3177 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ComponentType, memo } from 'react'; -import { NodeOrigin, getNodePositionWithOrigin } from '@xyflow/system'; +import { NodeOrigin, getNodeDimensions, getNodePositionWithOrigin, nodeHasDimensions } from '@xyflow/system'; import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; @@ -94,16 +94,19 @@ function NodeComponentWrapperInner({ y, }; }, shallow); - if (!node || node.hidden || !(node.computed?.width || node.width) || !(node.computed?.height || node.height)) { + + if (!node || node.hidden || !nodeHasDimensions(node)) { return null; } + const { width, height } = getNodeDimensions(node); + return ( ({ @@ -79,11 +81,9 @@ export function NodeWrapper({ const prevTargetPosition = useRef(node.targetPosition); const prevType = useRef(nodeType); - const width = node.width ?? undefined; - const height = node.height ?? undefined; - const computedWidth = node.computed?.width; - const computedHeight = node.computed?.height; - const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height); + const nodeDimensions = getNodeDimensions(node); + const inlineDimensions = getNodeInlineStyleDimensions(node); + const initialized = nodeHasDimensions(node); const hasHandleBounds = !!node[internalsSymbol]?.handleBounds; const moveSelectedNodes = useMoveSelectedNodes(); @@ -143,8 +143,7 @@ export function NodeWrapper({ const positionAbsoluteOrigin = getPositionWithOrigin({ x: positionAbsoluteX, y: positionAbsoluteY, - width: computedWidth ?? width ?? 0, - height: computedHeight ?? height ?? 0, + ...nodeDimensions, origin: node.origin || nodeOrigin, }); const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; @@ -226,8 +225,7 @@ export function NodeWrapper({ pointerEvents: hasPointerEvents ? 'all' : 'none', visibility: initialized ? 'visible' : 'hidden', ...node.style, - width: width ?? node.style?.width, - height: height ?? node.style?.height, + ...inlineDimensions, }} data-id={id} data-testid={`rf__node-${id}`} @@ -248,8 +246,6 @@ export function NodeWrapper({ id={id} data={node.data} type={nodeType} - width={computedWidth} - height={computedHeight} positionAbsoluteX={positionAbsoluteX} positionAbsoluteY={positionAbsoluteY} selected={node.selected} @@ -259,6 +255,7 @@ export function NodeWrapper({ dragging={dragging} dragHandle={node.dragHandle} zIndex={zIndex} + {...nodeDimensions} />
diff --git a/packages/react/src/components/NodeWrapper/utils.tsx b/packages/react/src/components/NodeWrapper/utils.tsx index 5dd73964..f98b6bb6 100644 --- a/packages/react/src/components/NodeWrapper/utils.tsx +++ b/packages/react/src/components/NodeWrapper/utils.tsx @@ -5,7 +5,7 @@ import { InputNode } from '../Nodes/InputNode'; import { DefaultNode } from '../Nodes/DefaultNode'; import { GroupNode } from '../Nodes/GroupNode'; import { OutputNode } from '../Nodes/OutputNode'; -import type { NodeTypes } from '../../types'; +import type { Node, NodeTypes } from '../../types'; export const arrowKeyDiffs: Record = { ArrowUp: { x: 0, y: -1 }, @@ -20,3 +20,22 @@ export const builtinNodeTypes: NodeTypes = { output: OutputNode as ComponentType, group: GroupNode as ComponentType, }; + +export function getNodeInlineStyleDimensions( + node: NodeType +): { + width: number | string | undefined; + height: number | string | undefined; +} { + if (!node.computed) { + return { + width: node.width ?? node.initialWidth ?? node.style?.width, + height: node.height ?? node.initialHeight ?? node.style?.height, + }; + } + + return { + width: node.width ?? node.style?.width, + height: node.height ?? node.style?.height, + }; +} diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 7917e3d6..6d92e8b4 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -65,6 +65,7 @@ const reactFlowFieldsToTrack = [ 'selectNodesOnDrag', 'nodeDragThreshold', 'onBeforeDelete', + 'debug', ] as const; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index c80552f8..88d8843e 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -139,6 +139,7 @@ function ReactFlow( width, height, colorMode = 'light', + debug, ...rest }: ReactFlowProps, ref: ForwardedRef @@ -274,6 +275,7 @@ function ReactFlow( selectNodesOnDrag={selectNodesOnDrag} nodeDragThreshold={nodeDragThreshold} onBeforeDelete={onBeforeDelete} + debug={debug} /> {children} diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 71a51a1e..823d5022 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -89,6 +89,7 @@ const createRFStore = ({ fitViewOnInitOptions, domNode, nodeOrigin, + debug, } = get(); const changes: NodeDimensionChange[] = []; @@ -130,6 +131,9 @@ const createRFStore = ({ set({ nodes: nextNodes, fitViewDone: nextFitViewDone }); if (changes?.length > 0) { + if (debug) { + console.log('React Flow: trigger node changes', changes); + } onNodesChange?.(changes); } }, @@ -149,7 +153,7 @@ const createRFStore = ({ get().triggerNodeChanges(changes); }, triggerNodeChanges: (changes) => { - const { onNodesChange, setNodes, nodes, hasDefaultNodes } = get(); + const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get(); if (changes?.length) { if (hasDefaultNodes) { @@ -157,11 +161,15 @@ const createRFStore = ({ setNodes(updatedNodes); } + if (debug) { + console.log('React Flow: trigger node changes', changes); + } + onNodesChange?.(changes); } }, triggerEdgeChanges: (changes) => { - const { onEdgesChange, setEdges, edges, hasDefaultEdges } = get(); + const { onEdgesChange, setEdges, edges, hasDefaultEdges, debug } = get(); if (changes?.length) { if (hasDefaultEdges) { @@ -169,6 +177,10 @@ const createRFStore = ({ setEdges(updatedEdges); } + if (debug) { + console.log('React Flow: trigger edge changes', changes); + } + onEdgesChange?.(changes); } }, diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 5a1f9562..0cf3c7f0 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -43,7 +43,9 @@ const getInitialState = ({ let transform: Transform = [0, 0, 1]; if (fitView && width && height) { - const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height); + const nodesWithDimensions = nextNodes.filter( + (node) => (node.width || node.initialWidth) && (node.height || node.initialHeight) + ); // @todo users nodeOrigin should be used here const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] }); const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); @@ -113,6 +115,7 @@ const getInitialState = ({ onSelectionChangeHandlers: [], lib: 'react', + debug: false, }; }; diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 650d0b0c..e8d91ba4 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -503,6 +503,11 @@ export interface ReactFlowProps; lib: string; + debug: boolean; }; export type ReactFlowActions = { diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 0e8a5abf..948e69eb 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -4,11 +4,12 @@ ## ⚠️ Breaking changes -- `useNodesData` not only returns data objects but also the type and the id of the node. +- `useNodesData` not only returns data objects but also the type and the id of the node ## Patch changes -- better cursor defaults for the pane, nodes and edges. +- better cursor defaults for the pane, nodes and edges +- add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr ## 0.0.36 diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index ae894066..91027f53 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -11,6 +11,7 @@ import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import type { NodeWrapperProps } from './types'; import type { Node } from '$lib/types'; + import { getNodeInlineStyleDimensions } from './utils'; interface $$Props extends NodeWrapperProps {} @@ -34,6 +35,10 @@ export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined; export let targetPosition: NodeWrapperProps['targetPosition'] = undefined; export let zIndex: NodeWrapperProps['zIndex']; + export let computedWidth: NodeWrapperProps['computedWidth'] = undefined; + export let computedHeight: NodeWrapperProps['computedHeight'] = undefined; + export let initialWidth: NodeWrapperProps['initialWidth'] = undefined; + export let initialHeight: NodeWrapperProps['initialHeight'] = undefined; export let width: NodeWrapperProps['width'] = undefined; export let height: NodeWrapperProps['height'] = undefined; export let dragHandle: NodeWrapperProps['dragHandle'] = undefined; @@ -77,6 +82,15 @@ let prevSourcePosition: Position | undefined = undefined; let prevTargetPosition: Position | undefined = undefined; + $: inlineStyleDimensions = getNodeInlineStyleDimensions({ + width, + height, + initialWidth, + initialHeight, + computedWidth, + computedHeight + }); + $: { connectableStore.set(!!connectable); } @@ -170,9 +184,7 @@ style:z-index={zIndex} style:transform="translate({positionOriginX}px, {positionOriginY}px)" style:visibility={initialized ? 'visible' : 'hidden'} - style="{style ?? ''}; {!width ? '' : `width:${width}px;`} {!height - ? '' - : `height:${height}px;`}" + style="{inlineStyleDimensions.width} {inlineStyleDimensions.height} {style ?? ''};" on:click={onSelectNodeHandler} on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })} on:mouseleave={(event) => dispatch('nodemouseleave', { node, event })} diff --git a/packages/svelte/src/lib/components/NodeWrapper/types.ts b/packages/svelte/src/lib/components/NodeWrapper/types.ts index 39fb0c10..a290ec08 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/types.ts +++ b/packages/svelte/src/lib/components/NodeWrapper/types.ts @@ -16,9 +16,13 @@ export type NodeWrapperProps = Pick< | 'targetPosition' | 'dragHandle' | 'hidden' + | 'width' + | 'height' + | 'initialWidth' + | 'initialHeight' > & { - width?: number; - height?: number; + computedWidth?: number; + computedHeight?: number; type: string; positionX: number; positionY: number; diff --git a/packages/svelte/src/lib/components/NodeWrapper/utils.ts b/packages/svelte/src/lib/components/NodeWrapper/utils.ts new file mode 100644 index 00000000..b2609587 --- /dev/null +++ b/packages/svelte/src/lib/components/NodeWrapper/utils.ts @@ -0,0 +1,33 @@ +export function getNodeInlineStyleDimensions({ + width, + height, + initialWidth, + initialHeight, + computedWidth, + computedHeight +}: { + width?: number; + height?: number; + initialWidth?: number; + initialHeight?: number; + computedWidth?: number; + computedHeight?: number; +}): { + width: string | undefined; + height: string | undefined; +} { + if (computedWidth === undefined && computedHeight === undefined) { + const styleWidth = width ?? initialWidth; + const styleHeight = height ?? initialHeight; + + return { + width: styleWidth ? `width:${styleWidth}px;` : '', + height: styleHeight ? `height:${styleHeight}px;` : '' + }; + } + + return { + width: width ? `width:${width}px;` : '', + height: height ? `height:${height}px;` : '' + }; +} diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte index df399a7d..8761942d 100644 --- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte +++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte @@ -1,6 +1,11 @@ + + + +
+ + + + + +
diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index 91027f53..a8b4f895 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -1,7 +1,13 @@ @@ -141,6 +152,11 @@ The Handle component is the part of a node that can be used to connect nodes. 'nodrag', 'nopan', position, + { + valid, + connectingto: connectingTo, + connectingfrom: connectingFrom + }, className ])} class:source={!isTarget} diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 4ddb5313..4f99a97c 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -15,7 +15,7 @@ import { type ConnectionHandle, } from '../types'; -import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType, resetRecentHandle } from './utils'; +import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils'; export type OnPointerDownParams = { autoPanOnConnect: boolean; @@ -107,7 +107,6 @@ function onPointerDown( return; } - let prevActiveHandle: Element; let connectionPosition = getEventPosition(event, containerBounds); let autoPanStarted = false; let connection: Connection | null = null; @@ -194,18 +193,6 @@ function onPointerDown( connectionStatus: getConnectionStatus(!!closestHandle, isValid), connectionEndHandle: result.endHandle, }); - - if (!closestHandle && !isValid && !handleDomNode) { - return resetRecentHandle(prevActiveHandle, lib); - } - - if (connection?.source !== connection?.target && handleDomNode) { - resetRecentHandle(prevActiveHandle, lib); - prevActiveHandle = handleDomNode; - handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`); - handleDomNode.classList.toggle('valid', isValid); - handleDomNode.classList.toggle(`${lib}-flow__handle-valid`, isValid); - } } function onPointerUp(event: MouseEvent | TouchEvent) { @@ -221,7 +208,6 @@ function onPointerDown( onEdgeUpdateEnd?.(event); } - resetRecentHandle(prevActiveHandle, lib); cancelConnection(); cancelAnimationFrame(autoPanId); autoPanStarted = false; diff --git a/packages/system/src/xyhandle/utils.ts b/packages/system/src/xyhandle/utils.ts index c10356b1..8c82f6e3 100644 --- a/packages/system/src/xyhandle/utils.ts +++ b/packages/system/src/xyhandle/utils.ts @@ -101,10 +101,6 @@ export function getHandleType( return null; } -export function resetRecentHandle(handleDomNode: Element, lib: string): void { - handleDomNode?.classList.remove('valid', 'connecting', `${lib}-flow__handle-valid`, `${lib}-flow__handle-connecting`); -} - export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) { let connectionStatus = null; From 59e656ea24aca044851971930da823644f159faa Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 12:54:24 +0100 Subject: [PATCH 39/44] chore(changelogs) --- packages/react/CHANGELOG.md | 1 + packages/svelte/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 69b51bf7..0f5f3b92 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -5,6 +5,7 @@ ## ⚠️ Breaking changes - `useNodesData` not only returns data objects but also the type and the id of the node +- status class names for Handle components are slightly different. It's now "connectingfrom" and "connectingto" instead of "connecting" ## Patch changes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 948e69eb..ea7f4181 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -5,6 +5,7 @@ ## ⚠️ Breaking changes - `useNodesData` not only returns data objects but also the type and the id of the node +- status class names for Handle components are slightly different. It's now "connectingfrom" and "connectingto" instead of "connecting" ## Patch changes From 0de6d4db332842e0188d4e86e0074c172aad5743 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 13:21:16 +0100 Subject: [PATCH 40/44] chore(examples): use width/height for specifying node sizes --- .../react/src/examples/NodeResizer/index.tsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/react/src/examples/NodeResizer/index.tsx b/examples/react/src/examples/NodeResizer/index.tsx index 42e83523..b0a272f2 100644 --- a/examples/react/src/examples/NodeResizer/index.tsx +++ b/examples/react/src/examples/NodeResizer/index.tsx @@ -50,7 +50,9 @@ const initialNodes: Node[] = [ maxHeight: 200, }, position: { x: 0, y: 60 }, - style: { ...nodeStyle, width: 100, height: 80 }, + width: 100, + height: 80, + style: { ...nodeStyle }, }, { id: '1b', @@ -64,9 +66,9 @@ const initialNodes: Node[] = [ maxHeight: 400, }, position: { x: 250, y: 0 }, + width: 174, + height: 123, style: { - width: 174, - height: 123, ...nodeStyle, }, }, @@ -75,7 +77,9 @@ const initialNodes: Node[] = [ type: 'customResizer', data: { label: 'custom resize icon' }, position: { x: 0, y: 200 }, - style: { width: 100, height: 60, ...nodeStyle }, + width: 100, + height: 60, + style: { ...nodeStyle }, }, { id: '3', @@ -94,7 +98,8 @@ const initialNodes: Node[] = [ keepAspectRatio: true, }, position: { x: 400, y: 200 }, - style: { ...nodeStyle, height: 50 }, + height: 50, + style: { ...nodeStyle }, }, { id: '4', @@ -121,7 +126,9 @@ const initialNodes: Node[] = [ type: 'defaultResizer', data: { label: 'Parent', keepAspectRatio: true }, position: { x: 700, y: 0 }, - style: { ...nodeStyle, width: 300, height: 400 }, + width: 300, + height: 400, + style: { ...nodeStyle }, }, { id: '5a', @@ -132,7 +139,9 @@ const initialNodes: Node[] = [ position: { x: 50, y: 50 }, parentNode: '5', extent: 'parent', - style: { ...nodeStyle, width: 50, height: 100 }, + width: 50, + height: 100, + style: { ...nodeStyle }, }, { id: '5b', From 52bdb12cf1eb22ae156770310c1d816f01aacba6 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 13:22:47 +0100 Subject: [PATCH 41/44] chore(changelogs): update --- packages/react/CHANGELOG.md | 1 + packages/svelte/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 0f5f3b92..051fdbd9 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -13,6 +13,7 @@ - `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges - fix bug where users couldn't drag a node after toggle nodes `hidden` attribute - add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr +- fix `NodeResizer` when used in combination with `nodeOrigin` ## 12.0.0-next.9 diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ea7f4181..365f85cb 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -11,6 +11,7 @@ - better cursor defaults for the pane, nodes and edges - add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr +- fix `NodeResizer` when used in combination with `nodeOrigin` ## 0.0.36 From 87d363371b2942277b651bdec537ef1e2b55f3ff Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 19:44:45 +0100 Subject: [PATCH 42/44] fix(react): handle drag for nodes that are initially hidden --- packages/react/src/hooks/useDrag.ts | 34 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/react/src/hooks/useDrag.ts b/packages/react/src/hooks/useDrag.ts index 52d3c6f6..ee26cc57 100644 --- a/packages/react/src/hooks/useDrag.ts +++ b/packages/react/src/hooks/useDrag.ts @@ -31,24 +31,22 @@ export function useDrag({ const xyDrag = useRef(); useEffect(() => { - if (nodeRef?.current) { - xyDrag.current = XYDrag({ - getStoreItems: () => store.getState(), - onNodeMouseDown: (id: string) => { - handleNodeClick({ - id, - store, - nodeRef, - }); - }, - onDragStart: () => { - setDragging(true); - }, - onDragStop: () => { - setDragging(false); - }, - }); - } + xyDrag.current = XYDrag({ + getStoreItems: () => store.getState(), + onNodeMouseDown: (id: string) => { + handleNodeClick({ + id, + store, + nodeRef, + }); + }, + onDragStart: () => { + setDragging(true); + }, + onDragStop: () => { + setDragging(false); + }, + }); }, []); useEffect(() => { From b2800575f6d68ea63f49680e18e56ee2fabc72ae Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 19:47:15 +0100 Subject: [PATCH 43/44] chore(changelogs): update --- packages/svelte/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 948e69eb..df03a57a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -10,6 +10,7 @@ - better cursor defaults for the pane, nodes and edges - add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr +- always re-measure nodes when new nodes get passed ## 0.0.36 From 8306964ad19fca134962e1adc95de85a47c61353 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 20:03:12 +0100 Subject: [PATCH 44/44] chore(packages): bump --- packages/react/package.json | 2 +- packages/svelte/package.json | 2 +- packages/system/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index ddb6d327..d9105c4f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.0.0-next.9", + "version": "12.0.0-next.10", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6fe2bec2..f73908cb 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "0.0.36", + "version": "0.0.37", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/system/package.json b/packages/system/package.json index f08cdd5b..be4eceff 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/system", - "version": "0.0.17", + "version": "0.0.18", "description": "xyflow core system that powers React Flow and Svelte Flow.", "keywords": [ "node-based UI",