clamp position on adoptUserNode

This commit is contained in:
peterkogo
2024-08-22 11:00:16 +02:00
parent 04774e5011
commit ea8b9e0050
5 changed files with 32 additions and 15 deletions

View File

@@ -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,
@@ -88,10 +87,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, nodeDimensions)
: internals.positionAbsolute;
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -147,7 +142,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({
@@ -178,7 +173,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,
@@ -203,8 +198,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}

View File

@@ -54,7 +54,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 });
},

View File

@@ -114,7 +114,7 @@ export const getInitialStore = ({
return {
flowId: writable<string | null>(null),
nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin),
nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin, storeNodeExtent),
nodeLookup: readable<NodeLookup<InternalNode>>(nodeLookup),
parentLookup: readable<ParentLookup<InternalNode>>(parentLookup),
edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),

View File

@@ -15,7 +15,9 @@ import {
type EdgeLookup,
type NodeLookup,
type ParentLookup,
type NodeOrigin
type NodeOrigin,
infiniteExtent,
type CoordinateExtent
} from '@xyflow/system';
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, InternalNode, Node } from '$lib/types';
@@ -131,7 +133,8 @@ export const createNodesStore = (
nodes: Node[],
nodeLookup: NodeLookup<InternalNode>,
parentLookup: ParentLookup<InternalNode>,
nodeOrigin: NodeOrigin = [0, 0]
nodeOrigin: NodeOrigin = [0, 0],
nodeExtent: CoordinateExtent = infiniteExtent
): {
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
update: (this: void, updater: Updater<Node[]>) => void;
@@ -148,6 +151,7 @@ export const createNodesStore = (
adoptUserNodes(nds, nodeLookup, parentLookup, {
elevateNodesOnSelect,
nodeOrigin,
nodeExtent,
defaults,
checkEquality: false
});

View File

@@ -18,7 +18,14 @@ import {
ParentLookup,
} from '../types';
import { getDimensions, getHandleBounds } from './dom';
import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general';
import {
clampPosition,
getBoundsOfRects,
getNodeDimensions,
isCoordinateExtent,
isNumeric,
nodeToRect,
} from './general';
import { getNodePositionWithOrigin } from './graph';
import { ParentExpandChild } from './types';
@@ -28,6 +35,7 @@ const defaultOptions = {
elevateNodesOnSelect: true,
defaults: {},
};
const adoptUserNodesDefaultOptions = {
...defaultOptions,
checkEquality: true,
@@ -63,6 +71,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
options?: UpdateNodesOptions<NodeType>
) {
const _options = { ...adoptUserNodesDefaultOptions, ...options };
const tmpLookup = new Map(nodeLookup);
nodeLookup.clear();
parentLookup.clear();
@@ -74,6 +83,10 @@ export function adoptUserNodes<NodeType extends NodeBase>(
if (_options.checkEquality && userNode === internalNode?.internals.userNode) {
nodeLookup.set(userNode.id, internalNode);
} else {
const positionOrigin = getNodePositionWithOrigin(userNode, _options.nodeOrigin);
const extent = isCoordinateExtent(userNode.extent) ? userNode.extent : _options.nodeExtent;
const positionAbsolute = clampPosition(positionOrigin, extent, getNodeDimensions(userNode));
internalNode = {
..._options.defaults,
...userNode,
@@ -82,7 +95,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
height: userNode.measured?.height,
},
internals: {
positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin),
positionAbsolute,
// if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured
handleBounds: !userNode.measured ? undefined : internalNode?.internals.handleBounds,
z: calculateZ(userNode, selectedNodeZ),