diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index bf119621..244efb64 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -5,6 +5,7 @@ ## Patch changes - use correct positions for intersection helpers +- fix minimap interaction for touch devices ## 12.0.0-next.14 diff --git a/packages/react/src/hooks/useMoveSelectedNodes.ts b/packages/react/src/hooks/useMoveSelectedNodes.ts index d189642b..52503c04 100644 --- a/packages/react/src/hooks/useMoveSelectedNodes.ts +++ b/packages/react/src/hooks/useMoveSelectedNodes.ts @@ -19,7 +19,7 @@ export function useMoveSelectedNodes() { const moveSelectedNodes = useCallback((params: { direction: XYPosition; factor: number }) => { const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } = store.getState(); - const nodeUpdates = []; + const nodeUpdates = new Map(); const isSelected = selectedAndDraggable(nodesDraggable); // by default a node moves 5px on each key press @@ -56,7 +56,7 @@ export function useMoveSelectedNodes() { node.position = position; node.internals.positionAbsolute = positionAbsolute; - nodeUpdates.push(node); + nodeUpdates.set(node.id, node); } updateNodePositions(nodeUpdates); diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 50d981c2..48010a2e 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -16,7 +16,7 @@ import { import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; import getInitialState from './initialState'; -import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions, InternalNode } from '../types'; +import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions } from '../types'; const createRFStore = ({ nodes, @@ -124,27 +124,26 @@ const createRFStore = ({ } }, updateNodePositions: (nodeDragItems, dragging = false) => { - const { nodeLookup, parentLookup } = get(); const parentExpandChildren: ParentExpandChild[] = []; + const changes = []; - const changes: NodeChange[] = nodeDragItems.map((node) => { + for (const [id, dragItem] of nodeDragItems) { // @todo add expandParent to drag item so that we can get rid of the look up here - const internalNode = nodeLookup.get(node.id); const change: NodeChange = { - id: node.id, + id, type: 'position', - position: node.position, + position: dragItem.position, dragging, }; - if (internalNode?.expandParent && internalNode?.parentId && change.position) { + if (dragItem?.expandParent && dragItem?.parentId && change.position) { parentExpandChildren.push({ - id: internalNode.id, - parentId: internalNode.parentId, + id, + parentId: dragItem.parentId, rect: { - ...node.internals.positionAbsolute, - width: internalNode.measured.width!, - height: internalNode.measured.height!, + ...dragItem.internals.positionAbsolute, + width: dragItem.measured.width!, + height: dragItem.measured.height!, }, }); @@ -152,10 +151,11 @@ const createRFStore = ({ change.position.y = Math.max(0, change.position.y); } - return change; - }); + changes.push(change); + } if (parentExpandChildren.length > 0) { + const { nodeLookup, parentLookup } = get(); const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup); changes.push(...parentExpandChanges); } diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 07d4ab76..39c9cad4 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -64,14 +64,14 @@ export function createStore({ const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => { const nodeLookup = get(store.nodeLookup); - for (const nodeDragItem of nodeDragItems) { - const node = nodeLookup.get(nodeDragItem.id)?.internals.userNode; + for (const [id, dragItem] of nodeDragItems) { + const node = nodeLookup.get(id)?.internals.userNode; if (!node) { continue; } - node.position = nodeDragItem.position; + node.position = dragItem.position; node.dragging = dragging; } diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 2fd0e3ab..91a08082 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -127,7 +127,7 @@ export type SelectionRect = Rect & { export type OnError = (id: string, message: string) => void; -export type UpdateNodePositions = (dragItems: NodeDragItem[] | InternalNodeBase[], dragging?: boolean) => void; +export type UpdateNodePositions = (dragItems: Map, dragging?: boolean) => void; export type PanBy = (delta: XYPosition) => boolean; export type UpdateConnection = (params: { diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 34823b5f..6ba7fbd4 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -120,8 +120,8 @@ export type NodeDragItem = { // distance from the mouse cursor to the node when start dragging distance: XYPosition; measured: { - width: number | null; - height: number | null; + width: number; + height: number; }; internals: { positionAbsolute: XYPosition; diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index f2b662da..0c259bbc 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -205,9 +205,13 @@ export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent return extent !== undefined && extent !== 'parent'; } -export function getNodeDimensions( - node: NodeType -): { width: number; height: number } { +export function getNodeDimensions(node: { + measured?: { width?: number; height?: number }; + width?: number; + height?: number; + initialWidth?: number; + initialHeight?: number; +}): { width: number; height: number } { return { width: node.measured?.width ?? node.width ?? node.initialWidth ?? 0, height: node.measured?.height ?? node.height ?? node.initialHeight ?? 0, diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 3bf70267..e8bb5357 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -10,6 +10,7 @@ import { getViewportForBounds, isCoordinateExtent, getNodeDimensions, + getPositionWithOrigin, } from './general'; import { type Transform, @@ -25,6 +26,7 @@ import { OnBeforeDeleteBase, NodeLookup, InternalNodeBase, + NodeDragItem, } from '../types'; import { errorMessages } from '../constants'; @@ -186,7 +188,7 @@ export const getNodesBounds = ( export type GetInternalNodesBoundsParams = { nodeOrigin?: NodeOrigin; useRelativePosition?: boolean; - filter?: (node: NodeBase) => boolean; + filter?: (node: NodeBase | NodeDragItem) => boolean; }; /** @@ -194,10 +196,9 @@ export type GetInternalNodesBoundsParams = { * @internal */ export const getInternalNodesBounds = ( - nodeLookup: NodeLookup, + nodeLookup: NodeLookup | Map, params: GetInternalNodesBoundsParams = { nodeOrigin: [0, 0], - useRelativePosition: false, } ): Rect => { if (nodeLookup.size === 0) { @@ -208,12 +209,22 @@ export const getInternalNodesBounds = ( nodeLookup.forEach((node) => { if (params.filter == undefined || params.filter(node)) { - const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin); + const { width, height } = getNodeDimensions(node); + const { x, y } = getPositionWithOrigin({ + x: node.internals.positionAbsolute.x, + y: node.internals.positionAbsolute.x, + width, + height, + origin: node.origin || params.nodeOrigin, + }); + box = getBoundsOfBoxes( box, rectToBox({ - ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'], - ...getNodeDimensions(node), + x, + y, + width, + height, }) ); } diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 573b87af..e205ced7 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -7,10 +7,10 @@ import { getPointerPosition, calculateNodePosition, snapPosition, - getNodesBounds, + getInternalNodesBounds, rectToBox, } from '../utils'; -import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils'; +import { getDragItems, getEventHandlerParams, hasSelector } from './utils'; import type { NodeBase, NodeDragItem, @@ -29,7 +29,12 @@ import type { InternalNodeBase, } from '../types'; -export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void; +export type OnDrag = ( + event: MouseEvent, + dragItems: Map, + node: NodeBase, + nodes: NodeBase[] +) => void; type StoreItems = { nodes: NodeBase[]; @@ -89,7 +94,7 @@ export function XYDrag voi }: XYDragParams): XYDragInstance { let lastPos: { x: number | null; y: number | null } = { x: null, y: null }; let autoPanId = 0; - let dragItems: NodeDragItem[] = []; + let dragItems = new Map(); let autoPanStarted = false; let mousePosition: XYPosition = { x: 0, y: 0 }; let containerBounds: DOMRect | null = null; @@ -117,13 +122,13 @@ export function XYDrag voi let hasChange = false; let nodesBox: Box = { x: 0, y: 0, x2: 0, y2: 0 }; - if (dragItems.length > 1 && nodeExtent) { - const rect = getNodesBounds(dragItems as unknown as NodeBase[], { nodeOrigin }); + if (dragItems.size > 1 && nodeExtent) { + const rect = getInternalNodesBounds(dragItems, { nodeOrigin }); nodesBox = rectToBox(rect); } - dragItems = dragItems.map((n) => { - let nextPosition = { x: x - n.distance.x, y: y - n.distance.y }; + for (const [id, dragItem] of dragItems) { + let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y }; if (snapToGrid) { nextPosition = snapPosition(nextPosition, snapGrid); @@ -136,13 +141,13 @@ export function XYDrag voi [nodeExtent[1][0], nodeExtent[1][1]], ]; - if (dragItems.length > 1 && nodeExtent && !n.extent) { - const { positionAbsolute } = n.internals; + if (dragItems.size > 1 && nodeExtent && !dragItem.extent) { + const { positionAbsolute } = dragItem.internals; const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0]; - const x2 = positionAbsolute.x + (n.measured?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0]; + const x2 = positionAbsolute.x + dragItem.measured.width - nodesBox.x2 + nodeExtent[1][0]; const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1]; - const y2 = positionAbsolute.y + (n.measured?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1]; + const y2 = positionAbsolute.y + dragItem.measured.height - nodesBox.y2 + nodeExtent[1][1]; adjustedNodeExtent = [ [x1, y1], @@ -151,7 +156,7 @@ export function XYDrag voi } const { position, positionAbsolute } = calculateNodePosition({ - nodeId: n.id, + nodeId: id, nextPosition, nodeLookup, nodeExtent: adjustedNodeExtent, @@ -160,13 +165,11 @@ export function XYDrag voi }); // we want to make sure that we only fire a change event when there is a change - hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y; + hasChange = hasChange || dragItem.position.x !== position.x || dragItem.position.y !== position.y; - n.position = position; - n.internals.positionAbsolute = positionAbsolute; - - return n; - }); + dragItem.position = position; + dragItem.internals.positionAbsolute = positionAbsolute; + } if (!hasChange) { return; @@ -185,8 +188,7 @@ export function XYDrag voi onNodeDrag?.(dragEvent, currentNode, currentNodes); if (!nodeId) { - const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag); - _onSelectionDrag(dragEvent, currentNode, currentNodes); + onSelectionDrag?.(dragEvent, currentNodes); } } } @@ -242,7 +244,7 @@ export function XYDrag voi lastPos = pointerPos; dragItems = getDragItems(nodeLookup, nodesDraggable, pointerPos, nodeId); - if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) { + if (dragItems.size > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) { const [currentNode, currentNodes] = getEventHandlerParams({ nodeId, dragItems, @@ -253,8 +255,7 @@ export function XYDrag voi onNodeDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); if (!nodeId) { - const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart); - _onSelectionDragStart(event.sourceEvent as MouseEvent, currentNode, currentNodes); + onSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNodes); } } } @@ -308,7 +309,7 @@ export function XYDrag voi dragStarted = false; cancelAnimationFrame(autoPanId); - if (dragItems.length > 0) { + if (dragItems.size > 0) { const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems(); updateNodePositions(dragItems, false); @@ -324,8 +325,7 @@ export function XYDrag voi onNodeDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); if (!nodeId) { - const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop); - _onSelectionDragStop(event.sourceEvent as MouseEvent, currentNode, currentNodes); + onSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNodes); } } } diff --git a/packages/system/src/xydrag/utils.ts b/packages/system/src/xydrag/utils.ts index b1cd0597..bb836a32 100644 --- a/packages/system/src/xydrag/utils.ts +++ b/packages/system/src/xydrag/utils.ts @@ -1,9 +1,5 @@ import { type NodeDragItem, type XYPosition, InternalNodeBase, NodeBase, NodeLookup } from '../types'; -export function wrapSelectionDragFunc(selectionFunc?: (event: MouseEvent, nodes: NodeBase[]) => void) { - return (event: MouseEvent, _: NodeBase, nodes: NodeBase[]) => selectionFunc?.(event, nodes); -} - export function isParentSelected(node: NodeType, nodeLookup: NodeLookup): boolean { if (!node.parentId) { return false; @@ -40,8 +36,8 @@ export function getDragItems( nodesDraggable: boolean, mousePos: XYPosition, nodeId?: string -): NodeDragItem[] { - const dragItems: NodeDragItem[] = []; +): Map { + const dragItems = new Map(); for (const [id, node] of nodeLookup) { if ( @@ -49,29 +45,32 @@ export function getDragItems( (!node.parentId || !isParentSelected(node, nodeLookup)) && (node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')) ) { - const internalNode = nodeLookup.get(id)!; + const internalNode = nodeLookup.get(id); - dragItems.push({ - id: internalNode.id, - position: internalNode.position || { x: 0, y: 0 }, - distance: { - x: mousePos.x - internalNode.internals.positionAbsolute.x, - y: mousePos.y - internalNode.internals.positionAbsolute.y, - }, - extent: internalNode.extent, - parentId: internalNode.parentId, - origin: internalNode.origin, - expandParent: internalNode.expandParent, - internals: { - positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 }, - }, - measured: { - width: internalNode.measured.width || 0, - height: internalNode.measured.height || 0, - }, - }); + if (internalNode) { + dragItems.set(id, { + id, + position: internalNode.position || { x: 0, y: 0 }, + distance: { + x: mousePos.x - internalNode.internals.positionAbsolute.x, + y: mousePos.y - internalNode.internals.positionAbsolute.y, + }, + extent: internalNode.extent, + parentId: internalNode.parentId, + origin: internalNode.origin, + expandParent: internalNode.expandParent, + internals: { + positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 }, + }, + measured: { + width: internalNode.measured.width ?? 0, + height: internalNode.measured.height ?? 0, + }, + }); + } } } + return dragItems; } @@ -84,20 +83,32 @@ export function getEventHandlerParams({ nodeLookup, }: { nodeId?: string; - dragItems: NodeDragItem[]; + dragItems: Map; nodeLookup: Map; }): [NodeType, NodeType[]] { - const nodesFromDragItems: NodeType[] = dragItems.map((n) => { - const node = nodeLookup.get(n.id)!; + const nodesFromDragItems: NodeType[] = []; - return { + for (const [id, dragItem] of dragItems) { + const node = nodeLookup.get(id); + + if (node) { + nodesFromDragItems.push({ + ...node, + position: dragItem.position, + }); + } + } + + if (!nodeId) { + return [nodesFromDragItems[0], nodesFromDragItems]; + } + + const node = nodeLookup.get(nodeId)!; + return [ + { ...node, - position: n.position, - measured: { - ...n.measured, - }, - }; - }); - - return [nodeId ? nodesFromDragItems.find((n) => n.id === nodeId)! : nodesFromDragItems[0], nodesFromDragItems]; + position: dragItems.get(nodeId)?.position || node.position, + }, + nodesFromDragItems, + ]; }