Merge branch 'next' into refactor/xy-drag-use-map
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>>(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user