refactor(react/svelte): use node lookup for perf improvement
This commit is contained in:
@@ -20,8 +20,6 @@ export function getNodesAndEdges(xElements = 10, yElements = 10): ElementsCollec
|
||||
style: { width: 50, height: 30, fontSize: 11 },
|
||||
data,
|
||||
position,
|
||||
width: 50,
|
||||
height: 30,
|
||||
};
|
||||
initialNodes.push(node);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "turbo run dev --parallel --concurrency 12",
|
||||
"dev:svelte": "turbo run dev --filter=svelte --filter=system",
|
||||
"dev:react": "turbo run dev --filter=react",
|
||||
"dev:react": "turbo run dev --filter=react-examples ",
|
||||
"test:svelte": "pnpm --filter=playwright run test:svelte",
|
||||
"test:svelte:ui": "pnpm --filter=playwright run test:svelte:ui",
|
||||
"test:react": "pnpm --filter=playwright run test:react",
|
||||
|
||||
@@ -65,8 +65,8 @@ function ResizeControl({
|
||||
|
||||
const dragHandler = drag<HTMLDivElement, unknown>()
|
||||
.on('start', (event: ResizeDragEvent) => {
|
||||
const { nodes, transform, snapGrid, snapToGrid } = store.getState();
|
||||
const node = nodes.find((n) => n.id === id);
|
||||
const { nodesLookup, transform, snapGrid, snapToGrid } = store.getState();
|
||||
const node = nodesLookup.get(id);
|
||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
|
||||
prevValues.current = {
|
||||
@@ -86,9 +86,9 @@ function ResizeControl({
|
||||
onResizeStart?.(event, { ...prevValues.current });
|
||||
})
|
||||
.on('drag', (event: ResizeDragEvent) => {
|
||||
const { nodes, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
|
||||
const { nodesLookup, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
|
||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
const node = nodes.find((n) => n.id === id);
|
||||
const node = nodesLookup.get(id);
|
||||
|
||||
if (node) {
|
||||
const changes: NodeChange[] = [];
|
||||
|
||||
@@ -43,7 +43,7 @@ const ConnectionLine = ({
|
||||
const { fromNode, handleId, toX, toY, connectionMode } = useStore(
|
||||
useCallback(
|
||||
(s: ReactFlowStore) => ({
|
||||
fromNode: s.nodes.find((n) => n.id === nodeId),
|
||||
fromNode: s.nodesLookup.get(nodeId),
|
||||
handleId: s.connectionStartHandle?.handleId,
|
||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
||||
|
||||
@@ -53,9 +53,9 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const store = useStoreApi();
|
||||
const edgePosition = useStore((state) => {
|
||||
const sourceNode = state.nodes.find((n) => n.id === source);
|
||||
const targetNode = state.nodes.find((n) => n.id === target);
|
||||
const edgePosition = useStore(function edgeSelector(state) {
|
||||
const sourceNode = state.nodesLookup.get(source);
|
||||
const targetNode = state.nodesLookup.get(target);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
|
||||
@@ -48,11 +48,16 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
const updates = entries.map((entry: ResizeObserverEntry) => ({
|
||||
id: entry.target.getAttribute('data-id') as string,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true,
|
||||
}));
|
||||
const updates = new Map();
|
||||
|
||||
entries.forEach((entry: ResizeObserverEntry) => {
|
||||
const id = entry.target.getAttribute('data-id') as string;
|
||||
updates.set(id, {
|
||||
id,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
updateNodeDimensions(updates);
|
||||
});
|
||||
|
||||
@@ -12,8 +12,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
|
||||
const visibleEdges =
|
||||
onlyRenderVisible && s.width && s.height
|
||||
? s.edges.filter((e) => {
|
||||
const sourceNode = s.nodes.find((n) => n.id === e.source);
|
||||
const targetNode = s.nodes.find((n) => n.id === e.target);
|
||||
const sourceNode = s.nodesLookup.get(e.source);
|
||||
const targetNode = s.nodesLookup.get(e.target);
|
||||
|
||||
return (
|
||||
sourceNode &&
|
||||
@@ -29,7 +29,7 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
|
||||
})
|
||||
: s.edges;
|
||||
|
||||
return groupEdgesByZLevel(visibleEdges, s.nodes, elevateEdgesOnSelect);
|
||||
return groupEdgesByZLevel(visibleEdges, s.nodesLookup, elevateEdgesOnSelect);
|
||||
},
|
||||
[onlyRenderVisible, elevateEdgesOnSelect]
|
||||
),
|
||||
|
||||
@@ -41,8 +41,8 @@ const createRFStore = ({
|
||||
(set, get) => ({
|
||||
...getInitialState({ nodes, edges, width, height, fitView }),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
const nextNodes = updateNodes(nodes, storeNodes, { nodeOrigin, elevateNodesOnSelect });
|
||||
const { nodesLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||
|
||||
set({ nodes: nextNodes });
|
||||
},
|
||||
@@ -68,7 +68,7 @@ const createRFStore = ({
|
||||
};
|
||||
|
||||
if (hasDefaultNodes) {
|
||||
nextState.nodes = updateNodes(nodes, [], {
|
||||
nextState.nodes = updateNodes(nodes, new Map(), {
|
||||
nodeOrigin: get().nodeOrigin,
|
||||
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
||||
});
|
||||
@@ -80,13 +80,23 @@ const createRFStore = ({
|
||||
set(nextState);
|
||||
},
|
||||
updateNodeDimensions: (updates) => {
|
||||
const { onNodesChange, fitView, nodes, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin } =
|
||||
get();
|
||||
const {
|
||||
onNodesChange,
|
||||
fitView,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
fitViewOnInit,
|
||||
fitViewDone,
|
||||
fitViewOnInitOptions,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
} = get();
|
||||
const changes: NodeDimensionChange[] = [];
|
||||
|
||||
const updatedNodes = updateNodeDimensionsSystem(
|
||||
updates,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
(id: string, dimensions: Dimensions) => {
|
||||
@@ -102,7 +112,7 @@ const createRFStore = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const nextNodes = updateAbsolutePositions(updatedNodes, nodeOrigin);
|
||||
const nextNodes = updateAbsolutePositions(updatedNodes, nodesLookup, nodeOrigin);
|
||||
|
||||
let nextFitViewDone = fitViewDone;
|
||||
if (!fitViewDone && fitViewOnInit) {
|
||||
@@ -138,12 +148,12 @@ const createRFStore = ({
|
||||
},
|
||||
|
||||
triggerNodeChanges: (changes) => {
|
||||
const { onNodesChange, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
const { onNodesChange, nodesLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
|
||||
if (changes?.length) {
|
||||
if (hasDefaultNodes) {
|
||||
const updatedNodes = applyNodeChanges(changes, nodes);
|
||||
const nextNodes = updateNodes(updatedNodes, nodes, {
|
||||
const nextNodes = updateNodes(updatedNodes, nodesLookup, {
|
||||
nodeOrigin,
|
||||
elevateNodesOnSelect,
|
||||
});
|
||||
|
||||
@@ -22,7 +22,8 @@ const getInitialState = ({
|
||||
height?: number;
|
||||
fitView?: boolean;
|
||||
} = {}): ReactFlowStore => {
|
||||
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||
const nodesLookup = new Map<string, Node>();
|
||||
const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||
|
||||
let transform: Transform = [0, 0, 1];
|
||||
|
||||
@@ -43,6 +44,7 @@ const getInitialState = ({
|
||||
height: 0,
|
||||
transform,
|
||||
nodes: nextNodes,
|
||||
nodesLookup,
|
||||
edges: edges,
|
||||
onNodesChange: null,
|
||||
onEdgesChange: null,
|
||||
|
||||
@@ -46,6 +46,7 @@ export type ReactFlowStore = {
|
||||
height: number;
|
||||
transform: Transform;
|
||||
nodes: Node[];
|
||||
nodesLookup: Map<string, Node>;
|
||||
edges: Edge[];
|
||||
onNodesChange: OnNodesChange | null;
|
||||
onEdgesChange: OnEdgesChange | null;
|
||||
@@ -141,7 +142,7 @@ export type ReactFlowActions = {
|
||||
getNodes: () => Node[];
|
||||
setEdges: (edges: Edge[]) => void;
|
||||
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
|
||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||
updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
|
||||
updateNodePositions: UpdateNodePositions;
|
||||
resetSelectedElements: () => void;
|
||||
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void;
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
|
||||
|
||||
return {
|
||||
nodes: get(store.nodes),
|
||||
nodesLookup: get(store.nodesLookup),
|
||||
edges: get(store.edges),
|
||||
nodeExtent: get(store.nodeExtent),
|
||||
snapGrid: snapGrid ? snapGrid : [0, 0],
|
||||
|
||||
@@ -17,11 +17,18 @@
|
||||
typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
: new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
const updates = entries.map((entry: ResizeObserverEntry) => ({
|
||||
id: entry.target.getAttribute('data-id') as string,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true
|
||||
}));
|
||||
const updates = new Map();
|
||||
|
||||
entries.forEach((entry: ResizeObserverEntry) => {
|
||||
const id = entry.target.getAttribute('data-id') as string;
|
||||
|
||||
updates.set(id, {
|
||||
id,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true
|
||||
});
|
||||
});
|
||||
|
||||
updateNodeDimensions(updates);
|
||||
});
|
||||
|
||||
|
||||
@@ -56,15 +56,15 @@ export function getDerivedConnectionProps(
|
||||
currentConnection,
|
||||
store.connectionLineType,
|
||||
store.connectionMode,
|
||||
store.nodes,
|
||||
store.nodesLookup,
|
||||
store.viewport
|
||||
],
|
||||
([connection, connectionLineType, connectionMode, nodes, viewport]) => {
|
||||
([connection, connectionLineType, connectionMode, nodesLookup, viewport]) => {
|
||||
if (!connection.connectionStartHandle?.nodeId) {
|
||||
return initConnectionProps;
|
||||
}
|
||||
|
||||
const fromNode = nodes.find((n) => n.id === connection.connectionStartHandle?.nodeId);
|
||||
const fromNode = nodesLookup.get(connection.connectionStartHandle?.nodeId);
|
||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||
const handleBoundsStrict =
|
||||
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
||||
|
||||
@@ -9,17 +9,18 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
||||
[
|
||||
store.edges,
|
||||
store.nodes,
|
||||
store.nodesLookup,
|
||||
store.onlyRenderVisibleElements,
|
||||
store.viewport,
|
||||
store.width,
|
||||
store.height
|
||||
],
|
||||
([edges, nodes, onlyRenderVisibleElements, viewport, width, height]) => {
|
||||
([edges, , nodesLookup, onlyRenderVisibleElements, viewport, width, height]) => {
|
||||
const visibleEdges =
|
||||
onlyRenderVisibleElements && width && height
|
||||
? edges.filter((edge) => {
|
||||
const sourceNode = nodes.find((node) => node.id === edge.source);
|
||||
const targetNode = nodes.find((node) => node.id === edge.target);
|
||||
const sourceNode = nodesLookup.get(edge.source);
|
||||
const targetNode = nodesLookup.get(edge.target);
|
||||
|
||||
return (
|
||||
sourceNode &&
|
||||
@@ -40,11 +41,11 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
||||
);
|
||||
|
||||
return derived(
|
||||
[visibleEdges, store.nodes, store.connectionMode, store.onError],
|
||||
([visibleEdges, nodes, connectionMode, onError]) => {
|
||||
[visibleEdges, store.nodes, store.nodesLookup, store.connectionMode, store.onError],
|
||||
([visibleEdges, , nodesLookup, connectionMode, onError]) => {
|
||||
const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => {
|
||||
const sourceNode = nodes.find((node) => node.id === edge.source);
|
||||
const targetNode = nodes.find((node) => node.id === edge.target);
|
||||
const sourceNode = nodesLookup.get(edge.source);
|
||||
const targetNode = nodesLookup.get(edge.target);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return res;
|
||||
@@ -70,7 +71,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodes, false);
|
||||
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodesLookup, false);
|
||||
|
||||
return groupedEdges;
|
||||
}
|
||||
|
||||
@@ -86,10 +86,11 @@ export function createStore({
|
||||
});
|
||||
};
|
||||
|
||||
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
|
||||
function updateNodeDimensions(updates: Map<string, NodeDimensionUpdate>) {
|
||||
const nextNodes = updateNodeDimensionsSystem(
|
||||
updates,
|
||||
get(store.nodes),
|
||||
get(store.nodesLookup),
|
||||
get(store.domNode),
|
||||
get(store.nodeOrigin)
|
||||
);
|
||||
|
||||
@@ -59,7 +59,11 @@ export const getInitialStore = ({
|
||||
height?: number;
|
||||
fitView?: boolean;
|
||||
}) => {
|
||||
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||
const nodesLookup = new Map<string, Node>();
|
||||
const nextNodes = updateNodes(nodes, nodesLookup, {
|
||||
nodeOrigin: [0, 0],
|
||||
elevateNodesOnSelect: false
|
||||
});
|
||||
|
||||
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
|
||||
@@ -75,7 +79,8 @@ export const getInitialStore = ({
|
||||
|
||||
return {
|
||||
flowId: writable<string | null>(null),
|
||||
nodes: createNodesStore(nextNodes),
|
||||
nodes: createNodesStore(nextNodes, nodesLookup),
|
||||
nodesLookup: readable<Map<string, Node>>(nodesLookup),
|
||||
visibleNodes: readable<Node[]>([]),
|
||||
edges: createEdgesStore(edges),
|
||||
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
||||
|
||||
@@ -27,7 +27,7 @@ export type SvelteFlowStoreActions = {
|
||||
setTranslateExtent: (extent: CoordinateExtent) => void;
|
||||
fitView: (options?: FitViewOptions) => boolean;
|
||||
updateNodePositions: UpdateNodePositions;
|
||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||
updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
|
||||
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
|
||||
addSelectedNodes: (ids: string[]) => void;
|
||||
addSelectedEdges: (ids: string[]) => void;
|
||||
|
||||
@@ -111,7 +111,8 @@ export type NodeStoreOptions = {
|
||||
// we are creating a custom store for the internals nodes in order to update the zIndex and positionAbsolute.
|
||||
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
||||
export const createNodesStore = (
|
||||
nodes: Node[]
|
||||
nodes: Node[],
|
||||
nodesLookup: Map<string, Node>
|
||||
): {
|
||||
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
||||
update: (this: void, updater: Updater<Node[]>) => void;
|
||||
@@ -125,7 +126,7 @@ export const createNodesStore = (
|
||||
let elevateNodesOnSelect = true;
|
||||
|
||||
const _set = (nds: Node[]): Node[] => {
|
||||
const nextNodes = updateNodes(nds, value, {
|
||||
const nextNodes = updateNodes(nds, nodesLookup, {
|
||||
elevateNodesOnSelect,
|
||||
defaults
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export type GroupedEdges<EdgeType extends EdgeBase> = {
|
||||
|
||||
export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
|
||||
edges: EdgeType[],
|
||||
nodes: NodeBase[],
|
||||
nodesLookup: Map<string, NodeBase>,
|
||||
elevateEdgesOnSelect = false
|
||||
): GroupedEdges<EdgeType>[] {
|
||||
let maxLevel = -1;
|
||||
@@ -43,8 +43,8 @@ export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
|
||||
let z = hasZIndex ? edge.zIndex! : 0;
|
||||
|
||||
if (elevateEdgesOnSelect) {
|
||||
const targetNode = nodes.find((n) => n.id === edge.target);
|
||||
const sourceNode = nodes.find((n) => n.id === edge.source);
|
||||
const targetNode = nodesLookup.get(edge.target);
|
||||
const sourceNode = nodesLookup.get(edge.source);
|
||||
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
|
||||
const selectedZIndex = Math.max(
|
||||
sourceNode?.[internalsSymbol]?.z || 0,
|
||||
|
||||
@@ -18,19 +18,21 @@ type ParentNodes = Record<string, boolean>;
|
||||
|
||||
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
||||
nodes: NodeType[],
|
||||
nodesLookup: Map<string, NodeType>,
|
||||
nodeOrigin: NodeOrigin = [0, 0],
|
||||
parentNodes?: ParentNodes
|
||||
) {
|
||||
return nodes.map((node) => {
|
||||
if (node.parentNode && !nodes.find((n) => n.id === node.parentNode)) {
|
||||
if (node.parentNode && !nodesLookup.has(node.parentNode)) {
|
||||
throw new Error(`Parent node ${node.parentNode} not found`);
|
||||
}
|
||||
|
||||
if (node.parentNode || parentNodes?.[node.id]) {
|
||||
const parentNode = node.parentNode ? nodes.find((n) => n.id === node.parentNode) : null;
|
||||
const parentNode = node.parentNode ? nodesLookup.get(node.parentNode) : null;
|
||||
const { x, y, z } = calculateXYZPosition(
|
||||
node,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
{
|
||||
...node.position,
|
||||
z: node[internalsSymbol]?.z ?? 0,
|
||||
@@ -62,7 +64,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
||||
|
||||
export function updateNodes<NodeType extends NodeBase>(
|
||||
nodes: NodeType[],
|
||||
storeNodes: NodeType[],
|
||||
nodesLookup: Map<string, NodeType>,
|
||||
options: UpdateNodesOptions<NodeType> = {
|
||||
nodeOrigin: [0, 0] as NodeOrigin,
|
||||
elevateNodesOnSelect: true,
|
||||
@@ -73,7 +75,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||
|
||||
const nextNodes = nodes.map((n) => {
|
||||
const currentStoreNode = storeNodes.find((storeNode) => n.id === storeNode.id);
|
||||
const currentStoreNode = nodesLookup.get(n.id);
|
||||
const node: NodeType = {
|
||||
...options.defaults,
|
||||
...n,
|
||||
@@ -96,10 +98,12 @@ export function updateNodes<NodeType extends NodeBase>(
|
||||
},
|
||||
});
|
||||
|
||||
nodesLookup.set(node.id, node);
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
const nodesWithPositions = updateAbsolutePositions(nextNodes, options.nodeOrigin, parentNodes);
|
||||
const nodesWithPositions = updateAbsolutePositions(nextNodes, nodesLookup, options.nodeOrigin, parentNodes);
|
||||
|
||||
return nodesWithPositions;
|
||||
}
|
||||
@@ -107,6 +111,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
||||
function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
node: NodeType,
|
||||
nodes: NodeType[],
|
||||
nodesLookup: Map<string, NodeType>,
|
||||
result: XYZPosition,
|
||||
nodeOrigin: NodeOrigin
|
||||
): XYZPosition {
|
||||
@@ -114,12 +119,13 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
return result;
|
||||
}
|
||||
|
||||
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
|
||||
const parentNode = nodesLookup.get(node.parentNode)!;
|
||||
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
||||
|
||||
return calculateXYZPosition(
|
||||
parentNode,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
{
|
||||
x: (result.x ?? 0) + parentNodePosition.x,
|
||||
y: (result.y ?? 0) + parentNodePosition.y,
|
||||
@@ -130,8 +136,9 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
}
|
||||
|
||||
export function updateNodeDimensions(
|
||||
updates: NodeDimensionUpdate[],
|
||||
updates: Map<string, NodeDimensionUpdate>,
|
||||
nodes: NodeBase[],
|
||||
nodesLookup: Map<string, NodeBase>,
|
||||
domNode: HTMLElement | null,
|
||||
nodeOrigin?: NodeOrigin,
|
||||
onUpdate?: (id: string, dimensions: Dimensions) => void
|
||||
@@ -146,7 +153,8 @@ export function updateNodeDimensions(
|
||||
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
||||
|
||||
const nextNodes = nodes.map((node) => {
|
||||
const update = updates.find((u) => u.id === node.id);
|
||||
const update = updates.get(node.id);
|
||||
|
||||
if (update) {
|
||||
const dimensions = getDimensions(update.nodeElement);
|
||||
const doUpdate = !!(
|
||||
@@ -158,7 +166,7 @@ export function updateNodeDimensions(
|
||||
if (doUpdate) {
|
||||
onUpdate?.(node.id, dimensions);
|
||||
|
||||
return {
|
||||
const newNode = {
|
||||
...node,
|
||||
...dimensions,
|
||||
[internalsSymbol]: {
|
||||
@@ -169,6 +177,10 @@ export function updateNodeDimensions(
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
nodesLookup.set(node.id, newNode);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBa
|
||||
|
||||
type StoreItems = {
|
||||
nodes: NodeBase[];
|
||||
nodesLookup: Map<string, NodeBase>;
|
||||
edges: EdgeBase[];
|
||||
nodeExtent: CoordinateExtent;
|
||||
snapGrid: SnapGrid;
|
||||
@@ -103,6 +104,7 @@ export function XYDrag({
|
||||
function updateNodes({ x, y }: XYPosition) {
|
||||
const {
|
||||
nodes,
|
||||
nodesLookup,
|
||||
nodeExtent,
|
||||
snapGrid,
|
||||
snapToGrid,
|
||||
@@ -163,11 +165,11 @@ export function XYDrag({
|
||||
updateNodePositions(dragItems, true, true);
|
||||
const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
|
||||
|
||||
if (dragEvent) {
|
||||
if (dragEvent && (onDrag || onNodeOrSelectionDrag)) {
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
});
|
||||
onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes);
|
||||
@@ -197,6 +199,7 @@ export function XYDrag({
|
||||
function startDrag(event: UseDragEvent) {
|
||||
const {
|
||||
nodes,
|
||||
nodesLookup,
|
||||
multiSelectionActive,
|
||||
nodesDraggable,
|
||||
transform,
|
||||
@@ -227,11 +230,11 @@ export function XYDrag({
|
||||
|
||||
const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
|
||||
|
||||
if (dragItems) {
|
||||
if (dragItems && (onDragStart || onNodeOrSelectionDragStart)) {
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
});
|
||||
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||
@@ -288,18 +291,20 @@ export function XYDrag({
|
||||
cancelAnimationFrame(autoPanId);
|
||||
|
||||
if (dragItems) {
|
||||
const { nodes, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
|
||||
const { nodesLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
|
||||
const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
||||
|
||||
updateNodePositions(dragItems, false, false);
|
||||
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
});
|
||||
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||
if (onDragStop || onNodeOrSelectionDragStop) {
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodesLookup,
|
||||
});
|
||||
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((event: MouseEvent) => {
|
||||
|
||||
@@ -75,14 +75,14 @@ export function getDragItems<NodeType extends NodeBase>(
|
||||
export function getEventHandlerParams<NodeType extends NodeBase>({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
nodesLookup,
|
||||
}: {
|
||||
nodeId?: string;
|
||||
dragItems: NodeDragItem[];
|
||||
nodes: NodeType[];
|
||||
nodesLookup: Map<string, NodeType>;
|
||||
}): [NodeType, NodeType[]] {
|
||||
const extentedDragItems: NodeType[] = dragItems.map((n) => {
|
||||
const node = nodes.find((node) => node.id === n.id)!;
|
||||
const node = nodesLookup.get(n.id)!;
|
||||
|
||||
return {
|
||||
...node,
|
||||
|
||||
Reference in New Issue
Block a user