refactor(react): separate user nodes and indernal nodes
This commit is contained in:
@@ -5,8 +5,8 @@ import { getEdgeParams } from './utils';
|
|||||||
|
|
||||||
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
|
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
|
||||||
const { sourceNode, targetNode } = useStore((s) => {
|
const { sourceNode, targetNode } = useStore((s) => {
|
||||||
const sourceNode = s.nodes.find((n) => n.id === source);
|
const sourceNode = s.nodeLookup.get(source);
|
||||||
const targetNode = s.nodes.find((n) => n.id === target);
|
const targetNode = s.nodeLookup.get(target);
|
||||||
|
|
||||||
return { sourceNode, targetNode };
|
return { sourceNode, targetNode };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Position, XYPosition, Node, Edge } from '@xyflow/react';
|
import { Position, XYPosition, Node, Edge, InternalNode } from '@xyflow/react';
|
||||||
|
|
||||||
// this helper function returns the intersection point
|
// this helper function returns the intersection point
|
||||||
// of the line between the center of the intersectionNode and the target node
|
// of the line between the center of the intersectionNode and the target node
|
||||||
@@ -56,7 +56,7 @@ function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
|
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
|
||||||
export function getEdgeParams(source: Node, target: Node) {
|
export function getEdgeParams(source: InternalNode, target: InternalNode) {
|
||||||
const sourceIntersectionPoint = getNodeIntersection(source, target);
|
const sourceIntersectionPoint = getNodeIntersection(source, target);
|
||||||
const targetIntersectionPoint = getNodeIntersection(target, source);
|
const targetIntersectionPoint = getNodeIntersection(target, source);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { memo, useEffect, useRef, type MouseEvent, useCallback, CSSProperties } from 'react';
|
import { memo, useEffect, useRef, type MouseEvent, useCallback, CSSProperties } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import { getNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system';
|
import { getInternalNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||||
import { Panel } from '../../components/Panel';
|
import { Panel } from '../../components/Panel';
|
||||||
@@ -26,7 +26,9 @@ const selector = (s: ReactFlowState) => {
|
|||||||
return {
|
return {
|
||||||
viewBB,
|
viewBB,
|
||||||
boundingRect:
|
boundingRect:
|
||||||
s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, { nodeOrigin: s.nodeOrigin }), viewBB) : viewBB,
|
s.nodeLookup.size > 0
|
||||||
|
? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { nodeOrigin: s.nodeOrigin }), viewBB)
|
||||||
|
: viewBB,
|
||||||
rfId: s.rfId,
|
rfId: s.rfId,
|
||||||
nodeOrigin: s.nodeOrigin,
|
nodeOrigin: s.nodeOrigin,
|
||||||
panZoom: s.panZoom,
|
panZoom: s.panZoom,
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { useCallback, CSSProperties } from 'react';
|
import { useCallback, CSSProperties } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import { getNodesBounds, Rect, Position, internalsSymbol, getNodeToolbarTransform } from '@xyflow/system';
|
import { Rect, Position, getNodeToolbarTransform, getNodesBounds } from '@xyflow/system';
|
||||||
|
|
||||||
import { Node, ReactFlowState } from '../../types';
|
import { InternalNode, ReactFlowState } from '../../types';
|
||||||
import { useStore } from '../../hooks/useStore';
|
import { useStore } from '../../hooks/useStore';
|
||||||
import { useNodeId } from '../../contexts/NodeIdContext';
|
import { useNodeId } from '../../contexts/NodeIdContext';
|
||||||
import { NodeToolbarPortal } from './NodeToolbarPortal';
|
import { NodeToolbarPortal } from './NodeToolbarPortal';
|
||||||
import type { NodeToolbarProps } from './types';
|
import type { NodeToolbarProps } from './types';
|
||||||
|
|
||||||
const nodeEqualityFn = (a?: Node, b?: Node) =>
|
const nodeEqualityFn = (a?: InternalNode, b?: InternalNode) =>
|
||||||
a?.computed?.positionAbsolute?.x !== b?.computed?.positionAbsolute?.x ||
|
a?.internals.positionAbsolute.x !== b?.internals.positionAbsolute.x ||
|
||||||
a?.computed?.positionAbsolute?.y !== b?.computed?.positionAbsolute?.y ||
|
a?.internals.positionAbsolute.y !== b?.internals.positionAbsolute.y ||
|
||||||
a?.computed?.width !== b?.computed?.width ||
|
a?.computed.width !== b?.computed.width ||
|
||||||
a?.computed?.height !== b?.computed?.height ||
|
a?.computed.height !== b?.computed.height ||
|
||||||
a?.selected !== b?.selected ||
|
a?.selected !== b?.selected ||
|
||||||
a?.[internalsSymbol]?.z !== b?.[internalsSymbol]?.z;
|
a?.internals.z !== b?.internals.z;
|
||||||
|
|
||||||
const nodesEqualityFn = (a: Node[], b: Node[]) => {
|
const nodesEqualityFn = (a: InternalNode[], b: InternalNode[]) => {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -49,10 +49,10 @@ export function NodeToolbar({
|
|||||||
const contextNodeId = useNodeId();
|
const contextNodeId = useNodeId();
|
||||||
|
|
||||||
const nodesSelector = useCallback(
|
const nodesSelector = useCallback(
|
||||||
(state: ReactFlowState): Node[] => {
|
(state: ReactFlowState): InternalNode[] => {
|
||||||
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<InternalNode[]>((acc, id) => {
|
||||||
const node = state.nodeLookup.get(id);
|
const node = state.nodeLookup.get(id);
|
||||||
if (node) {
|
if (node) {
|
||||||
acc.push(node);
|
acc.push(node);
|
||||||
@@ -74,7 +74,7 @@ export function NodeToolbar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
|
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
|
||||||
const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1));
|
const zIndex: number = Math.max(...nodes.map((node) => (node.internals?.z || 1) + 1));
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CSSProperties, useCallback } from 'react';
|
|||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import {
|
import {
|
||||||
internalsSymbol,
|
|
||||||
Position,
|
Position,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
@@ -53,7 +52,7 @@ const ConnectionLine = ({
|
|||||||
),
|
),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
const fromHandleBounds = fromNode?.internals?.handleBounds;
|
||||||
let handleBounds = fromHandleBounds?.[handleType];
|
let handleBounds = fromHandleBounds?.[handleType];
|
||||||
|
|
||||||
if (connectionMode === ConnectionMode.Loose) {
|
if (connectionMode === ConnectionMode.Loose) {
|
||||||
@@ -65,10 +64,10 @@ const ConnectionLine = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
||||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.computed?.width ?? 0) / 2;
|
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.computed.width ?? 0) / 2;
|
||||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.computed?.height ?? 0;
|
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.computed.height ?? 0;
|
||||||
const fromX = (fromNode.computed?.positionAbsolute?.x ?? 0) + fromHandleX;
|
const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX;
|
||||||
const fromY = (fromNode.computed?.positionAbsolute?.y ?? 0) + fromHandleY;
|
const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY;
|
||||||
const fromPosition = fromHandle?.position;
|
const fromPosition = fromHandle?.position;
|
||||||
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
onConnectStart,
|
onConnectStart,
|
||||||
onConnectEnd,
|
onConnectEnd,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
nodes,
|
nodeLookup,
|
||||||
rfId: flowId,
|
rfId: flowId,
|
||||||
panBy,
|
panBy,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
@@ -82,7 +82,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
domNode,
|
domNode,
|
||||||
handleId,
|
handleId,
|
||||||
nodeId,
|
nodeId,
|
||||||
nodes,
|
nodeLookup,
|
||||||
isTarget,
|
isTarget,
|
||||||
edgeUpdaterType: handleType,
|
edgeUpdaterType: handleType,
|
||||||
lib,
|
lib,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ function HandleComponent(
|
|||||||
connectionMode: currentStore.connectionMode,
|
connectionMode: currentStore.connectionMode,
|
||||||
connectionRadius: currentStore.connectionRadius,
|
connectionRadius: currentStore.connectionRadius,
|
||||||
domNode: currentStore.domNode,
|
domNode: currentStore.domNode,
|
||||||
nodes: currentStore.nodes,
|
nodeLookup: currentStore.nodeLookup,
|
||||||
lib: currentStore.lib,
|
lib: currentStore.lib,
|
||||||
isTarget,
|
isTarget,
|
||||||
handleId,
|
handleId,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
errorMessages,
|
errorMessages,
|
||||||
getNodeDimensions,
|
getNodeDimensions,
|
||||||
getPositionWithOrigin,
|
getPositionWithOrigin,
|
||||||
internalsSymbol,
|
|
||||||
isInputDOMNode,
|
isInputDOMNode,
|
||||||
nodeHasDimensions,
|
nodeHasDimensions,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
@@ -19,7 +18,7 @@ import { useDrag } from '../../hooks/useDrag';
|
|||||||
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
|
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
|
||||||
import { handleNodeClick } from '../Nodes/utils';
|
import { handleNodeClick } from '../Nodes/utils';
|
||||||
import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from './utils';
|
import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from './utils';
|
||||||
import type { Node, NodeWrapperProps } from '../../types';
|
import type { InternalNode, Node, NodeWrapperProps } from '../../types';
|
||||||
|
|
||||||
export function NodeWrapper<NodeType extends Node>({
|
export function NodeWrapper<NodeType extends Node>({
|
||||||
id,
|
id,
|
||||||
@@ -44,11 +43,11 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
onError,
|
onError,
|
||||||
}: NodeWrapperProps<NodeType>) {
|
}: NodeWrapperProps<NodeType>) {
|
||||||
const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
|
const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
|
||||||
const node = s.nodeLookup.get(id)! as NodeType;
|
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
|
||||||
|
|
||||||
const positionAbsolute = nodeExtent
|
const positionAbsolute = nodeExtent
|
||||||
? clampPosition(node.computed?.positionAbsolute, nodeExtent)
|
? clampPosition(node.internals.positionAbsolute, nodeExtent)
|
||||||
: node.computed?.positionAbsolute || { x: 0, y: 0 };
|
: node.internals.positionAbsolute || { x: 0, y: 0 };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
@@ -56,8 +55,8 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
// so we we need to force a re-render when some change
|
// so we we need to force a re-render when some change
|
||||||
positionAbsoluteX: positionAbsolute.x,
|
positionAbsoluteX: positionAbsolute.x,
|
||||||
positionAbsoluteY: positionAbsolute.y,
|
positionAbsoluteY: positionAbsolute.y,
|
||||||
zIndex: node[internalsSymbol]?.z ?? 0,
|
zIndex: node.internals.z,
|
||||||
isParent: !!node[internalsSymbol]?.isParent,
|
isParent: node.internals.isParent,
|
||||||
};
|
};
|
||||||
}, shallow);
|
}, shallow);
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
const nodeDimensions = getNodeDimensions(node);
|
const nodeDimensions = getNodeDimensions(node);
|
||||||
const inlineDimensions = getNodeInlineStyleDimensions(node);
|
const inlineDimensions = getNodeInlineStyleDimensions(node);
|
||||||
const initialized = nodeHasDimensions(node);
|
const initialized = nodeHasDimensions(node);
|
||||||
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
|
const hasHandleBounds = !!node.internals.handleBounds;
|
||||||
|
|
||||||
const moveSelectedNodes = useMoveSelectedNodes();
|
const moveSelectedNodes = useMoveSelectedNodes();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { InputNode } from '../Nodes/InputNode';
|
|||||||
import { DefaultNode } from '../Nodes/DefaultNode';
|
import { DefaultNode } from '../Nodes/DefaultNode';
|
||||||
import { GroupNode } from '../Nodes/GroupNode';
|
import { GroupNode } from '../Nodes/GroupNode';
|
||||||
import { OutputNode } from '../Nodes/OutputNode';
|
import { OutputNode } from '../Nodes/OutputNode';
|
||||||
import type { Node, NodeTypes } from '../../types';
|
import type { InternalNode, Node, NodeTypes } from '../../types';
|
||||||
|
|
||||||
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||||
ArrowUp: { x: 0, y: -1 },
|
ArrowUp: { x: 0, y: -1 },
|
||||||
@@ -21,12 +21,12 @@ export const builtinNodeTypes: NodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||||
node: NodeType
|
node: InternalNode<NodeType>
|
||||||
): {
|
): {
|
||||||
width: number | string | undefined;
|
width: number | string | undefined;
|
||||||
height: number | string | undefined;
|
height: number | string | undefined;
|
||||||
} {
|
} {
|
||||||
if (!node.computed) {
|
if (node.internals.handleBounds === undefined) {
|
||||||
return {
|
return {
|
||||||
width: node.width ?? node.initialWidth ?? node.style?.width,
|
width: node.width ?? node.initialWidth ?? node.style?.width,
|
||||||
height: node.height ?? node.initialHeight ?? node.style?.height,
|
height: node.height ?? node.initialHeight ?? node.style?.height,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useStore, useStoreApi } from '../../hooks/useStore';
|
|||||||
import { useDrag } from '../../hooks/useDrag';
|
import { useDrag } from '../../hooks/useDrag';
|
||||||
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
|
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
|
||||||
import { arrowKeyDiffs } from '../NodeWrapper/utils';
|
import { arrowKeyDiffs } from '../NodeWrapper/utils';
|
||||||
import type { Node, ReactFlowState } from '../../types';
|
import type { InternalNode, Node, ReactFlowState } from '../../types';
|
||||||
|
|
||||||
export type NodesSelectionProps<NodeType> = {
|
export type NodesSelectionProps<NodeType> = {
|
||||||
onSelectionContextMenu?: (event: MouseEvent, nodes: NodeType[]) => void;
|
onSelectionContextMenu?: (event: MouseEvent, nodes: NodeType[]) => void;
|
||||||
@@ -20,7 +20,13 @@ export type NodesSelectionProps<NodeType> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => {
|
const selector = (s: ReactFlowState) => {
|
||||||
const selectedNodes = s.nodes.filter((n) => n.selected);
|
const selectedNodes: InternalNode[] = [];
|
||||||
|
for (const [, node] of s.nodeLookup) {
|
||||||
|
if (node.selected) {
|
||||||
|
selectedNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
|
const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type SelectionListenerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
selectedNodes: s.nodes.filter((n) => n.selected),
|
selectedNodes: Array.from(s.nodeLookup.values()).filter((n) => n.selected),
|
||||||
selectedEdges: s.edges.filter((e) => e.selected),
|
selectedEdges: s.edges.filter((e) => e.selected),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export function Pane({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = (event: ReactMouseEvent): void => {
|
const onMouseMove = (event: ReactMouseEvent): void => {
|
||||||
const { userSelectionRect, edges, transform, nodeOrigin, nodes, triggerNodeChanges, triggerEdgeChanges } =
|
const { userSelectionRect, edgeLookup, transform, nodeOrigin, nodeLookup, triggerNodeChanges, triggerEdgeChanges } =
|
||||||
store.getState();
|
store.getState();
|
||||||
if (!isSelecting || !containerBounds.current || !userSelectionRect) {
|
if (!isSelecting || !containerBounds.current || !userSelectionRect) {
|
||||||
return;
|
return;
|
||||||
@@ -149,7 +149,7 @@ export function Pane({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectedNodes = getNodesInside(
|
const selectedNodes = getNodesInside(
|
||||||
nodes,
|
nodeLookup,
|
||||||
nextUserSelectRect,
|
nextUserSelectRect,
|
||||||
transform,
|
transform,
|
||||||
selectionMode === SelectionMode.Partial,
|
selectionMode === SelectionMode.Partial,
|
||||||
@@ -163,22 +163,22 @@ export function Pane({
|
|||||||
for (const selectedNode of selectedNodes) {
|
for (const selectedNode of selectedNodes) {
|
||||||
selectedNodeIds.add(selectedNode.id);
|
selectedNodeIds.add(selectedNode.id);
|
||||||
|
|
||||||
for (const edge of edges) {
|
for (const [edgeId, edge] of edgeLookup) {
|
||||||
if (edge.source === selectedNode.id || edge.target === selectedNode.id) {
|
if (edge.source === selectedNode.id || edge.target === selectedNode.id) {
|
||||||
selectedEdgeIds.add(edge.id);
|
selectedEdgeIds.add(edgeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevSelectedNodesCount.current !== selectedNodeIds.size) {
|
if (prevSelectedNodesCount.current !== selectedNodeIds.size) {
|
||||||
prevSelectedNodesCount.current = selectedNodeIds.size;
|
prevSelectedNodesCount.current = selectedNodeIds.size;
|
||||||
const changes = getSelectionChanges(nodes, selectedNodeIds, true) as NodeChange[];
|
const changes = getSelectionChanges(nodeLookup, selectedNodeIds, true) as NodeChange[];
|
||||||
triggerNodeChanges(changes);
|
triggerNodeChanges(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevSelectedEdgesCount.current !== selectedEdgeIds.size) {
|
if (prevSelectedEdgesCount.current !== selectedEdgeIds.size) {
|
||||||
prevSelectedEdgesCount.current = selectedEdgeIds.size;
|
prevSelectedEdgesCount.current = selectedEdgeIds.size;
|
||||||
const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
|
const changes = getSelectionChanges(edgeLookup, selectedEdgeIds) as EdgeChange[];
|
||||||
triggerEdgeChanges(changes);
|
triggerEdgeChanges(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { calculateNodePosition, snapPosition, type XYPosition } from '@xyflow/system';
|
import { calculateNodePosition, snapPosition, type XYPosition } from '@xyflow/system';
|
||||||
|
|
||||||
import { Node } from '../types';
|
import { type Node } from '../types';
|
||||||
import { useStoreApi } from './useStore';
|
import { useStoreApi } from './useStore';
|
||||||
|
|
||||||
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
|
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
|
||||||
@@ -17,18 +17,11 @@ export function useMoveSelectedNodes() {
|
|||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
const moveSelectedNodes = useCallback((params: { direction: XYPosition; factor: number }) => {
|
const moveSelectedNodes = useCallback((params: { direction: XYPosition; factor: number }) => {
|
||||||
const {
|
const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } =
|
||||||
nodeExtent,
|
store.getState();
|
||||||
nodes,
|
const nodeUpdates = [];
|
||||||
snapToGrid,
|
const isSelected = selectedAndDraggable(nodesDraggable);
|
||||||
snapGrid,
|
|
||||||
nodesDraggable,
|
|
||||||
onError,
|
|
||||||
updateNodePositions,
|
|
||||||
nodeLookup,
|
|
||||||
nodeOrigin,
|
|
||||||
} = store.getState();
|
|
||||||
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
|
||||||
// by default a node moves 5px on each key press
|
// by default a node moves 5px on each key press
|
||||||
// if snap grid is enabled, we use that for the velocity
|
// if snap grid is enabled, we use that for the velocity
|
||||||
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||||
@@ -37,32 +30,34 @@ export function useMoveSelectedNodes() {
|
|||||||
const xDiff = params.direction.x * xVelo * params.factor;
|
const xDiff = params.direction.x * xVelo * params.factor;
|
||||||
const yDiff = params.direction.y * yVelo * params.factor;
|
const yDiff = params.direction.y * yVelo * params.factor;
|
||||||
|
|
||||||
const nodeUpdates = selectedNodes.map((node) => {
|
for (const [, node] of nodeLookup) {
|
||||||
if (node.computed?.positionAbsolute) {
|
if (!isSelected(node)) {
|
||||||
let nextPosition = {
|
continue;
|
||||||
x: node.computed.positionAbsolute.x + xDiff,
|
|
||||||
y: node.computed.positionAbsolute.y + yDiff,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (snapToGrid) {
|
|
||||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { position, positionAbsolute } = calculateNodePosition({
|
|
||||||
nodeId: node.id,
|
|
||||||
nextPosition,
|
|
||||||
nodeLookup,
|
|
||||||
nodeExtent,
|
|
||||||
nodeOrigin,
|
|
||||||
onError,
|
|
||||||
});
|
|
||||||
|
|
||||||
node.position = position;
|
|
||||||
node.computed.positionAbsolute = positionAbsolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
let nextPosition = {
|
||||||
});
|
x: node.internals.positionAbsolute.x + xDiff,
|
||||||
|
y: node.internals.positionAbsolute.y + yDiff,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (snapToGrid) {
|
||||||
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
|
nodeId: node.id,
|
||||||
|
nextPosition,
|
||||||
|
nodeLookup,
|
||||||
|
nodeExtent,
|
||||||
|
nodeOrigin,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
|
node.position = position;
|
||||||
|
node.internals.positionAbsolute = positionAbsolute;
|
||||||
|
|
||||||
|
nodeUpdates.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
updateNodePositions(nodeUpdates);
|
updateNodePositions(nodeUpdates);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { internalsSymbol } from '@xyflow/system';
|
|
||||||
|
|
||||||
import { useStore } from './useStore';
|
import { useStore } from './useStore';
|
||||||
import type { ReactFlowState } from '../types';
|
import type { ReactFlowState } from '../types';
|
||||||
|
|
||||||
@@ -8,13 +6,13 @@ export type UseNodesInitializedOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
|
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
|
||||||
if (s.nodes.length === 0) {
|
if (s.nodeLookup.size === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const node of s.nodes) {
|
for (const [, node] of s.nodeLookup) {
|
||||||
if (options.includeHiddenNodes || !node.hidden) {
|
if (options.includeHiddenNodes || !node.hidden) {
|
||||||
if (node[internalsSymbol]?.handleBounds === undefined) {
|
if (node.internals.handleBounds === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
|
const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
|
||||||
return store.getState().nodeLookup.get(id) as NodeType;
|
return store.getState().nodeLookup.get(id)?.internals.userProvidedNode as NodeType;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
|
const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
|
||||||
@@ -227,7 +227,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
|||||||
const node =
|
const node =
|
||||||
isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
|
isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
|
||||||
? nodeOrRect
|
? nodeOrRect
|
||||||
: (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType);
|
: (store.getState().nodeLookup.get(nodeOrRect.id)?.internals.userProvidedNode as NodeType);
|
||||||
|
|
||||||
return node ? nodeToRect(node) : null;
|
return node ? nodeToRect(node) : null;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -242,7 +242,9 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (nodes || store.getState().nodes).filter((n) => {
|
return (nodes || store.getState().nodes).filter((n) => {
|
||||||
if (!isRect && (n.id === nodeOrRect!.id || !n.computed?.positionAbsolute)) {
|
const internalNode = store.getState().nodeLookup.get(n.id);
|
||||||
|
|
||||||
|
if (internalNode && !isRect && (n.id === nodeOrRect!.id || !internalNode.internals.positionAbsolute)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,5 +103,4 @@ export {
|
|||||||
addEdge,
|
addEdge,
|
||||||
updateEdge,
|
updateEdge,
|
||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
internalsSymbol,
|
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ const createRFStore = ({
|
|||||||
//
|
//
|
||||||
// When this happens, we take the note objects passed by the user and extend them with fields
|
// When this happens, we take the note objects passed by the user and extend them with fields
|
||||||
// relevant for internal React Flow operations.
|
// relevant for internal React Flow operations.
|
||||||
const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||||
|
|
||||||
set({ nodes: nodesWithInternalData });
|
set({ nodes });
|
||||||
},
|
},
|
||||||
setEdges: (edges: Edge[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { connectionLookup, edgeLookup } = get();
|
const { connectionLookup, edgeLookup } = get();
|
||||||
@@ -80,9 +80,9 @@ const createRFStore = ({
|
|||||||
// new dimensions and update the nodes.
|
// new dimensions and update the nodes.
|
||||||
updateNodeDimensions: (updates) => {
|
updateNodeDimensions: (updates) => {
|
||||||
const {
|
const {
|
||||||
|
nodes,
|
||||||
onNodesChange,
|
onNodesChange,
|
||||||
fitView,
|
fitView,
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
fitViewOnInit,
|
fitViewOnInit,
|
||||||
fitViewDone,
|
fitViewDone,
|
||||||
@@ -95,7 +95,6 @@ const createRFStore = ({
|
|||||||
|
|
||||||
const updatedNodes = updateNodeDimensionsSystem(
|
const updatedNodes = updateNodeDimensionsSystem(
|
||||||
updates,
|
updates,
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
domNode,
|
domNode,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
@@ -112,14 +111,14 @@ const createRFStore = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextNodes = updateAbsolutePositions(updatedNodes, nodeLookup, nodeOrigin);
|
updateAbsolutePositions(nodeLookup, nodeOrigin);
|
||||||
|
|
||||||
// we call fitView once initially after all dimensions are set
|
// 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({
|
||||||
...fitViewOnInitOptions,
|
...fitViewOnInitOptions,
|
||||||
nodes: fitViewOnInitOptions?.nodes || nextNodes,
|
nodes: fitViewOnInitOptions?.nodes || nodes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ const createRFStore = ({
|
|||||||
// has not provided an onNodesChange handler.
|
// has not provided an onNodesChange handler.
|
||||||
// Nodes are only rendered if they have a width and height
|
// Nodes are only rendered if they have a width and height
|
||||||
// attribute which they get from this handler.
|
// attribute which they get from this handler.
|
||||||
set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
|
set({ nodes, fitViewDone: nextFitViewDone });
|
||||||
|
|
||||||
if (changes?.length > 0) {
|
if (changes?.length > 0) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
@@ -143,7 +142,6 @@ const createRFStore = ({
|
|||||||
id: node.id,
|
id: node.id,
|
||||||
type: 'position',
|
type: 'position',
|
||||||
position: node.position,
|
position: node.position,
|
||||||
positionAbsolute: node.computed?.positionAbsolute,
|
|
||||||
dragging,
|
dragging,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,7 +183,7 @@ const createRFStore = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addSelectedNodes: (selectedNodeIds) => {
|
addSelectedNodes: (selectedNodeIds) => {
|
||||||
const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
const { multiSelectionActive, edgeLookup, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = get();
|
||||||
|
|
||||||
if (multiSelectionActive) {
|
if (multiSelectionActive) {
|
||||||
const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
||||||
@@ -193,11 +191,11 @@ const createRFStore = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerNodeChanges(getSelectionChanges(nodes, new Set([...selectedNodeIds]), true));
|
triggerNodeChanges(getSelectionChanges(nodeLookup, new Set([...selectedNodeIds]), true));
|
||||||
triggerEdgeChanges(getSelectionChanges(edges));
|
triggerEdgeChanges(getSelectionChanges(edgeLookup));
|
||||||
},
|
},
|
||||||
addSelectedEdges: (selectedEdgeIds) => {
|
addSelectedEdges: (selectedEdgeIds) => {
|
||||||
const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
const { multiSelectionActive, edgeLookup, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = get();
|
||||||
|
|
||||||
if (multiSelectionActive) {
|
if (multiSelectionActive) {
|
||||||
const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
||||||
@@ -205,8 +203,8 @@ const createRFStore = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerEdgeChanges(getSelectionChanges(edges, new Set([...selectedEdgeIds])));
|
triggerEdgeChanges(getSelectionChanges(edgeLookup, new Set([...selectedEdgeIds])));
|
||||||
triggerNodeChanges(getSelectionChanges(nodes, new Set(), true));
|
triggerNodeChanges(getSelectionChanges(nodeLookup, new Set(), true));
|
||||||
},
|
},
|
||||||
unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => {
|
unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => {
|
||||||
const { edges: storeEdges, nodes: storeNodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
const { edges: storeEdges, nodes: storeNodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
||||||
@@ -276,8 +274,8 @@ const createRFStore = ({
|
|||||||
const { transform, width, height, panZoom, translateExtent } = get();
|
const { transform, width, height, panZoom, translateExtent } = get();
|
||||||
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
||||||
},
|
},
|
||||||
fitView: (nodes: Node[], options?: FitViewOptions): boolean => {
|
fitView: (options?: FitViewOptions): boolean => {
|
||||||
const { panZoom, width, height, minZoom, maxZoom, nodeOrigin } = get();
|
const { panZoom, width, height, minZoom, maxZoom, nodeOrigin, nodeLookup } = get();
|
||||||
|
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
return false;
|
return false;
|
||||||
@@ -285,7 +283,7 @@ const createRFStore = ({
|
|||||||
|
|
||||||
return fitViewSystem(
|
return fitViewSystem(
|
||||||
{
|
{
|
||||||
nodes,
|
nodeLookup,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
panZoom,
|
panZoom,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const getInitialState = ({
|
|||||||
const storeNodes = defaultNodes ?? nodes ?? [];
|
const storeNodes = defaultNodes ?? nodes ?? [];
|
||||||
|
|
||||||
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
|
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
|
||||||
const nextNodes = adoptUserProvidedNodes(storeNodes, nodeLookup, {
|
adoptUserProvidedNodes(storeNodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false,
|
elevateNodesOnSelect: false,
|
||||||
});
|
});
|
||||||
@@ -43,7 +43,7 @@ const getInitialState = ({
|
|||||||
let transform: Transform = [0, 0, 1];
|
let transform: Transform = [0, 0, 1];
|
||||||
|
|
||||||
if (fitView && width && height) {
|
if (fitView && width && height) {
|
||||||
const nodesWithDimensions = nextNodes.filter(
|
const nodesWithDimensions = storeNodes.filter(
|
||||||
(node) => (node.width || node.initialWidth) && (node.height || node.initialHeight)
|
(node) => (node.width || node.initialWidth) && (node.height || node.initialHeight)
|
||||||
);
|
);
|
||||||
// @todo users nodeOrigin should be used here
|
// @todo users nodeOrigin should be used here
|
||||||
@@ -57,7 +57,7 @@ const getInitialState = ({
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
transform,
|
transform,
|
||||||
nodes: nextNodes,
|
nodes: storeNodes,
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
edges: storeEdges,
|
edges: storeEdges,
|
||||||
edgeLookup,
|
edgeLookup,
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError, NodeProps as NodePropsBase } from '@xyflow/system';
|
import type {
|
||||||
|
CoordinateExtent,
|
||||||
|
NodeBase,
|
||||||
|
NodeOrigin,
|
||||||
|
OnError,
|
||||||
|
NodeProps as NodePropsBase,
|
||||||
|
InternalNodeBase,
|
||||||
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { NodeTypes } from './general';
|
import { NodeTypes } from './general';
|
||||||
|
|
||||||
@@ -17,6 +24,8 @@ export type Node<
|
|||||||
focusable?: boolean;
|
focusable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||||
|
|
||||||
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
|
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
|
||||||
export type SelectionDragHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
export type SelectionDragHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
||||||
export type OnNodeDrag<NodeType extends Node = Node> = (
|
export type OnNodeDrag<NodeType extends Node = Node> = (
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import type {
|
|||||||
OnBeforeDelete,
|
OnBeforeDelete,
|
||||||
IsValidConnection,
|
IsValidConnection,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
|
InternalNode,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||||
@@ -52,7 +53,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
nodes: NodeType[];
|
nodes: NodeType[];
|
||||||
nodeLookup: NodeLookup<NodeType>;
|
nodeLookup: NodeLookup<InternalNode<NodeType>>;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
edgeLookup: EdgeLookup<EdgeType>;
|
edgeLookup: EdgeLookup<EdgeType>;
|
||||||
connectionLookup: ConnectionLookup;
|
connectionLookup: ConnectionLookup;
|
||||||
@@ -169,7 +170,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
|||||||
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
||||||
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
panBy: PanBy;
|
panBy: PanBy;
|
||||||
fitView: (nodes: NodeType[], options?: FitViewOptions) => boolean;
|
fitView: (options?: FitViewOptions) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { EdgeLookup, NodeLookup } from '@xyflow/system';
|
import { EdgeLookup, NodeLookup } from '@xyflow/system';
|
||||||
import type { Node, Edge, EdgeChange, NodeChange, NodeSelectionChange, EdgeSelectionChange } from '../types';
|
import type {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
EdgeChange,
|
||||||
|
NodeChange,
|
||||||
|
NodeSelectionChange,
|
||||||
|
EdgeSelectionChange,
|
||||||
|
InternalNode,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
export function handleParentExpand(updatedElements: any[], updateItem: any) {
|
export function handleParentExpand(updatedElements: any[], updateItem: any) {
|
||||||
for (const [index, item] of updatedElements.entries()) {
|
for (const [index, item] of updatedElements.entries()) {
|
||||||
@@ -125,11 +133,6 @@ function applyChange(change: any, element: any, elements: any[] = []): any {
|
|||||||
element.position = change.position;
|
element.position = change.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof change.positionAbsolute !== 'undefined') {
|
|
||||||
element.computed ??= {};
|
|
||||||
element.computed.positionAbsolute = change.positionAbsolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof change.dragging !== 'undefined') {
|
if (typeof change.dragging !== 'undefined') {
|
||||||
element.dragging = change.dragging;
|
element.dragging = change.dragging;
|
||||||
}
|
}
|
||||||
@@ -228,13 +231,13 @@ export function createSelectionChange(id: string, selected: boolean): NodeSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectionChanges(
|
export function getSelectionChanges(
|
||||||
items: any[],
|
items: Map<string, any>,
|
||||||
selectedIds: Set<string> = new Set(),
|
selectedIds: Set<string> = new Set(),
|
||||||
mutateItem = false
|
mutateItem = false
|
||||||
): NodeSelectionChange[] | EdgeSelectionChange[] {
|
): NodeSelectionChange[] | EdgeSelectionChange[] {
|
||||||
const changes: NodeSelectionChange[] | EdgeSelectionChange[] = [];
|
const changes: NodeSelectionChange[] | EdgeSelectionChange[] = [];
|
||||||
|
|
||||||
for (const item of items) {
|
for (const [, item] of items) {
|
||||||
const willBeSelected = selectedIds.has(item.id);
|
const willBeSelected = selectedIds.has(item.id);
|
||||||
|
|
||||||
// we don't want to set all items to selected=false on the first selection
|
// we don't want to set all items to selected=false on the first selection
|
||||||
@@ -266,7 +269,7 @@ export function getElementsDiffChanges({
|
|||||||
lookup,
|
lookup,
|
||||||
}: {
|
}: {
|
||||||
items: Node[] | undefined;
|
items: Node[] | undefined;
|
||||||
lookup: NodeLookup<Node>;
|
lookup: NodeLookup<InternalNode<Node>>;
|
||||||
}): NodeChange[];
|
}): NodeChange[];
|
||||||
export function getElementsDiffChanges({
|
export function getElementsDiffChanges({
|
||||||
items,
|
items,
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ export const errorMessages = {
|
|||||||
`Node with id "${id}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,
|
`Node with id "${id}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const internalsSymbol = Symbol.for('internals');
|
|
||||||
|
|
||||||
export const infiniteExtent: CoordinateExtent = [
|
export const infiniteExtent: CoordinateExtent = [
|
||||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3';
|
import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3';
|
||||||
|
|
||||||
import type { XYPosition, Rect } from './utils';
|
import type { XYPosition, Rect } from './utils';
|
||||||
import type { NodeBase, NodeDragItem, NodeOrigin } from './nodes';
|
import type { InternalNodeBase, NodeBase, NodeDragItem, NodeOrigin } from './nodes';
|
||||||
import type { ConnectingHandle, HandleType } from './handles';
|
import type { ConnectingHandle, HandleType } from './handles';
|
||||||
import { PanZoomInstance } from './panzoom';
|
import { PanZoomInstance } from './panzoom';
|
||||||
import { EdgeBase } from '..';
|
import { EdgeBase } from '..';
|
||||||
@@ -52,7 +52,7 @@ export type OnConnectEnd = (event: MouseEvent | TouchEvent) => void;
|
|||||||
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
||||||
|
|
||||||
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
||||||
nodes: NodeType[];
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
panZoom: PanZoomInstance;
|
panZoom: PanZoomInstance;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { internalsSymbol } from '../constants';
|
|
||||||
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
|
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
|
||||||
import { Optional } from '../utils/types';
|
import { Optional } from '../utils/types';
|
||||||
|
|
||||||
@@ -63,18 +62,25 @@ export type NodeBase<
|
|||||||
computed?: {
|
computed?: {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
positionAbsolute?: XYPosition;
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType & {
|
||||||
|
computed: {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
};
|
||||||
// Only used internally
|
// Only used internally
|
||||||
[internalsSymbol]?: {
|
internals: {
|
||||||
z?: number;
|
positionAbsolute: XYPosition;
|
||||||
handleBounds?: NodeHandleBounds;
|
z: number;
|
||||||
isParent?: boolean;
|
// @todo should we rename this to "handles" and use same type as node.handles?
|
||||||
|
isParent: boolean;
|
||||||
/** Holds a reference to the original node object provided by the user
|
/** Holds a reference to the original node object provided by the user
|
||||||
* (which may lack some fields, like `computed` or `[internalSymbol]`. Used
|
* (which may lack some fields, like `computed` or `[internalSymbol]`. Used
|
||||||
* as an optimization to avoid certain operations. */
|
* as an optimization to avoid certain operations. */
|
||||||
userProvidedNode: NodeBase<NodeData, NodeType>;
|
userProvidedNode: NodeType;
|
||||||
|
handleBounds?: NodeHandleBounds;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,6 +126,8 @@ export type NodeDragItem = {
|
|||||||
computed: {
|
computed: {
|
||||||
width: number | null;
|
width: number | null;
|
||||||
height: number | null;
|
height: number | null;
|
||||||
|
};
|
||||||
|
internals: {
|
||||||
positionAbsolute: XYPosition;
|
positionAbsolute: XYPosition;
|
||||||
};
|
};
|
||||||
extent?: 'parent' | CoordinateExtent;
|
extent?: 'parent' | CoordinateExtent;
|
||||||
@@ -137,4 +145,4 @@ export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
|||||||
|
|
||||||
export type Align = 'center' | 'start' | 'end';
|
export type Align = 'center' | 'start' | 'end';
|
||||||
|
|
||||||
export type NodeLookup<NodeType extends NodeBase = NodeBase> = Map<string, NodeType>;
|
export type NodeLookup<NodeType extends InternalNodeBase = InternalNodeBase> = Map<string, NodeType>;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Connection, Transform, errorMessages, internalsSymbol, isEdgeBase } from '../..';
|
import { Connection, InternalNodeBase, Transform, errorMessages, isEdgeBase, EdgeBase } from '../..';
|
||||||
import { EdgeBase, NodeBase } from '../../types';
|
|
||||||
import { getOverlappingArea, boxToRect, nodeToBox, getBoundsOfBoxes, devWarn } from '../general';
|
import { getOverlappingArea, boxToRect, nodeToBox, getBoundsOfBoxes, devWarn } from '../general';
|
||||||
|
|
||||||
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
|
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
|
||||||
@@ -24,8 +23,8 @@ export function getEdgeCenter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GetEdgeZIndexParams = {
|
export type GetEdgeZIndexParams = {
|
||||||
sourceNode: NodeBase;
|
sourceNode: InternalNodeBase;
|
||||||
targetNode: NodeBase;
|
targetNode: InternalNodeBase;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
elevateOnSelect?: boolean;
|
elevateOnSelect?: boolean;
|
||||||
@@ -43,14 +42,14 @@ export function getElevatedEdgeZIndex({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const edgeOrConnectedNodeSelected = selected || targetNode.selected || sourceNode.selected;
|
const edgeOrConnectedNodeSelected = selected || targetNode.selected || sourceNode.selected;
|
||||||
const selectedZIndex = Math.max(sourceNode[internalsSymbol]?.z || 0, targetNode[internalsSymbol]?.z || 0, 1000);
|
const selectedZIndex = Math.max(sourceNode.internals.z || 0, targetNode.internals.z || 0, 1000);
|
||||||
|
|
||||||
return zIndex + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
return zIndex + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
type IsEdgeVisibleParams = {
|
type IsEdgeVisibleParams = {
|
||||||
sourceNode: NodeBase;
|
sourceNode: InternalNodeBase;
|
||||||
targetNode: NodeBase;
|
targetNode: InternalNodeBase;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
import { EdgePosition } from '../../types/edges';
|
import { EdgePosition } from '../../types/edges';
|
||||||
import { ConnectionMode, OnError } from '../../types/general';
|
import { ConnectionMode, OnError } from '../../types/general';
|
||||||
import { NodeBase, NodeHandle } from '../../types/nodes';
|
import { InternalNodeBase, NodeHandle } from '../../types/nodes';
|
||||||
import { Position } from '../../types/utils';
|
import { Position } from '../../types/utils';
|
||||||
import { errorMessages, internalsSymbol } from '../../constants';
|
import { errorMessages } from '../../constants';
|
||||||
import { HandleElement } from '../../types';
|
import { HandleElement } from '../../types';
|
||||||
import { getNodeDimensions } from '../general';
|
import { getNodeDimensions } from '../general';
|
||||||
|
|
||||||
export type GetEdgePositionParams = {
|
export type GetEdgePositionParams = {
|
||||||
id: string;
|
id: string;
|
||||||
sourceNode: NodeBase;
|
sourceNode: InternalNodeBase;
|
||||||
sourceHandle: string | null;
|
sourceHandle: string | null;
|
||||||
targetNode: NodeBase;
|
targetNode: InternalNodeBase;
|
||||||
targetHandle: string | null;
|
targetHandle: string | null;
|
||||||
connectionMode: ConnectionMode;
|
connectionMode: ConnectionMode;
|
||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isNodeInitialized(node: NodeBase): boolean {
|
function isNodeInitialized(node: InternalNodeBase): boolean {
|
||||||
return (
|
return (
|
||||||
!!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) &&
|
node &&
|
||||||
!!(node?.computed?.width || node?.width || node?.initialWidth)
|
!!(node.internals.handleBounds || node.handles?.length) &&
|
||||||
|
!!(node.computed.width || node.width || node.initialWidth)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceHandleBounds = sourceNode[internalsSymbol]?.handleBounds || toHandleBounds(sourceNode.handles);
|
const sourceHandleBounds = sourceNode.internals.handleBounds || toHandleBounds(sourceNode.handles);
|
||||||
const targetHandleBounds = targetNode[internalsSymbol]?.handleBounds || toHandleBounds(targetNode.handles);
|
const targetHandleBounds = targetNode.internals.handleBounds || toHandleBounds(targetNode.handles);
|
||||||
|
|
||||||
const sourceHandle = getHandle(sourceHandleBounds?.source ?? [], params.sourceHandle);
|
const sourceHandle = getHandle(sourceHandleBounds?.source ?? [], params.sourceHandle);
|
||||||
const targetHandle = getHandle(
|
const targetHandle = getHandle(
|
||||||
@@ -95,9 +96,9 @@ function toHandleBounds(handles?: NodeHandle[]) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] {
|
function getHandlePosition(position: Position, node: InternalNodeBase, handle: HandleElement | null = null): number[] {
|
||||||
const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
|
const x = (handle?.x ?? 0) + (node.internals.positionAbsolute?.x ?? 0);
|
||||||
const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
|
const y = (handle?.y ?? 0) + (node.internals.positionAbsolute?.y ?? 0);
|
||||||
const { width, height } = handle ?? getNodeDimensions(node);
|
const { width, height } = handle ?? getNodeDimensions(node);
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
NodeOrigin,
|
NodeOrigin,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
Transform,
|
Transform,
|
||||||
|
InternalNodeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { type Viewport } from '../types';
|
import { type Viewport } from '../types';
|
||||||
import { getNodePositionWithOrigin } from './graph';
|
import { getNodePositionWithOrigin } from './graph';
|
||||||
@@ -65,7 +66,7 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
|
|||||||
height: y2 - y,
|
height: y2 - y,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const nodeToRect = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => {
|
export const nodeToRect = (node: InternalNodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => {
|
||||||
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -75,7 +76,7 @@ export const nodeToRect = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rec
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nodeToBox = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => {
|
export const nodeToBox = (node: InternalNodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => {
|
||||||
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
OnError,
|
OnError,
|
||||||
OnBeforeDeleteBase,
|
OnBeforeDeleteBase,
|
||||||
NodeLookup,
|
NodeLookup,
|
||||||
|
InternalNodeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { errorMessages } from '../constants';
|
import { errorMessages } from '../constants';
|
||||||
|
|
||||||
@@ -47,6 +48,10 @@ export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any):
|
|||||||
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
|
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
|
||||||
'id' in element && 'position' in element && !('source' in element) && !('target' in element);
|
'id' in element && 'position' in element && !('source' in element) && !('target' in element);
|
||||||
|
|
||||||
|
export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalNodeBase>(
|
||||||
|
element: any
|
||||||
|
): element is NodeType => 'id' in element && 'computed' in element && !('source' in element) && !('target' in element);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in a node, and get connected nodes where edge.source === node.id
|
* Pass in a node, and get connected nodes where edge.source === node.id
|
||||||
* @public
|
* @public
|
||||||
@@ -101,7 +106,7 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getNodePositionWithOrigin = (
|
export const getNodePositionWithOrigin = (
|
||||||
node: NodeBase | undefined,
|
node: InternalNodeBase | undefined,
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
nodeOrigin: NodeOrigin = [0, 0]
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } => {
|
): { position: XYPosition; positionAbsolute: XYPosition } => {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -128,12 +133,10 @@ export const getNodePositionWithOrigin = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
position,
|
position,
|
||||||
positionAbsolute: node.computed?.positionAbsolute
|
positionAbsolute: {
|
||||||
? {
|
x: node.internals.positionAbsolute.x - offsetX,
|
||||||
x: node.computed.positionAbsolute.x - offsetX,
|
y: node.internals.positionAbsolute.y - offsetY,
|
||||||
y: node.computed.positionAbsolute.y - offsetY,
|
},
|
||||||
}
|
|
||||||
: position,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,6 +154,7 @@ export type GetNodesBoundsParams = {
|
|||||||
* @param params.useRelativePosition - Whether to use the relative or absolute node positions
|
* @param params.useRelativePosition - Whether to use the relative or absolute node positions
|
||||||
* @returns Bounding box enclosing all nodes
|
* @returns Bounding box enclosing all nodes
|
||||||
*/
|
*/
|
||||||
|
// @todo how to handle this if users do not have absolute positions?
|
||||||
export const getNodesBounds = (
|
export const getNodesBounds = (
|
||||||
nodes: NodeBase[],
|
nodes: NodeBase[],
|
||||||
params: GetNodesBoundsParams = { nodeOrigin: [0, 0], useRelativePosition: false }
|
params: GetNodesBoundsParams = { nodeOrigin: [0, 0], useRelativePosition: false }
|
||||||
@@ -161,6 +165,7 @@ export const getNodesBounds = (
|
|||||||
|
|
||||||
const box = nodes.reduce(
|
const box = nodes.reduce(
|
||||||
(currBox, node) => {
|
(currBox, node) => {
|
||||||
|
// @ts-expect-error
|
||||||
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
|
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
|
||||||
return getBoundsOfBoxes(
|
return getBoundsOfBoxes(
|
||||||
currBox,
|
currBox,
|
||||||
@@ -176,8 +181,47 @@ export const getNodesBounds = (
|
|||||||
return boxToRect(box);
|
return boxToRect(box);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodesInside = <NodeType extends NodeBase>(
|
export type GetInternalNodesBoundsParams = {
|
||||||
nodes: NodeType[],
|
nodeOrigin?: NodeOrigin;
|
||||||
|
useRelativePosition?: boolean;
|
||||||
|
filter?: (node: NodeBase) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines a bounding box that contains all given nodes in an array
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const getInternalNodesBounds = (
|
||||||
|
nodeLookup: NodeLookup,
|
||||||
|
params: GetInternalNodesBoundsParams = {
|
||||||
|
nodeOrigin: [0, 0],
|
||||||
|
useRelativePosition: false,
|
||||||
|
}
|
||||||
|
): Rect => {
|
||||||
|
if (nodeLookup.size === 0) {
|
||||||
|
return { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity };
|
||||||
|
|
||||||
|
nodeLookup.forEach((node) => {
|
||||||
|
if (params.filter == undefined || params.filter(node)) {
|
||||||
|
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
|
||||||
|
box = getBoundsOfBoxes(
|
||||||
|
box,
|
||||||
|
rectToBox({
|
||||||
|
...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
|
||||||
|
...getNodeDimensions(node),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return boxToRect(box);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
||||||
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
[tx, ty, tScale]: Transform = [0, 0, 1],
|
[tx, ty, tScale]: Transform = [0, 0, 1],
|
||||||
partially = false,
|
partially = false,
|
||||||
@@ -191,13 +235,15 @@ export const getNodesInside = <NodeType extends NodeBase>(
|
|||||||
height: rect.height / tScale,
|
height: rect.height / tScale,
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibleNodes = nodes.reduce<NodeType[]>((res, node) => {
|
const visibleNodes: NodeType[] = [];
|
||||||
|
|
||||||
|
for (const [, node] of nodeLookup) {
|
||||||
const { computed, selectable = true, hidden = false } = node;
|
const { computed, selectable = true, hidden = false } = node;
|
||||||
const width = computed?.width ?? node.width ?? node.initialWidth ?? null;
|
const width = computed?.width ?? node.width ?? node.initialWidth ?? null;
|
||||||
const height = computed?.height ?? node.height ?? node.initialHeight ?? null;
|
const height = computed?.height ?? node.height ?? node.initialHeight ?? null;
|
||||||
|
|
||||||
if ((excludeNonSelectableNodes && !selectable) || hidden) {
|
if ((excludeNonSelectableNodes && !selectable) || hidden) {
|
||||||
return res;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin));
|
const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin));
|
||||||
@@ -208,11 +254,9 @@ export const getNodesInside = <NodeType extends NodeBase>(
|
|||||||
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
|
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
|
||||||
|
|
||||||
if (isVisible || node.dragging) {
|
if (isVisible || node.dragging) {
|
||||||
res.push(node);
|
visibleNodes.push(node);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return res;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return visibleNodes;
|
return visibleNodes;
|
||||||
};
|
};
|
||||||
@@ -236,17 +280,20 @@ export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
export function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
||||||
{ nodes, width, height, panZoom, minZoom, maxZoom, nodeOrigin = [0, 0] }: Params,
|
{ nodeLookup, width, height, panZoom, minZoom, maxZoom, nodeOrigin = [0, 0] }: Params,
|
||||||
options?: Options
|
options?: Options
|
||||||
) {
|
) {
|
||||||
const filteredNodes = nodes.filter((n) => {
|
const filteredNodes: InternalNodeBase[] = [];
|
||||||
|
|
||||||
|
nodeLookup.forEach((n) => {
|
||||||
const isVisible = n.computed?.width && n.computed?.height && (options?.includeHiddenNodes || !n.hidden);
|
const isVisible = n.computed?.width && n.computed?.height && (options?.includeHiddenNodes || !n.hidden);
|
||||||
|
|
||||||
if (options?.nodes?.length) {
|
if (
|
||||||
return isVisible && options?.nodes.some((optionNode) => optionNode.id === n.id);
|
isVisible &&
|
||||||
|
(!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id)))
|
||||||
|
) {
|
||||||
|
filteredNodes.push(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isVisible;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filteredNodes.length > 0) {
|
if (filteredNodes.length > 0) {
|
||||||
@@ -303,7 +350,7 @@ export function calculateNodePosition<NodeType extends NodeBase>({
|
|||||||
}: {
|
}: {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
nextPosition: XYPosition;
|
nextPosition: XYPosition;
|
||||||
nodeLookup: NodeLookup<NodeType>;
|
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
@@ -313,16 +360,17 @@ export function calculateNodePosition<NodeType extends NodeBase>({
|
|||||||
const { x: parentX, y: parentY } = parentNode
|
const { x: parentX, y: parentY } = parentNode
|
||||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||||
: { x: 0, y: 0 };
|
: { x: 0, y: 0 };
|
||||||
|
|
||||||
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||||
|
|
||||||
if (node.extent === 'parent' && !node.expandParent) {
|
if (node.extent === 'parent' && !node.expandParent) {
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
onError?.('005', errorMessages['error005']());
|
onError?.('005', errorMessages['error005']());
|
||||||
} else {
|
} else {
|
||||||
const nodeWidth = node.computed?.width;
|
const nodeWidth = node.computed.width;
|
||||||
const nodeHeight = node.computed?.height;
|
const nodeHeight = node.computed.height;
|
||||||
const parentWidth = parentNode?.computed?.width;
|
const parentWidth = parentNode.computed.width;
|
||||||
const parentHeight = parentNode?.computed?.height;
|
const parentHeight = parentNode.computed.height;
|
||||||
|
|
||||||
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
||||||
const currNodeOrigin = node.origin || nodeOrigin;
|
const currNodeOrigin = node.origin || nodeOrigin;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { internalsSymbol } from '../constants';
|
|
||||||
import {
|
import {
|
||||||
NodeBase,
|
NodeBase,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
@@ -12,54 +11,53 @@ import {
|
|||||||
ConnectionLookup,
|
ConnectionLookup,
|
||||||
EdgeBase,
|
EdgeBase,
|
||||||
EdgeLookup,
|
EdgeLookup,
|
||||||
|
InternalNodeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getDimensions, getHandleBounds } from './dom';
|
import { getDimensions, getHandleBounds } from './dom';
|
||||||
import { isNumeric } from './general';
|
import { isNumeric } from './general';
|
||||||
import { getNodePositionWithOrigin } from './graph';
|
import { getNodePositionWithOrigin } from './graph';
|
||||||
|
|
||||||
type ParentNodes = Record<string, boolean>;
|
type ParentNodes = Set<string>;
|
||||||
|
|
||||||
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
nodeLookup: Map<string, NodeType>,
|
|
||||||
nodeOrigin: NodeOrigin = [0, 0],
|
nodeOrigin: NodeOrigin = [0, 0],
|
||||||
parentNodes?: ParentNodes
|
parentNodes?: ParentNodes
|
||||||
) {
|
) {
|
||||||
return nodes.map((node) => {
|
for (const [, node] of nodeLookup) {
|
||||||
if (node.parentNode && !nodeLookup.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?.has(node.id)) {
|
||||||
const parentNode = node.parentNode ? nodeLookup.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,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
{
|
{
|
||||||
...node.position,
|
...node.position,
|
||||||
z: node[internalsSymbol]?.z ?? 0,
|
z: node.internals.z ?? 0,
|
||||||
},
|
},
|
||||||
parentNode?.origin || nodeOrigin
|
parentNode?.origin || nodeOrigin
|
||||||
);
|
);
|
||||||
|
|
||||||
const positionChanged = x !== node.computed?.positionAbsolute?.x || y !== node.computed?.positionAbsolute?.y;
|
const positionChanged = x !== node.internals.positionAbsolute?.x || y !== node.internals.positionAbsolute?.y;
|
||||||
node.computed!.positionAbsolute = positionChanged
|
node.internals.positionAbsolute = positionChanged
|
||||||
? {
|
? {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
}
|
}
|
||||||
: node.computed?.positionAbsolute;
|
: node.internals.positionAbsolute;
|
||||||
|
|
||||||
node[internalsSymbol]!.z = z;
|
node.internals.z = z;
|
||||||
|
|
||||||
if (parentNodes?.[node.id]) {
|
if (parentNodes?.has(node.id)) {
|
||||||
node[internalsSymbol]!.isParent = true;
|
node.internals.isParent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
nodeLookup.set(node.id, node);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateNodesOptions<NodeType extends NodeBase> = {
|
type UpdateNodesOptions<NodeType extends NodeBase> = {
|
||||||
@@ -70,64 +68,55 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
|||||||
|
|
||||||
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
|
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
nodeLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
options: UpdateNodesOptions<NodeType> = {
|
options: UpdateNodesOptions<NodeType> = {
|
||||||
nodeOrigin: [0, 0] as NodeOrigin,
|
nodeOrigin: [0, 0] as NodeOrigin,
|
||||||
elevateNodesOnSelect: true,
|
elevateNodesOnSelect: true,
|
||||||
defaults: {},
|
defaults: {},
|
||||||
}
|
}
|
||||||
): NodeType[] {
|
) {
|
||||||
const tmpLookup = new Map(nodeLookup);
|
const tmpLookup = new Map(nodeLookup);
|
||||||
nodeLookup.clear();
|
nodeLookup.clear();
|
||||||
const parentNodes: ParentNodes = {};
|
const parentNodes: ParentNodes = new Set();
|
||||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||||
|
|
||||||
const nextNodes = nodes.map((n) => {
|
nodes.forEach((n) => {
|
||||||
const currentStoreNode = tmpLookup.get(n.id);
|
const currentStoreNode = tmpLookup.get(n.id);
|
||||||
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode) {
|
if (n.parentNode) {
|
||||||
|
parentNodes.add(n.parentNode);
|
||||||
|
}
|
||||||
|
if (n === currentStoreNode?.internals?.userProvidedNode) {
|
||||||
nodeLookup.set(n.id, currentStoreNode);
|
nodeLookup.set(n.id, currentStoreNode);
|
||||||
return currentStoreNode;
|
return currentStoreNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node: NodeType = {
|
const node: InternalNodeBase<NodeType> = {
|
||||||
...options.defaults,
|
...options.defaults,
|
||||||
...n,
|
...n,
|
||||||
computed: {
|
computed: {
|
||||||
positionAbsolute: n.position,
|
|
||||||
width: n.computed?.width,
|
width: n.computed?.width,
|
||||||
height: n.computed?.height,
|
height: n.computed?.height,
|
||||||
},
|
},
|
||||||
};
|
internals: {
|
||||||
const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);
|
positionAbsolute: n.position,
|
||||||
const currInternals = n?.[internalsSymbol] || currentStoreNode?.[internalsSymbol];
|
handleBounds: currentStoreNode?.internals?.handleBounds,
|
||||||
|
z: (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0),
|
||||||
if (node.parentNode) {
|
|
||||||
parentNodes[node.parentNode] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(node, internalsSymbol, {
|
|
||||||
enumerable: false,
|
|
||||||
value: {
|
|
||||||
handleBounds: currInternals?.handleBounds,
|
|
||||||
z,
|
|
||||||
userProvidedNode: n,
|
userProvidedNode: n,
|
||||||
|
isParent: false,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
nodeLookup.set(node.id, node);
|
nodeLookup.set(node.id, node);
|
||||||
|
|
||||||
return node;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodesWithPositions = updateAbsolutePositions(nextNodes, nodeLookup, options.nodeOrigin, parentNodes);
|
if (parentNodes.size > 0) {
|
||||||
|
updateAbsolutePositions(nodeLookup, options.nodeOrigin, parentNodes);
|
||||||
return nodesWithPositions;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateXYZPosition<NodeType extends NodeBase>(
|
function calculateXYZPosition<NodeType extends NodeBase>(
|
||||||
node: NodeType,
|
node: NodeType,
|
||||||
nodes: NodeType[],
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
nodeLookup: Map<string, NodeType>,
|
|
||||||
result: XYZPosition,
|
result: XYZPosition,
|
||||||
nodeOrigin: NodeOrigin
|
nodeOrigin: NodeOrigin
|
||||||
): XYZPosition {
|
): XYZPosition {
|
||||||
@@ -140,12 +129,11 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
|
|
||||||
return calculateXYZPosition(
|
return calculateXYZPosition(
|
||||||
parentNode,
|
parentNode,
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
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,
|
||||||
z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0,
|
z: (parentNode.internals.z ?? 0) > (result.z ?? 0) ? parentNode.internals.z ?? 0 : result.z ?? 0,
|
||||||
},
|
},
|
||||||
parentNode.origin || nodeOrigin
|
parentNode.origin || nodeOrigin
|
||||||
);
|
);
|
||||||
@@ -153,25 +141,25 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
|
|
||||||
export function updateNodeDimensions<NodeType extends NodeBase>(
|
export function updateNodeDimensions<NodeType extends NodeBase>(
|
||||||
updates: Map<string, NodeDimensionUpdate>,
|
updates: Map<string, NodeDimensionUpdate>,
|
||||||
nodes: NodeType[],
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
nodeLookup: Map<string, NodeType>,
|
|
||||||
domNode: HTMLElement | null,
|
domNode: HTMLElement | null,
|
||||||
nodeOrigin?: NodeOrigin,
|
nodeOrigin?: NodeOrigin,
|
||||||
onUpdate?: (id: string, dimensions: Dimensions) => void
|
onUpdate?: (id: string, dimensions: Dimensions) => void
|
||||||
): NodeType[] | null {
|
): { hasUpdate: boolean } {
|
||||||
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
||||||
|
let hasUpdate = false;
|
||||||
|
|
||||||
if (!viewportNode) {
|
if (!viewportNode) {
|
||||||
return null;
|
return { hasUpdate };
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = window.getComputedStyle(viewportNode);
|
const style = window.getComputedStyle(viewportNode);
|
||||||
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
||||||
|
|
||||||
const nextNodes = nodes.map((node) => {
|
updates.forEach((update) => {
|
||||||
const update = updates.get(node.id);
|
const node = nodeLookup.get(update.id);
|
||||||
|
|
||||||
if (update) {
|
if (node) {
|
||||||
const dimensions = getDimensions(update.nodeElement);
|
const dimensions = getDimensions(update.nodeElement);
|
||||||
const doUpdate = !!(
|
const doUpdate = !!(
|
||||||
dimensions.width &&
|
dimensions.width &&
|
||||||
@@ -180,6 +168,7 @@ export function updateNodeDimensions<NodeType extends NodeBase>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
|
hasUpdate = true;
|
||||||
onUpdate?.(node.id, dimensions);
|
onUpdate?.(node.id, dimensions);
|
||||||
|
|
||||||
const newNode = {
|
const newNode = {
|
||||||
@@ -188,25 +177,20 @@ export function updateNodeDimensions<NodeType extends NodeBase>(
|
|||||||
...node.computed,
|
...node.computed,
|
||||||
...dimensions,
|
...dimensions,
|
||||||
},
|
},
|
||||||
[internalsSymbol]: {
|
internals: {
|
||||||
...node[internalsSymbol],
|
...node.internals,
|
||||||
handleBounds: {
|
handleBounds: {
|
||||||
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin),
|
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin),
|
||||||
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin),
|
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeLookup.set(node.id, newNode);
|
nodeLookup.set(node.id, newNode);
|
||||||
|
|
||||||
return newNode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return nextNodes;
|
return { hasUpdate };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function panBy({
|
export function panBy({
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ import type {
|
|||||||
OnSelectionDrag,
|
OnSelectionDrag,
|
||||||
UpdateNodePositions,
|
UpdateNodePositions,
|
||||||
Box,
|
Box,
|
||||||
|
InternalNodeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void;
|
export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
type StoreItems<OnNodeDrag> = {
|
type StoreItems<OnNodeDrag> = {
|
||||||
nodes: NodeBase[];
|
nodes: NodeBase[];
|
||||||
nodeLookup: Map<string, NodeBase>;
|
nodeLookup: Map<string, InternalNodeBase>;
|
||||||
edges: EdgeBase[];
|
edges: EdgeBase[];
|
||||||
nodeExtent: CoordinateExtent;
|
nodeExtent: CoordinateExtent;
|
||||||
snapGrid: SnapGrid;
|
snapGrid: SnapGrid;
|
||||||
@@ -130,19 +131,23 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
|
|
||||||
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
||||||
// based on its position so that the node stays at it's position relative to the selection.
|
// based on its position so that the node stays at it's position relative to the selection.
|
||||||
const adjustedNodeExtent: CoordinateExtent = [
|
let adjustedNodeExtent: CoordinateExtent = [
|
||||||
[nodeExtent[0][0], nodeExtent[0][1]],
|
[nodeExtent[0][0], nodeExtent[0][1]],
|
||||||
[nodeExtent[1][0], nodeExtent[1][1]],
|
[nodeExtent[1][0], nodeExtent[1][1]],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (dragItems.length > 1 && nodeExtent && !n.extent) {
|
if (dragItems.length > 1 && nodeExtent && !n.extent) {
|
||||||
adjustedNodeExtent[0][0] = n.computed.positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
|
const { positionAbsolute } = n.internals;
|
||||||
adjustedNodeExtent[1][0] =
|
const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
|
||||||
n.computed.positionAbsolute.x + (n.computed?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0];
|
const x2 = positionAbsolute.x + (n.computed?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0];
|
||||||
|
|
||||||
adjustedNodeExtent[0][1] = n.computed.positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
|
const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
|
||||||
adjustedNodeExtent[1][1] =
|
const y2 = positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
||||||
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
|
||||||
|
adjustedNodeExtent = [
|
||||||
|
[x1, y1],
|
||||||
|
[x2, y2],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { position, positionAbsolute } = calculateNodePosition({
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
@@ -158,7 +163,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
|
hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
|
||||||
|
|
||||||
n.position = position;
|
n.position = position;
|
||||||
n.computed.positionAbsolute = positionAbsolute;
|
n.internals.positionAbsolute = positionAbsolute;
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
@@ -208,7 +213,6 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
|
|
||||||
function startDrag(event: UseDragEvent) {
|
function startDrag(event: UseDragEvent) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
multiSelectionActive,
|
multiSelectionActive,
|
||||||
nodesDraggable,
|
nodesDraggable,
|
||||||
@@ -236,7 +240,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
|
|
||||||
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||||
lastPos = pointerPos;
|
lastPos = pointerPos;
|
||||||
dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId);
|
dragItems = getDragItems(nodeLookup, nodesDraggable, pointerPos, nodeId);
|
||||||
|
|
||||||
if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
|
if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
|
||||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { type NodeDragItem, type XYPosition, NodeBase } from '../types';
|
import { type NodeDragItem, type XYPosition, InternalNodeBase, NodeBase, NodeLookup } from '../types';
|
||||||
|
|
||||||
export function wrapSelectionDragFunc(selectionFunc?: (event: MouseEvent, nodes: NodeBase[]) => void) {
|
export function wrapSelectionDragFunc(selectionFunc?: (event: MouseEvent, nodes: NodeBase[]) => void) {
|
||||||
return (event: MouseEvent, _: NodeBase, nodes: NodeBase[]) => selectionFunc?.(event, nodes);
|
return (event: MouseEvent, _: NodeBase, nodes: NodeBase[]) => selectionFunc?.(event, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isParentSelected<NodeType extends NodeBase>(node: NodeType, nodes: NodeType[]): boolean {
|
export function isParentSelected<NodeType extends NodeBase>(node: NodeType, nodeLookup: NodeLookup): boolean {
|
||||||
if (!node.parentNode) {
|
if (!node.parentNode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNode = nodes.find((node) => node.id === node.parentNode);
|
const parentNode = nodeLookup.get(node.parentNode);
|
||||||
|
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
return false;
|
return false;
|
||||||
@@ -19,7 +19,7 @@ export function isParentSelected<NodeType extends NodeBase>(node: NodeType, node
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isParentSelected(parentNode, nodes);
|
return isParentSelected(parentNode, nodeLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
|
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
|
||||||
@@ -36,39 +36,43 @@ export function hasSelector(target: Element, selector: string, domNode: Element)
|
|||||||
|
|
||||||
// looks for all selected nodes and created a NodeDragItem for each of them
|
// looks for all selected nodes and created a NodeDragItem for each of them
|
||||||
export function getDragItems<NodeType extends NodeBase>(
|
export function getDragItems<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||||
nodesDraggable: boolean,
|
nodesDraggable: boolean,
|
||||||
mousePos: XYPosition,
|
mousePos: XYPosition,
|
||||||
nodeId?: string
|
nodeId?: string
|
||||||
): NodeDragItem[] {
|
): NodeDragItem[] {
|
||||||
return nodes
|
const dragItems: NodeDragItem[] = [];
|
||||||
.filter(
|
|
||||||
(n) =>
|
for (const [id, node] of nodeLookup) {
|
||||||
(n.selected || n.id === nodeId) &&
|
if (
|
||||||
(!n.parentNode || !isParentSelected(n, nodes)) &&
|
(node.selected || node.id === nodeId) &&
|
||||||
(n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'))
|
(!node.parentNode || !isParentSelected(node, nodeLookup)) &&
|
||||||
)
|
(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'))
|
||||||
.map((n) => ({
|
) {
|
||||||
id: n.id,
|
const internalNode = nodeLookup.get(id)!;
|
||||||
position: n.position || { x: 0, y: 0 },
|
|
||||||
distance: {
|
dragItems.push({
|
||||||
x: mousePos.x - (n.computed?.positionAbsolute?.x ?? 0),
|
id: internalNode.id,
|
||||||
y: mousePos.y - (n.computed?.positionAbsolute?.y ?? 0),
|
position: internalNode.position || { x: 0, y: 0 },
|
||||||
},
|
distance: {
|
||||||
delta: {
|
x: mousePos.x - (internalNode.internals.positionAbsolute?.x ?? 0),
|
||||||
x: 0,
|
y: mousePos.y - (internalNode.internals.positionAbsolute?.y ?? 0),
|
||||||
y: 0,
|
},
|
||||||
},
|
extent: internalNode.extent,
|
||||||
extent: n.extent,
|
parentNode: internalNode.parentNode,
|
||||||
parentNode: n.parentNode,
|
origin: internalNode.origin,
|
||||||
origin: n.origin,
|
expandParent: internalNode.expandParent,
|
||||||
expandParent: n.expandParent,
|
internals: {
|
||||||
computed: {
|
positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 },
|
||||||
positionAbsolute: n.computed?.positionAbsolute || { x: 0, y: 0 },
|
},
|
||||||
width: n.computed?.width || 0,
|
computed: {
|
||||||
height: n.computed?.height || 0,
|
width: internalNode.computed.width || 0,
|
||||||
},
|
height: internalNode.computed.height || 0,
|
||||||
}));
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dragItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns two params:
|
// returns two params:
|
||||||
@@ -91,7 +95,6 @@ export function getEventHandlerParams<NodeType extends NodeBase>({
|
|||||||
position: n.position,
|
position: n.position,
|
||||||
computed: {
|
computed: {
|
||||||
...n.computed,
|
...n.computed,
|
||||||
positionAbsolute: n.computed.positionAbsolute,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import {
|
|||||||
type HandleType,
|
type HandleType,
|
||||||
type Connection,
|
type Connection,
|
||||||
type PanBy,
|
type PanBy,
|
||||||
type NodeBase,
|
|
||||||
type Transform,
|
type Transform,
|
||||||
type ConnectingHandle,
|
type ConnectingHandle,
|
||||||
type OnConnectEnd,
|
type OnConnectEnd,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
type IsValidConnection,
|
type IsValidConnection,
|
||||||
type ConnectionHandle,
|
type ConnectionHandle,
|
||||||
|
NodeLookup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils';
|
import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils';
|
||||||
@@ -25,7 +25,7 @@ export type OnPointerDownParams = {
|
|||||||
handleId: string | null;
|
handleId: string | null;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
isTarget: boolean;
|
isTarget: boolean;
|
||||||
nodes: NodeBase[];
|
nodeLookup: NodeLookup;
|
||||||
lib: string;
|
lib: string;
|
||||||
flowId: string | null;
|
flowId: string | null;
|
||||||
edgeUpdaterType?: HandleType;
|
edgeUpdaterType?: HandleType;
|
||||||
@@ -79,7 +79,7 @@ function onPointerDown(
|
|||||||
edgeUpdaterType,
|
edgeUpdaterType,
|
||||||
isTarget,
|
isTarget,
|
||||||
domNode,
|
domNode,
|
||||||
nodes,
|
nodeLookup,
|
||||||
lib,
|
lib,
|
||||||
autoPanOnConnect,
|
autoPanOnConnect,
|
||||||
flowId,
|
flowId,
|
||||||
@@ -116,7 +116,7 @@ function onPointerDown(
|
|||||||
let handleDomNode: Element | null = null;
|
let handleDomNode: Element | null = null;
|
||||||
|
|
||||||
const handleLookup = getHandleLookup({
|
const handleLookup = getHandleLookup({
|
||||||
nodes,
|
nodeLookup,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
handleType,
|
handleType,
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import {
|
|||||||
type HandleType,
|
type HandleType,
|
||||||
type NodeHandleBounds,
|
type NodeHandleBounds,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
type NodeBase,
|
|
||||||
type ConnectionHandle,
|
type ConnectionHandle,
|
||||||
|
InternalNodeBase,
|
||||||
|
NodeLookup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { internalsSymbol } from '../constants';
|
|
||||||
|
|
||||||
// this functions collects all handles and adds an absolute position
|
// this functions collects all handles and adds an absolute position
|
||||||
// so that we can later find the closest handle to the mouse position
|
// so that we can later find the closest handle to the mouse position
|
||||||
export function getHandles(
|
export function getHandles(
|
||||||
node: NodeBase,
|
node: InternalNodeBase,
|
||||||
handleBounds: NodeHandleBounds,
|
handleBounds: NodeHandleBounds,
|
||||||
type: HandleType,
|
type: HandleType,
|
||||||
currentHandle: string
|
currentHandle: string
|
||||||
@@ -22,8 +22,8 @@ export function getHandles(
|
|||||||
id: h.id || null,
|
id: h.id || null,
|
||||||
type,
|
type,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
x: (node.computed?.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
|
x: (node.internals.positionAbsolute.x ?? 0) + h.x + h.width / 2,
|
||||||
y: (node.computed?.positionAbsolute?.y ?? 0) + h.y + h.height / 2,
|
y: (node.internals.positionAbsolute.y ?? 0) + h.y + h.height / 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -62,28 +62,30 @@ export function getClosestHandle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetHandleLookupParams = {
|
type GetHandleLookupParams = {
|
||||||
nodes: NodeBase[];
|
nodeLookup: NodeLookup;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
handleId: string | null;
|
handleId: string | null;
|
||||||
handleType: string;
|
handleType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
|
export function getHandleLookup({
|
||||||
return nodes.reduce<ConnectionHandle[]>((res, node) => {
|
nodeLookup,
|
||||||
if (node[internalsSymbol]) {
|
nodeId,
|
||||||
const { handleBounds } = node[internalsSymbol];
|
handleId,
|
||||||
let sourceHandles: ConnectionHandle[] = [];
|
handleType,
|
||||||
let targetHandles: ConnectionHandle[] = [];
|
}: GetHandleLookupParams): ConnectionHandle[] {
|
||||||
|
const connectionHandles: ConnectionHandle[] = [];
|
||||||
|
|
||||||
if (handleBounds) {
|
for (const [, node] of nodeLookup) {
|
||||||
sourceHandles = getHandles(node, handleBounds, 'source', `${nodeId}-${handleId}-${handleType}`);
|
if (node.internals.handleBounds) {
|
||||||
targetHandles = getHandles(node, handleBounds, 'target', `${nodeId}-${handleId}-${handleType}`);
|
const id = `${nodeId}-${handleId}-${handleType}`;
|
||||||
}
|
const sourceHandles = getHandles(node, node.internals.handleBounds, 'source', id);
|
||||||
|
const targetHandles = getHandles(node, node.internals.handleBounds, 'target', id);
|
||||||
res.push(...sourceHandles, ...targetHandles);
|
connectionHandles.push(...sourceHandles, ...targetHandles);
|
||||||
}
|
}
|
||||||
return res;
|
}
|
||||||
}, []);
|
|
||||||
|
return connectionHandles;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHandleType(
|
export function getHandleType(
|
||||||
|
|||||||
Reference in New Issue
Block a user