diff --git a/packages/core/src/components/Nodes/NodeWrapper.ts b/packages/core/src/components/Nodes/NodeWrapper.ts index 1373c780..b0764588 100644 --- a/packages/core/src/components/Nodes/NodeWrapper.ts +++ b/packages/core/src/components/Nodes/NodeWrapper.ts @@ -172,7 +172,7 @@ const NodeWrapper = defineComponent({ }) } }, - { immediate: true, flush: 'post' }, + { immediate: true, flush: 'pre' }, ) }) diff --git a/packages/core/src/composables/useKeyPress.ts b/packages/core/src/composables/useKeyPress.ts index 6132869c..1c194cb3 100644 --- a/packages/core/src/composables/useKeyPress.ts +++ b/packages/core/src/composables/useKeyPress.ts @@ -75,6 +75,7 @@ function useKeyOrCode(code: string, keysToWatch: string | string[]) { * @internal * @param keyFilter - Can be a boolean, a string or an array of strings. If it's a boolean, it will always return that value. If it's a string, it will return true if the key is pressed. If it's an array of strings, it will return true if any of the keys are pressed, or a combination is pressed (e.g. ['ctrl+a', 'ctrl+b']) * @param onChange - Callback function that will be called when the key state changes + * @param options - Options object */ export function useKeyPress( keyFilter: MaybeRefOrGetter, diff --git a/packages/core/src/container/EdgeRenderer/MarkerDefinitions.vue b/packages/core/src/container/EdgeRenderer/MarkerDefinitions.vue index 36eab17e..75eff032 100644 --- a/packages/core/src/container/EdgeRenderer/MarkerDefinitions.vue +++ b/packages/core/src/container/EdgeRenderer/MarkerDefinitions.vue @@ -1,6 +1,6 @@ diff --git a/packages/core/src/container/Pane/Pane.vue b/packages/core/src/container/Pane/Pane.vue index 1fd92936..aae5f9b1 100644 --- a/packages/core/src/container/Pane/Pane.vue +++ b/packages/core/src/container/Pane/Pane.vue @@ -50,15 +50,14 @@ useKeyPress( return } - const nodesToRemove = getNodes.value.reduce((res, node) => { - if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) { - res.push(node) + const nodesToRemove: GraphNode[] = [] + for (const node of getNodes.value) { + if (!node.selected && node.parentNode && nodesToRemove.some((n) => n.id === node.parentNode)) { + nodesToRemove.push(node) } else if (node.selected) { - res.push(node) + nodesToRemove.push(node) } - - return res - }, []) + } if (nodesToRemove || getSelectedEdges.value) { if (getSelectedEdges.value.length > 0) { diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 44554125..736d1c7c 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -141,7 +141,7 @@ export function useActions( const style = window.getComputedStyle(viewportNode) const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform) - const changes: NodeDimensionChange[] = Array.from({ length: updates.length }) + const changes: NodeDimensionChange[] = [] for (let i = 0; i < updates.length; ++i) { const update = updates[i] @@ -378,7 +378,7 @@ export function useActions( return } - state.nodes = createGraphNodes(nextNodes, state.nodes, findNode, state.hooks.error.trigger) + state.nodes = createGraphNodes(nextNodes, findNode, state.hooks.error.trigger) } const setEdges: Actions['setEdges'] = (edges) => { @@ -419,7 +419,7 @@ export function useActions( let nextNodes = nodes instanceof Function ? nodes(state.nodes) : nodes nextNodes = Array.isArray(nextNodes) ? nextNodes : [nextNodes] - const graphNodes = createGraphNodes(nextNodes, state.nodes, findNode, state.hooks.error.trigger) + const graphNodes = createGraphNodes(nextNodes, findNode, state.hooks.error.trigger) const changes: NodeAddChange[] = [] for (const node of graphNodes) { @@ -665,7 +665,6 @@ export function useActions( return [nodeRect, node, isRectObj] } - // todo: rename to `findIntersectingNodes` const getIntersectingNodes: Actions['getIntersectingNodes'] = (nodeOrRect, partially = true, nodes = state.nodes) => { const [nodeRect, node, isRect] = getNodeRect(nodeOrRect) diff --git a/packages/core/src/store/state.ts b/packages/core/src/store/state.ts index d650cee6..ba6068a6 100644 --- a/packages/core/src/store/state.ts +++ b/packages/core/src/store/state.ts @@ -8,9 +8,7 @@ export function useState(): State { return { vueFlowRef: null, viewportRef: null, - // todo: change this to a Set nodes: [], - // todo: change this to a Set edges: [], connectionLookup: new Map(), nodeTypes: {}, diff --git a/packages/core/src/types/flow.ts b/packages/core/src/types/flow.ts index 1c6b53bc..08439b49 100644 --- a/packages/core/src/types/flow.ts +++ b/packages/core/src/types/flow.ts @@ -23,7 +23,10 @@ import type { VueFlowStore } from './store' // todo: should be object type export type ElementData = any -/** A flow element (after parsing into state) */ +/** + * @deprecated - will be removed in the next major version + * A flow element (after parsing into state) + */ export type FlowElement< NodeData = ElementData, EdgeData = ElementData, @@ -31,6 +34,10 @@ export type FlowElement< EdgeEvents extends Record = any, > = GraphNode | GraphEdge +/** + * @deprecated - will be removed in the next major version + * An array of flow elements (after parsing into state) + */ export type FlowElements< NodeData = ElementData, EdgeData = ElementData, @@ -54,6 +61,7 @@ export type Elements< > = Element[] export type MaybeElement = Node | Edge | Connection | FlowElement | Element + export interface CustomThemeVars { [key: string]: string | number | undefined } @@ -82,13 +90,11 @@ export enum Position { Bottom = 'bottom', } -// todo: Rename to `Point` export interface XYPosition { x: number y: number } -// todo: Rename to `AbsolutePoint` export type XYZPosition = XYPosition & { z: number } export interface Dimensions { diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 7634b486..67312ff5 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -69,22 +69,18 @@ export function getHostForElement(element: HTMLElement): Document { return window.document } -// todo: refactor generic to use MaybeElement export function isEdge(element: MaybeElement): element is Edge { return element && typeof element === 'object' && 'id' in element && 'source' in element && 'target' in element } -// todo: refactor generic to use MaybeElement export function isGraphEdge(element: MaybeElement): element is GraphEdge { return isEdge(element) && 'sourceNode' in element && 'targetNode' in element } -// todo: refactor generic to use MaybeElement export function isNode(element: MaybeElement): element is Node { return element && typeof element === 'object' && 'id' in element && 'position' in element && !isEdge(element) } -// todo: refactor generic to use MaybeElement export function isGraphNode(element: MaybeElement): element is GraphNode { return isNode(element) && 'computedPosition' in element } @@ -105,15 +101,15 @@ export function parseNode(node: Node, existingNode?: GraphNode, parentNode?: str width: 0, height: 0, }), + computedPosition: markRaw({ + z: 0, + ...node.position, + }), // todo: shouldn't be defined initially, as we want to use handleBounds to check if a node was actually initialized or not handleBounds: { source: [], target: [], }, - computedPosition: markRaw({ - z: 0, - ...node.position, - }), draggable: undefined, selectable: undefined, connectable: undefined, @@ -356,17 +352,23 @@ export function getBoundsofRects(rect1: Rect, rect2: Rect) { } export function getRectOfNodes(nodes: GraphNode[]) { - const box = nodes.reduce( - (currBox, { computedPosition = { x: 0, y: 0 }, dimensions = { width: 0, height: 0 } } = {} as any) => - getBoundsOfBoxes( - currBox, - rectToBox({ - ...computedPosition, - ...dimensions, - } as Rect), - ), - { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, x2: Number.NEGATIVE_INFINITY, y2: Number.NEGATIVE_INFINITY }, - ) + let box: Box = { + x: Number.POSITIVE_INFINITY, + y: Number.POSITIVE_INFINITY, + x2: Number.NEGATIVE_INFINITY, + y2: Number.NEGATIVE_INFINITY, + } + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i] + box = getBoundsOfBoxes( + box, + rectToBox({ + ...node.computedPosition, + ...node.dimensions, + } as Rect), + ) + } return boxToRect(box) } diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts index 001201ac..1b1af450 100644 --- a/packages/core/src/utils/handle.ts +++ b/packages/core/src/utils/handle.ts @@ -222,8 +222,12 @@ interface GetHandleLookupParams { } export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) { - return nodes.reduce((res, node) => { + const handleLookup: ConnectionHandle[] = [] + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i] + const { handleBounds } = node + let sourceHandles: ConnectionHandle[] = [] let targetHandles: ConnectionHandle[] = [] @@ -232,9 +236,10 @@ export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHand targetHandles = getHandles(node, handleBounds, 'target', `${nodeId}-${handleId}-${handleType}`) } - res.push(...sourceHandles, ...targetHandles) - return res - }, []) + handleLookup.push(...sourceHandles, ...targetHandles) + } + + return handleLookup } export function getHandleType(edgeUpdaterType: HandleType | undefined, handleDomNode: Element | null): HandleType | null { diff --git a/packages/core/src/utils/store.ts b/packages/core/src/utils/store.ts index 3d0ae197..2cb7a866 100644 --- a/packages/core/src/utils/store.ts +++ b/packages/core/src/utils/store.ts @@ -89,22 +89,18 @@ export function updateEdgeAction( return newEdge } -export function createGraphNodes( - nodes: Node[], - currGraphNodes: GraphNode[], - findNode: Actions['findNode'], - triggerError: State['hooks']['error']['trigger'], -) { +export function createGraphNodes(nodes: Node[], findNode: Actions['findNode'], triggerError: State['hooks']['error']['trigger']) { const parentNodes: Record = {} - const nextNodes = nodes.reduce((nextNodes, node, currentIndex) => { - // make sure we don't try to add invalid nodes + const nextNodes: GraphNode[] = [] + for (let i = 0; i < nodes.length; ++i) { + const node = nodes[i] + if (!isNode(node)) { triggerError( - new VueFlowError(ErrorCode.NODE_INVALID, (node as undefined | Record)?.id) || - `[ID UNKNOWN|INDEX ${currentIndex}]`, + new VueFlowError(ErrorCode.NODE_INVALID, (node as undefined | Record)?.id) || `[ID UNKNOWN|INDEX ${i}]`, ) - return nextNodes + continue } const parsed = parseNode(node, findNode(node.id), node.parentNode) @@ -113,13 +109,11 @@ export function createGraphNodes( parentNodes[node.parentNode] = true } - return nextNodes.concat(parsed) - }, [] as GraphNode[]) - - const allNodes = [...nextNodes, ...currGraphNodes] + nextNodes[i] = parsed + } for (const node of nextNodes) { - const parentNode = allNodes.find((n) => n.id === node.parentNode) + const parentNode = findNode(node.parentNode) || nextNodes.find((n) => n.id === node.parentNode) if (node.parentNode && !parentNode) { triggerError(new VueFlowError(ErrorCode.NODE_MISSING_PARENT, node.id, node.parentNode))