Merge pull request #4572 from xyflow/fix-extent

fix node extent
This commit is contained in:
Moritz Klack
2024-09-05 16:39:23 +02:00
committed by GitHub
17 changed files with 264 additions and 116 deletions
@@ -2,7 +2,6 @@ import { type MouseEvent, type KeyboardEvent } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import {
clampPosition,
elementSelectionKeys,
errorMessages,
getNodeDimensions,
@@ -87,10 +86,6 @@ export function NodeWrapper<NodeType extends Node>({
const nodeDimensions = getNodeDimensions(node);
const inlineDimensions = getNodeInlineStyleDimensions(node);
// TODO: clamping should happen earlier
const clampedPosition = nodeExtent
? clampPosition(internals.positionAbsolute, nodeExtent)
: internals.positionAbsolute;
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -146,7 +141,7 @@ export function NodeWrapper<NodeType extends Node>({
store.setState({
ariaLiveMessage: `Moved selected node ${event.key
.replace('Arrow', '')
.toLowerCase()}. New position, x: ${~~clampedPosition.x}, y: ${~~clampedPosition.y}`,
.toLowerCase()}. New position, x: ${~~internals.positionAbsolute.x}, y: ${~~internals.positionAbsolute.y}`,
});
moveSelectedNodes({
@@ -177,7 +172,7 @@ export function NodeWrapper<NodeType extends Node>({
ref={nodeRef}
style={{
zIndex: internals.z,
transform: `translate(${clampedPosition.x}px,${clampedPosition.y}px)`,
transform: `translate(${internals.positionAbsolute.x}px,${internals.positionAbsolute.y}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: hasDimensions ? 'visible' : 'hidden',
...node.style,
@@ -202,8 +197,8 @@ export function NodeWrapper<NodeType extends Node>({
id={id}
data={node.data}
type={nodeType}
positionAbsoluteX={clampedPosition.x}
positionAbsoluteY={clampedPosition.y}
positionAbsoluteX={internals.positionAbsolute.x}
positionAbsoluteY={internals.positionAbsolute.y}
selected={node.selected}
selectable={isSelectable}
draggable={isDraggable}
@@ -4,7 +4,7 @@ import { Provider } from '../../contexts/StoreContext';
import { createStore } from '../../store';
import { BatchProvider } from '../BatchProvider';
import type { Node, Edge } from '../../types';
import { NodeOrigin } from '@xyflow/system';
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export type ReactFlowProviderProps = {
initialNodes?: Node[];
@@ -15,6 +15,7 @@ export type ReactFlowProviderProps = {
initialHeight?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
children: ReactNode;
};
@@ -27,6 +28,7 @@ export function ReactFlowProvider({
initialHeight: height,
fitView,
nodeOrigin,
nodeExtent,
children,
}: ReactFlowProviderProps) {
const [store] = useState(() =>
@@ -39,6 +41,7 @@ export function ReactFlowProvider({
height,
fitView,
nodeOrigin,
nodeExtent,
})
);
@@ -3,7 +3,7 @@ import { useContext, type ReactNode } from 'react';
import StoreContext from '../../contexts/StoreContext';
import { ReactFlowProvider } from '../../components/ReactFlowProvider';
import type { Node, Edge } from '../../types';
import { NodeOrigin } from '@xyflow/system';
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export function Wrapper({
children,
@@ -15,6 +15,7 @@ export function Wrapper({
height,
fitView,
nodeOrigin,
nodeExtent,
}: {
children: ReactNode;
nodes?: Node[];
@@ -25,6 +26,7 @@ export function Wrapper({
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) {
const isWrapped = useContext(StoreContext);
@@ -44,6 +46,7 @@ export function Wrapper({
initialHeight={height}
fitView={fitView}
nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent}
>
{children}
</ReactFlowProvider>
@@ -160,7 +160,15 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
data-testid="rf__wrapper"
id={id}
>
<Wrapper nodes={nodes} edges={edges} width={width} height={height} fitView={fitView} nodeOrigin={nodeOrigin}>
<Wrapper
nodes={nodes}
edges={edges}
width={width}
height={height}
fitView={fitView}
nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent}
>
<GraphView<NodeType, EdgeType>
onInit={onInit}
onNodeClick={onNodeClick}
+30 -19
View File
@@ -1,6 +1,5 @@
import { createWithEqualityFn } from 'zustand/traditional';
import {
clampPosition,
getFitViewNodes,
fitView as fitViewSystem,
adoptUserNodes,
@@ -15,6 +14,7 @@ import {
ParentExpandChild,
initialConnection,
NodeOrigin,
CoordinateExtent,
} from '@xyflow/system';
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -30,6 +30,7 @@ const createStore = ({
height,
fitView,
nodeOrigin,
nodeExtent,
}: {
nodes?: Node[];
edges?: Edge[];
@@ -39,10 +40,11 @@ const createStore = ({
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) =>
createWithEqualityFn<ReactFlowState>(
(set, get) => ({
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, defaultNodes, defaultEdges }),
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
setNodes: (nodes: Node[]) => {
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
// setNodes() is called exclusively in response to user actions:
@@ -51,7 +53,12 @@ const createStore = ({
//
// 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, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true });
adoptUserNodes(nodes, nodeLookup, parentLookup, {
nodeOrigin,
nodeExtent,
elevateNodesOnSelect,
checkEquality: true,
});
set({ nodes });
},
@@ -87,6 +94,7 @@ const createStore = ({
fitViewOnInitOptions,
domNode,
nodeOrigin,
nodeExtent,
debug,
fitViewSync,
} = get();
@@ -96,14 +104,15 @@ const createStore = ({
nodeLookup,
parentLookup,
domNode,
nodeOrigin
nodeOrigin,
nodeExtent
);
if (!updatedInternals) {
return;
}
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin });
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
if (params.triggerFitView) {
// we call fitView once initially after all dimensions are set
@@ -277,24 +286,26 @@ const createStore = ({
triggerNodeChanges(nodeChanges);
triggerEdgeChanges(edgeChanges);
},
setNodeExtent: (nodeExtent) => {
const { nodeLookup } = get();
setNodeExtent: (nextNodeExtent) => {
const { nodes, nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, nodeExtent } = get();
for (const [, node] of nodeLookup) {
const positionAbsolute = clampPosition(node.internals.positionAbsolute, nodeExtent);
nodeLookup.set(node.id, {
...node,
internals: {
...node.internals,
positionAbsolute,
},
});
if (
nextNodeExtent[0][0] === nodeExtent[0][0] &&
nextNodeExtent[0][1] === nodeExtent[0][1] &&
nextNodeExtent[1][0] === nodeExtent[1][0] &&
nextNodeExtent[1][1] === nodeExtent[1][1]
) {
return;
}
set({
nodeExtent,
adoptUserNodes(nodes, nodeLookup, parentLookup, {
nodeOrigin,
nodeExtent: nextNodeExtent,
elevateNodesOnSelect,
checkEquality: false,
});
set({ nodeExtent: nextNodeExtent });
},
panBy: (delta): Promise<boolean> => {
const { transform, width, height, panZoom, translateExtent } = get();
+7 -1
View File
@@ -9,6 +9,7 @@ import {
getInternalNodesBounds,
NodeOrigin,
initialConnection,
CoordinateExtent,
} from '@xyflow/system';
import type { Edge, InternalNode, Node, ReactFlowStore } from '../types';
@@ -22,6 +23,7 @@ const getInitialState = ({
height,
fitView,
nodeOrigin,
nodeExtent,
}: {
nodes?: Node[];
edges?: Edge[];
@@ -31,18 +33,22 @@ const getInitialState = ({
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
} = {}): ReactFlowStore => {
const nodeLookup = new Map<string, InternalNode>();
const parentLookup = new Map();
const connectionLookup = new Map();
const edgeLookup = new Map();
const storeEdges = defaultEdges ?? edges ?? [];
const storeNodes = defaultNodes ?? nodes ?? [];
const storeNodeOrigin = nodeOrigin ?? [0, 0];
const storeNodeExtent = nodeExtent ?? infiniteExtent;
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
nodeOrigin: storeNodeOrigin,
nodeExtent: storeNodeExtent,
elevateNodesOnSelect: false,
});
@@ -76,7 +82,7 @@ const getInitialState = ({
minZoom: 0.5,
maxZoom: 2,
translateExtent: infiniteExtent,
nodeExtent: infiniteExtent,
nodeExtent: storeNodeExtent,
nodesSelectionActive: false,
userSelectionActive: false,
userSelectionRect: null,