Merge branch 'next' into refactor/xy-drag-use-map

This commit is contained in:
moklick
2024-04-16 18:00:32 +02:00
16 changed files with 386 additions and 318 deletions
@@ -9,6 +9,10 @@ import {
type NodeChange,
type NodeDimensionChange,
type NodePositionChange,
handleExpandParent,
evaluateAbsolutePosition,
ParentExpandChild,
XYPosition,
} from '@xyflow/system';
import { useStoreApi } from '../../hooks/useStore';
@@ -62,24 +66,49 @@ function ResizeControl({
};
},
onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => {
const { triggerNodeChanges } = store.getState();
const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState();
const changes: NodeChange[] = [];
const nextPosition = { x: change.x, y: change.y };
if (change.isXPosChange || change.isYPosChange) {
const positionChange: NodePositionChange = {
id,
type: 'position',
position: {
x: change.x,
y: change.y,
const node = nodeLookup.get(id);
if (node && node.expandParent && node.parentId) {
const child: ParentExpandChild = {
id: node.id,
parentId: node.parentId,
rect: {
width: change.width ?? node.measured.width!,
height: change.height ?? node.measured.height!,
...evaluateAbsolutePosition(
{
x: change.x ?? node.position.x,
y: change.y ?? node.position.y,
},
node.parentId,
nodeLookup,
node.origin ?? nodeOrigin
),
},
};
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
changes.push(...parentExpandChanges);
// when the parent was expanded by the child node, its position will be clamped at 0,0
nextPosition.x = change.x ? Math.max(0, change.x) : undefined;
nextPosition.y = change.y ? Math.max(0, change.y) : undefined;
}
if (nextPosition.x !== undefined && nextPosition.y !== undefined) {
const positionChange: NodePositionChange = {
id,
type: 'position',
position: { ...(nextPosition as XYPosition) },
};
changes.push(positionChange);
}
if (change.isWidthChange || change.isHeightChange) {
if (change.width !== undefined && change.height !== undefined) {
const dimensionChange: NodeDimensionChange = {
id,
type: 'dimensions',
@@ -42,21 +42,14 @@ export function NodeWrapper<NodeType extends Node>({
nodeOrigin,
onError,
}: NodeWrapperProps<NodeType>) {
const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
const { node, internals, isParent } = useStore((s) => {
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
const positionAbsolute = nodeExtent
? clampPosition(node.internals.positionAbsolute, nodeExtent)
: node.internals.positionAbsolute || { x: 0, y: 0 };
const isParent = s.parentLookup.has(id);
return {
node,
// we are mutating positionAbsolute, z and isParent attributes for sub flows
// so we we need to force a re-render when some change
positionAbsoluteX: positionAbsolute.x,
positionAbsoluteY: positionAbsolute.y,
zIndex: node.internals.z,
isParent: node.internals.isParent,
internals: node.internals,
isParent,
};
}, shallow);
@@ -140,10 +133,15 @@ export function NodeWrapper<NodeType extends Node>({
return null;
}
const positionAbsoluteOrigin = getPositionWithOrigin({
x: positionAbsoluteX,
y: positionAbsoluteY,
...nodeDimensions,
const positionAbsolute = nodeExtent
? clampPosition(node.internals.positionAbsolute, nodeExtent)
: node.internals.positionAbsolute || { x: 0, y: 0 };
const positionWithOrigin = getPositionWithOrigin({
x: positionAbsolute.x,
y: positionAbsolute.y,
width: nodeDimensions.width,
height: nodeDimensions.height,
origin: node.origin || nodeOrigin,
});
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -190,7 +188,7 @@ export function NodeWrapper<NodeType extends Node>({
store.setState({
ariaLiveMessage: `Moved selected node ${event.key
.replace('Arrow', '')
.toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
.toLowerCase()}. New position, x: ${~~positionAbsolute.x}, y: ${~~positionAbsolute.y}`,
});
moveSelectedNodes({
@@ -220,8 +218,8 @@ export function NodeWrapper<NodeType extends Node>({
])}
ref={nodeRef}
style={{
zIndex,
transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
zIndex: internals.z,
transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: initialized ? 'visible' : 'hidden',
...node.style,
@@ -246,15 +244,15 @@ export function NodeWrapper<NodeType extends Node>({
id={id}
data={node.data}
type={nodeType}
positionAbsoluteX={positionAbsoluteX}
positionAbsoluteY={positionAbsoluteY}
positionAbsoluteX={positionAbsolute.x}
positionAbsoluteY={positionAbsolute.y}
selected={node.selected}
isConnectable={isConnectable}
sourcePosition={node.sourcePosition}
targetPosition={node.targetPosition}
dragging={dragging}
dragHandle={node.dragHandle}
zIndex={zIndex}
zIndex={internals.z}
{...nodeDimensions}
/>
</Provider>
+15 -4
View File
@@ -1,6 +1,6 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import {
evaluateNodePosition,
evaluateAbsolutePosition,
getElementsToRemove,
getOverlappingArea,
isRectObject,
@@ -232,10 +232,21 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const getNodeRect = useCallback((node: NodeType | { id: string }): Rect | null => {
const { nodeLookup, nodeOrigin } = store.getState();
const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!;
const nodeWithPos = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin);
return nodeWithPos ? nodeToRect(nodeWithPos) : null;
const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!;
const position = nodeToUse.parentId
? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin)
: nodeToUse.position;
const nodeWithPosition = {
id: nodeToUse.id,
position,
width: nodeToUse.measured?.width ?? nodeToUse.width,
height: nodeToUse.measured?.height ?? nodeToUse.height,
data: nodeToUse.data,
};
return nodeToRect(nodeWithPosition);
}, []);
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
+24 -15
View File
@@ -7,10 +7,11 @@ import {
panBy as panBySystem,
updateNodeInternals as updateNodeInternalsSystem,
updateConnectionLookup,
handleParentExpand,
handleExpandParent,
NodeChange,
EdgeSelectionChange,
NodeSelectionChange,
ParentExpandChild,
} from '@xyflow/system';
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -38,14 +39,14 @@ const createRFStore = ({
(set, get) => ({
...getInitialState({ nodes, edges, width, height, fitView, defaultNodes, defaultEdges }),
setNodes: (nodes: Node[]) => {
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
// setNodes() is called exclusively in response to user actions:
// - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
// - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
//
// When this happens, we take the note objects passed by the user and extend them with fields
// relevant for internal React Flow operations.
adoptUserNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true });
set({ nodes });
},
@@ -76,6 +77,7 @@ const createRFStore = ({
onNodesChange,
fitView,
nodeLookup,
parentLookup,
fitViewOnInit,
fitViewDone,
fitViewOnInitOptions,
@@ -84,7 +86,13 @@ const createRFStore = ({
debug,
} = get();
const { changes, updatedInternals } = updateNodeInternalsSystem(updates, nodeLookup, domNode, nodeOrigin);
const { changes, updatedInternals } = updateNodeInternalsSystem(
updates,
nodeLookup,
parentLookup,
domNode,
nodeOrigin
);
if (!updatedInternals) {
return;
@@ -116,8 +124,8 @@ const createRFStore = ({
}
},
updateNodePositions: (nodeDragItems, dragging = false) => {
const { nodeLookup } = get();
const triggerChangeNodes: InternalNode[] = [];
const { nodeLookup, parentLookup } = get();
const parentExpandChildren: ParentExpandChild[] = [];
const changes: NodeChange[] = nodeDragItems.map((node) => {
// @todo add expandParent to drag item so that we can get rid of the look up here
@@ -129,13 +137,14 @@ const createRFStore = ({
dragging,
};
if (internalNode?.expandParent && change.position) {
triggerChangeNodes.push({
...internalNode,
position: change.position,
internals: {
...internalNode.internals,
positionAbsolute: node.internals.positionAbsolute,
if (internalNode?.expandParent && internalNode?.parentId && change.position) {
parentExpandChildren.push({
id: internalNode.id,
parentId: internalNode.parentId,
rect: {
...node.internals.positionAbsolute,
width: internalNode.measured.width!,
height: internalNode.measured.height!,
},
});
@@ -146,8 +155,8 @@ const createRFStore = ({
return change;
});
if (triggerChangeNodes.length > 0) {
const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup);
if (parentExpandChildren.length > 0) {
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup);
changes.push(...parentExpandChanges);
}
+3 -1
View File
@@ -29,13 +29,14 @@ const getInitialState = ({
fitView?: boolean;
} = {}): ReactFlowStore => {
const nodeLookup = new Map();
const parentLookup = new Map();
const connectionLookup = new Map();
const edgeLookup = new Map();
const storeEdges = defaultEdges ?? edges ?? [];
const storeNodes = defaultNodes ?? nodes ?? [];
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
adoptUserNodes(storeNodes, nodeLookup, {
adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
nodeOrigin: [0, 0],
elevateNodesOnSelect: false,
});
@@ -59,6 +60,7 @@ const getInitialState = ({
transform,
nodes: storeNodes,
nodeLookup,
parentLookup,
edges: storeEdges,
edgeLookup,
connectionLookup,
+1
View File
@@ -54,6 +54,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
transform: Transform;
nodes: NodeType[];
nodeLookup: NodeLookup<InternalNode<NodeType>>;
parentLookup: Map<string, InternalNode<NodeType>[]>;
edges: Edge[];
edgeLookup: EdgeLookup<EdgeType>;
connectionLookup: ConnectionLookup;