@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user