refactor(react/svelte): use nodeLookup
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
ReactFlowInstance,
|
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
@@ -18,11 +17,6 @@ import {
|
|||||||
|
|
||||||
import { getNodesAndEdges } from './utils';
|
import { getNodesAndEdges } from './utils';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
|
||||||
reactFlowInstance.fitView();
|
|
||||||
console.log(reactFlowInstance.getNodes());
|
|
||||||
};
|
|
||||||
|
|
||||||
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
|
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
|
||||||
|
|
||||||
const StressFlow = () => {
|
const StressFlow = () => {
|
||||||
@@ -64,11 +58,11 @@ const StressFlow = () => {
|
|||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onInit={onInit}
|
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgeChange}
|
onEdgesChange={onEdgeChange}
|
||||||
minZoom={0.2}
|
minZoom={0.2}
|
||||||
|
fitView
|
||||||
>
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function MiniMap({
|
|||||||
|
|
||||||
const onSvgNodeClick = onNodeClick
|
const onSvgNodeClick = onNodeClick
|
||||||
? useCallback((event: MouseEvent, nodeId: string) => {
|
? useCallback((event: MouseEvent, nodeId: string) => {
|
||||||
const node = store.getState().nodes.find((n) => n.id === nodeId)!;
|
const node = store.getState().nodeLookup.get(nodeId)!;
|
||||||
onNodeClick(event, node);
|
onNodeClick(event, node);
|
||||||
}, [])
|
}, [])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ function ResizeControl({
|
|||||||
|
|
||||||
const dragHandler = drag<HTMLDivElement, unknown>()
|
const dragHandler = drag<HTMLDivElement, unknown>()
|
||||||
.on('start', (event: ResizeDragEvent) => {
|
.on('start', (event: ResizeDragEvent) => {
|
||||||
const { nodesLookup, transform, snapGrid, snapToGrid } = store.getState();
|
const { nodeLookup, transform, snapGrid, snapToGrid } = store.getState();
|
||||||
const node = nodesLookup.get(id);
|
const node = nodeLookup.get(id);
|
||||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||||
|
|
||||||
prevValues.current = {
|
prevValues.current = {
|
||||||
@@ -86,9 +86,9 @@ function ResizeControl({
|
|||||||
onResizeStart?.(event, { ...prevValues.current });
|
onResizeStart?.(event, { ...prevValues.current });
|
||||||
})
|
})
|
||||||
.on('drag', (event: ResizeDragEvent) => {
|
.on('drag', (event: ResizeDragEvent) => {
|
||||||
const { nodesLookup, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
|
const { nodeLookup, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
|
||||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||||
const node = nodesLookup.get(id);
|
const node = nodeLookup.get(id);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
const changes: NodeChange[] = [];
|
const changes: NodeChange[] = [];
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ function NodeToolbar({
|
|||||||
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || ''];
|
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || ''];
|
||||||
|
|
||||||
return nodeIds.reduce<Node[]>((acc, id) => {
|
return nodeIds.reduce<Node[]>((acc, id) => {
|
||||||
const node = state.nodes.find((n) => n.id === id);
|
const node = state.nodeLookup.get(id);
|
||||||
if (node) {
|
if (node) {
|
||||||
acc.push(node);
|
acc.push(node);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as Node[]);
|
}, []);
|
||||||
},
|
},
|
||||||
[nodeId, contextNodeId]
|
[nodeId, contextNodeId]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const ConnectionLine = ({
|
|||||||
const { fromNode, handleId, toX, toY, connectionMode } = useStore(
|
const { fromNode, handleId, toX, toY, connectionMode } = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(s: ReactFlowStore) => ({
|
(s: ReactFlowStore) => ({
|
||||||
fromNode: s.nodesLookup.get(nodeId),
|
fromNode: s.nodeLookup.get(nodeId),
|
||||||
handleId: s.connectionStartHandle?.handleId,
|
handleId: s.connectionStartHandle?.handleId,
|
||||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
||||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { isNumeric } from '@xyflow/system';
|
import { isNumeric } from '@xyflow/system';
|
||||||
|
|
||||||
import type { BaseEdgeProps } from '../../types';
|
import type { BaseEdgeProps } from '../../types';
|
||||||
|
|
||||||
import EdgeText from './EdgeText';
|
import EdgeText from './EdgeText';
|
||||||
|
|
||||||
const BaseEdge = ({
|
const BaseEdge = ({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent } from 'react';
|
import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent, useCallback } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import { getMarkerId, elementSelectionKeys, XYHandle, type Connection, getEdgePosition } from '@xyflow/system';
|
import { getMarkerId, elementSelectionKeys, XYHandle, type Connection, getEdgePosition } from '@xyflow/system';
|
||||||
@@ -53,26 +53,30 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||||
const [updating, setUpdating] = useState<boolean>(false);
|
const [updating, setUpdating] = useState<boolean>(false);
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const edgePosition = useStore(function edgeSelector(state) {
|
const edgePosition = useStore(
|
||||||
const sourceNode = state.nodesLookup.get(source);
|
useCallback(
|
||||||
const targetNode = state.nodesLookup.get(target);
|
(state) => {
|
||||||
|
const sourceNode = state.nodeLookup.get(source);
|
||||||
|
const targetNode = state.nodeLookup.get(target);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
if (!sourceNode || !targetNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos = getEdgePosition({
|
return getEdgePosition({
|
||||||
id,
|
id,
|
||||||
sourceNode,
|
sourceNode,
|
||||||
targetNode,
|
targetNode,
|
||||||
sourceHandle: sourceHandleId || null,
|
sourceHandle: sourceHandleId || null,
|
||||||
targetHandle: targetHandleId || null,
|
targetHandle: targetHandleId || null,
|
||||||
connectionMode: state.connectionMode,
|
connectionMode: state.connectionMode,
|
||||||
onError: state.onError,
|
onError: state.onError,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
return pos;
|
[source, target]
|
||||||
}, shallow);
|
),
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
||||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* The Handle component is used to connect nodes. When the user mousedowns a handle, we start the connection process.
|
||||||
|
* The user can then drag the connection to another handle or node. When the user releases the mouse, we check if the
|
||||||
|
* connection is valid and if so, we call the onConnect callback.
|
||||||
|
*/
|
||||||
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function getMouseHandler(
|
|||||||
return handler === undefined
|
return handler === undefined
|
||||||
? handler
|
? handler
|
||||||
: (event: MouseEvent) => {
|
: (event: MouseEvent) => {
|
||||||
const node = getState().nodes.find((n) => n.id === id)!;
|
const node = getState().nodeLookup.get(id)!;
|
||||||
handler(event, { ...node });
|
handler(event, { ...node });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,8 @@ export function handleNodeClick({
|
|||||||
unselect?: boolean;
|
unselect?: boolean;
|
||||||
nodeRef?: RefObject<HTMLDivElement>;
|
nodeRef?: RefObject<HTMLDivElement>;
|
||||||
}) {
|
}) {
|
||||||
const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodes, onError } = store.getState();
|
const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeLookup, onError } = store.getState();
|
||||||
const node = nodes.find((n) => n.id === id)!;
|
const node = nodeLookup.get(id);
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
onError?.('012', errorMessages['error012'](id));
|
onError?.('012', errorMessages['error012'](id));
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
if (targetPosChanged) {
|
if (targetPosChanged) {
|
||||||
prevTargetPosition.current = targetPosition;
|
prevTargetPosition.current = targetPosition;
|
||||||
}
|
}
|
||||||
store.getState().updateNodeDimensions([{ id, nodeElement: nodeRef.current, forceUpdate: true }]);
|
store.getState().updateNodeDimensions(new Map([[id, { id, nodeElement: nodeRef.current, forceUpdate: true }]]));
|
||||||
}
|
}
|
||||||
}, [id, type, sourcePosition, targetPosition]);
|
}, [id, type, sourcePosition, targetPosition]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* This is a helper component for calling the onSelectionChange listener.
|
||||||
|
* It will only be mounted if the user has passed an onSelectionChange listener
|
||||||
|
* or is using the useOnSelectionChange hook.
|
||||||
|
* @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component?
|
||||||
|
*/
|
||||||
import { memo, useEffect } from 'react';
|
import { memo, useEffect } from 'react';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
@@ -24,8 +30,6 @@ function areEqual(a: SelectorSlice, b: SelectorSlice) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just a helper component for calling the onSelectionChange listener.
|
|
||||||
// @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component?
|
|
||||||
const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) => {
|
const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
|
const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* This component helps us to update the store with the vlues coming from the user.
|
||||||
|
* We distinguish between values we can update directly with `useDirectStoreUpdater` (like `snapGrid`)
|
||||||
|
* and values that have a dedicated setter function in the store (like `setNodes`).
|
||||||
|
*/
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { StoreApi } from 'zustand';
|
import { StoreApi } from 'zustand';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
@@ -70,10 +75,10 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
reset: s.reset,
|
reset: s.reset,
|
||||||
});
|
});
|
||||||
|
|
||||||
function useStoreUpdater<T>(value: T | undefined, setStoreState: (param: T) => void) {
|
function useStoreUpdater<T>(value: T | undefined, setStoreAction: (param: T) => void) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
setStoreState(value);
|
setStoreAction(value);
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ const EdgeRenderer = ({
|
|||||||
children,
|
children,
|
||||||
}: EdgeRendererProps) => {
|
}: EdgeRendererProps) => {
|
||||||
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
||||||
|
// we are grouping edges by zIndex here in order to be able to render them in the correct order
|
||||||
|
// each zIndex gets its own svg element
|
||||||
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
|
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,7 +72,7 @@ const EdgeRenderer = ({
|
|||||||
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
||||||
<svg key={level} style={{ zIndex: level }} className="react-flow__edges react-flow__container">
|
<svg key={level} style={{ zIndex: level }} className="react-flow__edges react-flow__container">
|
||||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
||||||
<g>
|
<>
|
||||||
{edges.map((edge) => {
|
{edges.map((edge) => {
|
||||||
let edgeType = edge.type || 'default';
|
let edgeType = edge.type || 'default';
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ const EdgeRenderer = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</g>
|
</>
|
||||||
</svg>
|
</svg>
|
||||||
))}
|
))}
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getNode = useCallback<Instance.GetNode<NodeData>>((id) => {
|
const getNode = useCallback<Instance.GetNode<NodeData>>((id) => {
|
||||||
return store.getState().nodes.find((n) => n.id === id);
|
return store.getState().nodeLookup.get(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getEdges = useCallback<Instance.GetEdges<EdgeData>>(() => {
|
const getEdges = useCallback<Instance.GetEdges<EdgeData>>(() => {
|
||||||
@@ -181,7 +181,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
nodeOrRect: Node<NodeData> | { id: Node['id'] } | Rect
|
nodeOrRect: Node<NodeData> | { id: Node['id'] } | Rect
|
||||||
): [Rect | null, Node<NodeData> | null | undefined, boolean] => {
|
): [Rect | null, Node<NodeData> | null | undefined, boolean] => {
|
||||||
const isRect = isRectObject(nodeOrRect);
|
const isRect = isRectObject(nodeOrRect);
|
||||||
const node = isRect ? null : store.getState().nodes.find((n) => n.id === nodeOrRect.id);
|
const node = isRect ? null : store.getState().nodeLookup.get(nodeOrRect.id);
|
||||||
|
|
||||||
if (!isRect && !node) {
|
if (!isRect && !node) {
|
||||||
[null, null, isRect];
|
[null, null, isRect];
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ function useUpdateNodeInternals(): UpdateNodeInternals {
|
|||||||
|
|
||||||
return useCallback<UpdateNodeInternals>((id: string | string[]) => {
|
return useCallback<UpdateNodeInternals>((id: string | string[]) => {
|
||||||
const { domNode, updateNodeDimensions } = store.getState();
|
const { domNode, updateNodeDimensions } = store.getState();
|
||||||
|
|
||||||
const updateIds = Array.isArray(id) ? id : [id];
|
const updateIds = Array.isArray(id) ? id : [id];
|
||||||
const updates = updateIds.reduce<NodeDimensionUpdate[]>((res, updateId) => {
|
const updates = new Map<string, NodeDimensionUpdate>();
|
||||||
|
|
||||||
|
updateIds.forEach((updateId) => {
|
||||||
const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement;
|
const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement;
|
||||||
|
|
||||||
if (nodeElement) {
|
if (nodeElement) {
|
||||||
res.push({ id: updateId, nodeElement, forceUpdate: true });
|
updates.set(updateId, { id: updateId, nodeElement, forceUpdate: true });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return res;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => updateNodeDimensions(updates));
|
requestAnimationFrame(() => updateNodeDimensions(updates));
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
|
|||||||
const visibleEdges =
|
const visibleEdges =
|
||||||
onlyRenderVisible && s.width && s.height
|
onlyRenderVisible && s.width && s.height
|
||||||
? s.edges.filter((e) => {
|
? s.edges.filter((e) => {
|
||||||
const sourceNode = s.nodesLookup.get(e.source);
|
const sourceNode = s.nodeLookup.get(e.source);
|
||||||
const targetNode = s.nodesLookup.get(e.target);
|
const targetNode = s.nodeLookup.get(e.target);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sourceNode &&
|
sourceNode &&
|
||||||
@@ -29,7 +29,7 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
|
|||||||
})
|
})
|
||||||
: s.edges;
|
: s.edges;
|
||||||
|
|
||||||
return groupEdgesByZLevel(visibleEdges, s.nodesLookup, elevateEdgesOnSelect);
|
return groupEdgesByZLevel(visibleEdges, s.nodeLookup, elevateEdgesOnSelect);
|
||||||
},
|
},
|
||||||
[onlyRenderVisible, elevateEdgesOnSelect]
|
[onlyRenderVisible, elevateEdgesOnSelect]
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -41,18 +41,19 @@ const createRFStore = ({
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
...getInitialState({ nodes, edges, width, height, fitView }),
|
...getInitialState({ nodes, edges, width, height, fitView }),
|
||||||
setNodes: (nodes: Node[]) => {
|
setNodes: (nodes: Node[]) => {
|
||||||
const { nodesLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin, elevateNodesOnSelect });
|
// Whenver new nodes are set, we need to calculate the absolute positions of the nodes
|
||||||
|
// and update the nodeLookup.
|
||||||
|
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||||
|
|
||||||
set({ nodes: nextNodes });
|
set({ nodes: nextNodes });
|
||||||
},
|
},
|
||||||
getNodes: () => {
|
|
||||||
return get().nodes;
|
|
||||||
},
|
|
||||||
setEdges: (edges: Edge[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { defaultEdgeOptions = {} } = get();
|
const { defaultEdgeOptions = {} } = get();
|
||||||
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
|
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
|
||||||
},
|
},
|
||||||
|
// when the user works with an uncontrolled flow,
|
||||||
|
// we set a flag `hasDefaultNodes` / `hasDefaultEdges`
|
||||||
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
|
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
|
||||||
const hasDefaultNodes = typeof nodes !== 'undefined';
|
const hasDefaultNodes = typeof nodes !== 'undefined';
|
||||||
const hasDefaultEdges = typeof edges !== 'undefined';
|
const hasDefaultEdges = typeof edges !== 'undefined';
|
||||||
@@ -79,12 +80,15 @@ const createRFStore = ({
|
|||||||
|
|
||||||
set(nextState);
|
set(nextState);
|
||||||
},
|
},
|
||||||
|
// Every node gets registerd at a ResizeObserver. Whenever a node
|
||||||
|
// changes its dimensions, this function is called to measure the
|
||||||
|
// new dimensions and update the nodes.
|
||||||
updateNodeDimensions: (updates) => {
|
updateNodeDimensions: (updates) => {
|
||||||
const {
|
const {
|
||||||
onNodesChange,
|
onNodesChange,
|
||||||
fitView,
|
fitView,
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
fitViewOnInit,
|
fitViewOnInit,
|
||||||
fitViewDone,
|
fitViewDone,
|
||||||
fitViewOnInitOptions,
|
fitViewOnInitOptions,
|
||||||
@@ -96,7 +100,7 @@ const createRFStore = ({
|
|||||||
const updatedNodes = updateNodeDimensionsSystem(
|
const updatedNodes = updateNodeDimensionsSystem(
|
||||||
updates,
|
updates,
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
domNode,
|
domNode,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
(id: string, dimensions: Dimensions) => {
|
(id: string, dimensions: Dimensions) => {
|
||||||
@@ -112,8 +116,9 @@ const createRFStore = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextNodes = updateAbsolutePositions(updatedNodes, nodesLookup, nodeOrigin);
|
const nextNodes = updateAbsolutePositions(updatedNodes, nodeLookup, nodeOrigin);
|
||||||
|
|
||||||
|
// we call fitView once initially after all dimensions are set
|
||||||
let nextFitViewDone = fitViewDone;
|
let nextFitViewDone = fitViewDone;
|
||||||
if (!fitViewDone && fitViewOnInit) {
|
if (!fitViewDone && fitViewOnInit) {
|
||||||
nextFitViewDone = fitView(nextNodes, {
|
nextFitViewDone = fitView(nextNodes, {
|
||||||
@@ -122,6 +127,11 @@ const createRFStore = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// here we are cirmumventing the onNodesChange handler
|
||||||
|
// in order to be able to display nodes even if the user
|
||||||
|
// has not provided an onNodesChange handler.
|
||||||
|
// Nodes are only rendered if they have a width and height
|
||||||
|
// attribute which they get from this handler.
|
||||||
set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
|
set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
|
||||||
|
|
||||||
if (changes?.length > 0) {
|
if (changes?.length > 0) {
|
||||||
@@ -148,12 +158,12 @@ const createRFStore = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
triggerNodeChanges: (changes) => {
|
triggerNodeChanges: (changes) => {
|
||||||
const { onNodesChange, nodesLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
const { onNodesChange, nodeLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
|
|
||||||
if (changes?.length) {
|
if (changes?.length) {
|
||||||
if (hasDefaultNodes) {
|
if (hasDefaultNodes) {
|
||||||
const updatedNodes = applyNodeChanges(changes, nodes);
|
const updatedNodes = applyNodeChanges(changes, nodes);
|
||||||
const nextNodes = updateNodes(updatedNodes, nodesLookup, {
|
const nextNodes = updateNodes(updatedNodes, nodeLookup, {
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ const getInitialState = ({
|
|||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
const nodesLookup = new Map<string, Node>();
|
const nodeLookup = new Map<string, Node>();
|
||||||
const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
|
|
||||||
let transform: Transform = [0, 0, 1];
|
let transform: Transform = [0, 0, 1];
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ const getInitialState = ({
|
|||||||
height: 0,
|
height: 0,
|
||||||
transform,
|
transform,
|
||||||
nodes: nextNodes,
|
nodes: nextNodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
edges: edges,
|
edges: edges,
|
||||||
onNodesChange: null,
|
onNodesChange: null,
|
||||||
onEdgesChange: null,
|
onEdgesChange: null,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export type ReactFlowStore = {
|
|||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodesLookup: Map<string, Node>;
|
nodeLookup: Map<string, Node>;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
onNodesChange: OnNodesChange | null;
|
onNodesChange: OnNodesChange | null;
|
||||||
onEdgesChange: OnEdgesChange | null;
|
onEdgesChange: OnEdgesChange | null;
|
||||||
@@ -139,7 +139,6 @@ export type ReactFlowStore = {
|
|||||||
|
|
||||||
export type ReactFlowActions = {
|
export type ReactFlowActions = {
|
||||||
setNodes: (nodes: Node[]) => void;
|
setNodes: (nodes: Node[]) => void;
|
||||||
getNodes: () => Node[];
|
|
||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
|
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
|
||||||
updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
|
updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
|
||||||
|
|||||||
@@ -42,24 +42,39 @@ export function handleParentExpand(res: any[], updateItem: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function applies changes to nodes or edges that are triggered by React Flow internally.
|
||||||
|
// When you drag a node for example, React Flow will send a position change update.
|
||||||
|
// This function then applies the changes and returns the updated elements.
|
||||||
function applyChanges(changes: any[], elements: any[]): any[] {
|
function applyChanges(changes: any[], elements: any[]): any[] {
|
||||||
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
|
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
|
||||||
if (changes.some((c) => c.type === 'reset')) {
|
if (changes.some((c) => c.type === 'reset')) {
|
||||||
return changes.filter((c) => c.type === 'reset').map((c) => c.item);
|
return changes.filter((c) => c.type === 'reset').map((c) => c.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let remainingChanges = changes;
|
||||||
const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item);
|
const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item);
|
||||||
|
|
||||||
return elements.reduce((res: any[], item: any) => {
|
return elements.reduce((res: any[], item: any) => {
|
||||||
const currentChanges = changes.filter((c) => c.id === item.id);
|
const nextChanges: any[] = [];
|
||||||
|
const _remainingChanges: any[] = [];
|
||||||
|
|
||||||
if (currentChanges.length === 0) {
|
remainingChanges.forEach((c) => {
|
||||||
|
if (c.id === item.id) {
|
||||||
|
nextChanges.push(c);
|
||||||
|
} else {
|
||||||
|
_remainingChanges.push(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
remainingChanges = _remainingChanges;
|
||||||
|
|
||||||
|
if (nextChanges.length === 0) {
|
||||||
res.push(item);
|
res.push(item);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItem = { ...item };
|
const updateItem = { ...item };
|
||||||
|
|
||||||
for (const currentChange of currentChanges) {
|
for (const currentChange of nextChanges) {
|
||||||
if (currentChange) {
|
if (currentChange) {
|
||||||
switch (currentChange.type) {
|
switch (currentChange.type) {
|
||||||
case 'select': {
|
case 'select': {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: get(store.nodes),
|
nodes: get(store.nodes),
|
||||||
nodesLookup: get(store.nodesLookup),
|
nodeLookup: get(store.nodeLookup),
|
||||||
edges: get(store.edges),
|
edges: get(store.edges),
|
||||||
nodeExtent: get(store.nodeExtent),
|
nodeExtent: get(store.nodeExtent),
|
||||||
snapGrid: snapGrid ? snapGrid : [0, 0],
|
snapGrid: snapGrid ? snapGrid : [0, 0],
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
|
{id}
|
||||||
d={path}
|
d={path}
|
||||||
class={cc(['svelte-flow__edge-path', className])}
|
class={cc(['svelte-flow__edge-path', className])}
|
||||||
marker-start={markerStart}
|
marker-start={markerStart}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ export function useUpdateNodeInternals(): UpdateNodeInternals {
|
|||||||
// @todo: do we want to add this to system?
|
// @todo: do we want to add this to system?
|
||||||
const updateInternals = (id: string | string[]) => {
|
const updateInternals = (id: string | string[]) => {
|
||||||
const updateIds = Array.isArray(id) ? id : [id];
|
const updateIds = Array.isArray(id) ? id : [id];
|
||||||
const updates = updateIds.reduce<NodeDimensionUpdate[]>((res, updateId) => {
|
const updates = new Map();
|
||||||
|
|
||||||
|
updateIds.forEach((updateId) => {
|
||||||
const nodeElement = get(domNode)?.querySelector(
|
const nodeElement = get(domNode)?.querySelector(
|
||||||
`.svelte-flow__node[data-id="${updateId}"]`
|
`.svelte-flow__node[data-id="${updateId}"]`
|
||||||
) as HTMLDivElement;
|
) as HTMLDivElement;
|
||||||
|
|
||||||
if (nodeElement) {
|
if (nodeElement) {
|
||||||
res.push({ id: updateId, nodeElement, forceUpdate: true });
|
updates.set(updateId, { id: updateId, nodeElement, forceUpdate: true });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return res;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => updateNodeDimensions(updates));
|
requestAnimationFrame(() => updateNodeDimensions(updates));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,15 +56,15 @@ export function getDerivedConnectionProps(
|
|||||||
currentConnection,
|
currentConnection,
|
||||||
store.connectionLineType,
|
store.connectionLineType,
|
||||||
store.connectionMode,
|
store.connectionMode,
|
||||||
store.nodesLookup,
|
store.nodeLookup,
|
||||||
store.viewport
|
store.viewport
|
||||||
],
|
],
|
||||||
([connection, connectionLineType, connectionMode, nodesLookup, viewport]) => {
|
([connection, connectionLineType, connectionMode, nodeLookup, viewport]) => {
|
||||||
if (!connection.connectionStartHandle?.nodeId) {
|
if (!connection.connectionStartHandle?.nodeId) {
|
||||||
return initConnectionProps;
|
return initConnectionProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromNode = nodesLookup.get(connection.connectionStartHandle?.nodeId);
|
const fromNode = nodeLookup.get(connection.connectionStartHandle?.nodeId);
|
||||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||||
const handleBoundsStrict =
|
const handleBoundsStrict =
|
||||||
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
|||||||
[
|
[
|
||||||
store.edges,
|
store.edges,
|
||||||
store.nodes,
|
store.nodes,
|
||||||
store.nodesLookup,
|
store.nodeLookup,
|
||||||
store.onlyRenderVisibleElements,
|
store.onlyRenderVisibleElements,
|
||||||
store.viewport,
|
store.viewport,
|
||||||
store.width,
|
store.width,
|
||||||
store.height
|
store.height
|
||||||
],
|
],
|
||||||
([edges, , nodesLookup, onlyRenderVisibleElements, viewport, width, height]) => {
|
([edges, , nodeLookup, onlyRenderVisibleElements, viewport, width, height]) => {
|
||||||
const visibleEdges =
|
const visibleEdges =
|
||||||
onlyRenderVisibleElements && width && height
|
onlyRenderVisibleElements && width && height
|
||||||
? edges.filter((edge) => {
|
? edges.filter((edge) => {
|
||||||
const sourceNode = nodesLookup.get(edge.source);
|
const sourceNode = nodeLookup.get(edge.source);
|
||||||
const targetNode = nodesLookup.get(edge.target);
|
const targetNode = nodeLookup.get(edge.target);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sourceNode &&
|
sourceNode &&
|
||||||
@@ -41,11 +41,11 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return derived(
|
return derived(
|
||||||
[visibleEdges, store.nodes, store.nodesLookup, store.connectionMode, store.onError],
|
[visibleEdges, store.nodes, store.nodeLookup, store.connectionMode, store.onError],
|
||||||
([visibleEdges, , nodesLookup, connectionMode, onError]) => {
|
([visibleEdges, , nodeLookup, connectionMode, onError]) => {
|
||||||
const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => {
|
const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => {
|
||||||
const sourceNode = nodesLookup.get(edge.source);
|
const sourceNode = nodeLookup.get(edge.source);
|
||||||
const targetNode = nodesLookup.get(edge.target);
|
const targetNode = nodeLookup.get(edge.target);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
if (!sourceNode || !targetNode) {
|
||||||
return res;
|
return res;
|
||||||
@@ -71,7 +71,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
|||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodesLookup, false);
|
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodeLookup, false);
|
||||||
|
|
||||||
return groupedEdges;
|
return groupedEdges;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function createStore({
|
|||||||
const nextNodes = updateNodeDimensionsSystem(
|
const nextNodes = updateNodeDimensionsSystem(
|
||||||
updates,
|
updates,
|
||||||
get(store.nodes),
|
get(store.nodes),
|
||||||
get(store.nodesLookup),
|
get(store.nodeLookup),
|
||||||
get(store.domNode),
|
get(store.domNode),
|
||||||
get(store.nodeOrigin)
|
get(store.nodeOrigin)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ export const getInitialStore = ({
|
|||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const nodesLookup = new Map<string, Node>();
|
const nodeLookup = new Map<string, Node>();
|
||||||
const nextNodes = updateNodes(nodes, nodesLookup, {
|
const nextNodes = updateNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false
|
elevateNodesOnSelect: false
|
||||||
});
|
});
|
||||||
@@ -79,8 +79,8 @@ export const getInitialStore = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
flowId: writable<string | null>(null),
|
flowId: writable<string | null>(null),
|
||||||
nodes: createNodesStore(nextNodes, nodesLookup),
|
nodes: createNodesStore(nextNodes, nodeLookup),
|
||||||
nodesLookup: readable<Map<string, Node>>(nodesLookup),
|
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
||||||
visibleNodes: readable<Node[]>([]),
|
visibleNodes: readable<Node[]>([]),
|
||||||
edges: createEdgesStore(edges),
|
edges: createEdgesStore(edges),
|
||||||
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export type NodeStoreOptions = {
|
|||||||
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
||||||
export const createNodesStore = (
|
export const createNodesStore = (
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodesLookup: Map<string, Node>
|
nodeLookup: Map<string, Node>
|
||||||
): {
|
): {
|
||||||
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
||||||
update: (this: void, updater: Updater<Node[]>) => void;
|
update: (this: void, updater: Updater<Node[]>) => void;
|
||||||
@@ -126,7 +126,7 @@ export const createNodesStore = (
|
|||||||
let elevateNodesOnSelect = true;
|
let elevateNodesOnSelect = true;
|
||||||
|
|
||||||
const _set = (nds: Node[]): Node[] => {
|
const _set = (nds: Node[]): Node[] => {
|
||||||
const nextNodes = updateNodes(nds, nodesLookup, {
|
const nextNodes = updateNodes(nds, nodeLookup, {
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
defaults
|
defaults
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export function getPointerPosition(
|
|||||||
): XYPosition & { xSnapped: number; ySnapped: number } {
|
): XYPosition & { xSnapped: number; ySnapped: number } {
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const pointerPos = pointToRendererPoint({ x, y }, transform);
|
const pointerPos = pointToRendererPoint({ x, y }, transform);
|
||||||
|
|
||||||
const { x: xSnapped, y: ySnapped } = snapToGrid ? snapPosition(pointerPos, snapGrid) : pointerPos;
|
const { x: xSnapped, y: ySnapped } = snapToGrid ? snapPosition(pointerPos, snapGrid) : pointerPos;
|
||||||
|
|
||||||
// we need the snapped position in order to be able to skip unnecessary drag events
|
// we need the snapped position in order to be able to skip unnecessary drag events
|
||||||
@@ -58,6 +57,9 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The handle bounds are calculated relative to the node element.
|
||||||
|
// We store them in the internals object of the node in order to avoid
|
||||||
|
// unnecessary recalculations.
|
||||||
export const getHandleBounds = (
|
export const getHandleBounds = (
|
||||||
selector: string,
|
selector: string,
|
||||||
nodeElement: HTMLDivElement,
|
nodeElement: HTMLDivElement,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type GroupedEdges<EdgeType extends EdgeBase> = {
|
|||||||
|
|
||||||
export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
|
export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
|
||||||
edges: EdgeType[],
|
edges: EdgeType[],
|
||||||
nodesLookup: Map<string, NodeBase>,
|
nodeLookup: Map<string, NodeBase>,
|
||||||
elevateEdgesOnSelect = false
|
elevateEdgesOnSelect = false
|
||||||
): GroupedEdges<EdgeType>[] {
|
): GroupedEdges<EdgeType>[] {
|
||||||
let maxLevel = -1;
|
let maxLevel = -1;
|
||||||
@@ -43,8 +43,8 @@ export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
|
|||||||
let z = hasZIndex ? edge.zIndex! : 0;
|
let z = hasZIndex ? edge.zIndex! : 0;
|
||||||
|
|
||||||
if (elevateEdgesOnSelect) {
|
if (elevateEdgesOnSelect) {
|
||||||
const targetNode = nodesLookup.get(edge.target);
|
const targetNode = nodeLookup.get(edge.target);
|
||||||
const sourceNode = nodesLookup.get(edge.source);
|
const sourceNode = nodeLookup.get(edge.source);
|
||||||
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
|
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
|
||||||
const selectedZIndex = Math.max(
|
const selectedZIndex = Math.max(
|
||||||
sourceNode?.[internalsSymbol]?.z || 0,
|
sourceNode?.[internalsSymbol]?.z || 0,
|
||||||
|
|||||||
@@ -18,21 +18,21 @@ type ParentNodes = Record<string, boolean>;
|
|||||||
|
|
||||||
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
nodesLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, NodeType>,
|
||||||
nodeOrigin: NodeOrigin = [0, 0],
|
nodeOrigin: NodeOrigin = [0, 0],
|
||||||
parentNodes?: ParentNodes
|
parentNodes?: ParentNodes
|
||||||
) {
|
) {
|
||||||
return nodes.map((node) => {
|
return nodes.map((node) => {
|
||||||
if (node.parentNode && !nodesLookup.has(node.parentNode)) {
|
if (node.parentNode && !nodeLookup.has(node.parentNode)) {
|
||||||
throw new Error(`Parent node ${node.parentNode} not found`);
|
throw new Error(`Parent node ${node.parentNode} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.parentNode || parentNodes?.[node.id]) {
|
if (node.parentNode || parentNodes?.[node.id]) {
|
||||||
const parentNode = node.parentNode ? nodesLookup.get(node.parentNode) : null;
|
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : null;
|
||||||
const { x, y, z } = calculateXYZPosition(
|
const { x, y, z } = calculateXYZPosition(
|
||||||
node,
|
node,
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
{
|
{
|
||||||
...node.position,
|
...node.position,
|
||||||
z: node[internalsSymbol]?.z ?? 0,
|
z: node[internalsSymbol]?.z ?? 0,
|
||||||
@@ -64,7 +64,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
|||||||
|
|
||||||
export function updateNodes<NodeType extends NodeBase>(
|
export function updateNodes<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
nodesLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, NodeType>,
|
||||||
options: UpdateNodesOptions<NodeType> = {
|
options: UpdateNodesOptions<NodeType> = {
|
||||||
nodeOrigin: [0, 0] as NodeOrigin,
|
nodeOrigin: [0, 0] as NodeOrigin,
|
||||||
elevateNodesOnSelect: true,
|
elevateNodesOnSelect: true,
|
||||||
@@ -75,7 +75,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||||
|
|
||||||
const nextNodes = nodes.map((n) => {
|
const nextNodes = nodes.map((n) => {
|
||||||
const currentStoreNode = nodesLookup.get(n.id);
|
const currentStoreNode = nodeLookup.get(n.id);
|
||||||
const node: NodeType = {
|
const node: NodeType = {
|
||||||
...options.defaults,
|
...options.defaults,
|
||||||
...n,
|
...n,
|
||||||
@@ -98,12 +98,12 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
nodesLookup.set(node.id, node);
|
nodeLookup.set(node.id, node);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodesWithPositions = updateAbsolutePositions(nextNodes, nodesLookup, options.nodeOrigin, parentNodes);
|
const nodesWithPositions = updateAbsolutePositions(nextNodes, nodeLookup, options.nodeOrigin, parentNodes);
|
||||||
|
|
||||||
return nodesWithPositions;
|
return nodesWithPositions;
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
function calculateXYZPosition<NodeType extends NodeBase>(
|
function calculateXYZPosition<NodeType extends NodeBase>(
|
||||||
node: NodeType,
|
node: NodeType,
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
nodesLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, NodeType>,
|
||||||
result: XYZPosition,
|
result: XYZPosition,
|
||||||
nodeOrigin: NodeOrigin
|
nodeOrigin: NodeOrigin
|
||||||
): XYZPosition {
|
): XYZPosition {
|
||||||
@@ -119,13 +119,13 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNode = nodesLookup.get(node.parentNode)!;
|
const parentNode = nodeLookup.get(node.parentNode)!;
|
||||||
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
||||||
|
|
||||||
return calculateXYZPosition(
|
return calculateXYZPosition(
|
||||||
parentNode,
|
parentNode,
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
{
|
{
|
||||||
x: (result.x ?? 0) + parentNodePosition.x,
|
x: (result.x ?? 0) + parentNodePosition.x,
|
||||||
y: (result.y ?? 0) + parentNodePosition.y,
|
y: (result.y ?? 0) + parentNodePosition.y,
|
||||||
@@ -138,7 +138,7 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
export function updateNodeDimensions(
|
export function updateNodeDimensions(
|
||||||
updates: Map<string, NodeDimensionUpdate>,
|
updates: Map<string, NodeDimensionUpdate>,
|
||||||
nodes: NodeBase[],
|
nodes: NodeBase[],
|
||||||
nodesLookup: Map<string, NodeBase>,
|
nodeLookup: Map<string, NodeBase>,
|
||||||
domNode: HTMLElement | null,
|
domNode: HTMLElement | null,
|
||||||
nodeOrigin?: NodeOrigin,
|
nodeOrigin?: NodeOrigin,
|
||||||
onUpdate?: (id: string, dimensions: Dimensions) => void
|
onUpdate?: (id: string, dimensions: Dimensions) => void
|
||||||
@@ -178,7 +178,7 @@ export function updateNodeDimensions(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
nodesLookup.set(node.id, newNode);
|
nodeLookup.set(node.id, newNode);
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBa
|
|||||||
|
|
||||||
type StoreItems = {
|
type StoreItems = {
|
||||||
nodes: NodeBase[];
|
nodes: NodeBase[];
|
||||||
nodesLookup: Map<string, NodeBase>;
|
nodeLookup: Map<string, NodeBase>;
|
||||||
edges: EdgeBase[];
|
edges: EdgeBase[];
|
||||||
nodeExtent: CoordinateExtent;
|
nodeExtent: CoordinateExtent;
|
||||||
snapGrid: SnapGrid;
|
snapGrid: SnapGrid;
|
||||||
@@ -104,7 +104,7 @@ export function XYDrag({
|
|||||||
function updateNodes({ x, y }: XYPosition) {
|
function updateNodes({ x, y }: XYPosition) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
@@ -169,7 +169,7 @@ export function XYDrag({
|
|||||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
});
|
});
|
||||||
onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||||
onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes);
|
onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes);
|
||||||
@@ -199,7 +199,7 @@ export function XYDrag({
|
|||||||
function startDrag(event: UseDragEvent) {
|
function startDrag(event: UseDragEvent) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
multiSelectionActive,
|
multiSelectionActive,
|
||||||
nodesDraggable,
|
nodesDraggable,
|
||||||
transform,
|
transform,
|
||||||
@@ -214,7 +214,7 @@ export function XYDrag({
|
|||||||
dragStarted = true;
|
dragStarted = true;
|
||||||
|
|
||||||
if ((!selectNodesOnDrag || !isSelectable) && !multiSelectionActive && nodeId) {
|
if ((!selectNodesOnDrag || !isSelectable) && !multiSelectionActive && nodeId) {
|
||||||
if (!nodes.find((n) => n.id === nodeId)?.selected) {
|
if (!nodeLookup.get(nodeId)?.selected) {
|
||||||
// we need to reset selected nodes when selectNodesOnDrag=false
|
// we need to reset selected nodes when selectNodesOnDrag=false
|
||||||
unselectNodesAndEdges();
|
unselectNodesAndEdges();
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ export function XYDrag({
|
|||||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
});
|
});
|
||||||
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||||
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||||
@@ -291,7 +291,7 @@ export function XYDrag({
|
|||||||
cancelAnimationFrame(autoPanId);
|
cancelAnimationFrame(autoPanId);
|
||||||
|
|
||||||
if (dragItems) {
|
if (dragItems) {
|
||||||
const { nodesLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
|
const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
|
||||||
const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
||||||
|
|
||||||
updateNodePositions(dragItems, false, false);
|
updateNodePositions(dragItems, false, false);
|
||||||
@@ -300,7 +300,7 @@ export function XYDrag({
|
|||||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
});
|
});
|
||||||
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||||
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ export function getDragItems<NodeType extends NodeBase>(
|
|||||||
export function getEventHandlerParams<NodeType extends NodeBase>({
|
export function getEventHandlerParams<NodeType extends NodeBase>({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
nodesLookup,
|
nodeLookup,
|
||||||
}: {
|
}: {
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
dragItems: NodeDragItem[];
|
dragItems: NodeDragItem[];
|
||||||
nodesLookup: Map<string, NodeType>;
|
nodeLookup: Map<string, NodeType>;
|
||||||
}): [NodeType, NodeType[]] {
|
}): [NodeType, NodeType[]] {
|
||||||
const extentedDragItems: NodeType[] = dragItems.map((n) => {
|
const extentedDragItems: NodeType[] = dragItems.map((n) => {
|
||||||
const node = nodesLookup.get(n.id)!;
|
const node = nodeLookup.get(n.id)!;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
|||||||
Reference in New Issue
Block a user