added nodeExtent to Svelte Flow and React Flow Provider

This commit is contained in:
peterkogo
2024-08-22 10:34:52 +02:00
parent 535744efa1
commit 04774e5011
11 changed files with 61 additions and 15 deletions
@@ -90,7 +90,7 @@ export function NodeWrapper<NodeType extends Node>({
const inlineDimensions = getNodeInlineStyleDimensions(node); const inlineDimensions = getNodeInlineStyleDimensions(node);
// TODO: clamping should happen earlier // TODO: clamping should happen earlier
const clampedPosition = nodeExtent const clampedPosition = nodeExtent
? clampPosition(internals.positionAbsolute, nodeExtent, node.measured) ? clampPosition(internals.positionAbsolute, nodeExtent, nodeDimensions)
: internals.positionAbsolute; : internals.positionAbsolute;
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -4,7 +4,7 @@ import { Provider } from '../../contexts/StoreContext';
import { createStore } from '../../store'; import { createStore } from '../../store';
import { BatchProvider } from '../BatchProvider'; import { BatchProvider } from '../BatchProvider';
import type { Node, Edge } from '../../types'; import type { Node, Edge } from '../../types';
import { NodeOrigin } from '@xyflow/system'; import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export type ReactFlowProviderProps = { export type ReactFlowProviderProps = {
initialNodes?: Node[]; initialNodes?: Node[];
@@ -15,6 +15,7 @@ export type ReactFlowProviderProps = {
initialHeight?: number; initialHeight?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
children: ReactNode; children: ReactNode;
}; };
@@ -27,6 +28,7 @@ export function ReactFlowProvider({
initialHeight: height, initialHeight: height,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent,
children, children,
}: ReactFlowProviderProps) { }: ReactFlowProviderProps) {
const [store] = useState(() => const [store] = useState(() =>
@@ -39,6 +41,7 @@ export function ReactFlowProvider({
height, height,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent,
}) })
); );
@@ -3,7 +3,7 @@ import { useContext, type ReactNode } from 'react';
import StoreContext from '../../contexts/StoreContext'; import StoreContext from '../../contexts/StoreContext';
import { ReactFlowProvider } from '../../components/ReactFlowProvider'; import { ReactFlowProvider } from '../../components/ReactFlowProvider';
import type { Node, Edge } from '../../types'; import type { Node, Edge } from '../../types';
import { NodeOrigin } from '@xyflow/system'; import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export function Wrapper({ export function Wrapper({
children, children,
@@ -15,6 +15,7 @@ export function Wrapper({
height, height,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent,
}: { }: {
children: ReactNode; children: ReactNode;
nodes?: Node[]; nodes?: Node[];
@@ -25,6 +26,7 @@ export function Wrapper({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) { }) {
const isWrapped = useContext(StoreContext); const isWrapped = useContext(StoreContext);
@@ -44,6 +46,7 @@ export function Wrapper({
initialHeight={height} initialHeight={height}
fitView={fitView} fitView={fitView}
nodeOrigin={nodeOrigin} nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent}
> >
{children} {children}
</ReactFlowProvider> </ReactFlowProvider>
@@ -160,7 +160,15 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
data-testid="rf__wrapper" data-testid="rf__wrapper"
id={id} 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> <GraphView<NodeType, EdgeType>
onInit={onInit} onInit={onInit}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
+4 -1
View File
@@ -15,6 +15,7 @@ import {
ParentExpandChild, ParentExpandChild,
initialConnection, initialConnection,
NodeOrigin, NodeOrigin,
CoordinateExtent,
} from '@xyflow/system'; } from '@xyflow/system';
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -30,6 +31,7 @@ const createStore = ({
height, height,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent,
}: { }: {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -39,10 +41,11 @@ const createStore = ({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) => }) =>
createWithEqualityFn<ReactFlowState>( createWithEqualityFn<ReactFlowState>(
(set, get) => ({ (set, get) => ({
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, defaultNodes, defaultEdges }), ...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get(); const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
// setNodes() is called exclusively in response to user actions: // setNodes() is called exclusively in response to user actions:
+7 -1
View File
@@ -9,6 +9,7 @@ import {
getInternalNodesBounds, getInternalNodesBounds,
NodeOrigin, NodeOrigin,
initialConnection, initialConnection,
CoordinateExtent,
} from '@xyflow/system'; } from '@xyflow/system';
import type { Edge, InternalNode, Node, ReactFlowStore } from '../types'; import type { Edge, InternalNode, Node, ReactFlowStore } from '../types';
@@ -22,6 +23,7 @@ const getInitialState = ({
height, height,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent,
}: { }: {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -31,18 +33,22 @@ const getInitialState = ({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
} = {}): ReactFlowStore => { } = {}): ReactFlowStore => {
const nodeLookup = new Map<string, InternalNode>(); const nodeLookup = new Map<string, InternalNode>();
const parentLookup = new Map(); const parentLookup = new Map();
const connectionLookup = new Map(); const connectionLookup = new Map();
const edgeLookup = new Map(); const edgeLookup = new Map();
const storeEdges = defaultEdges ?? edges ?? []; const storeEdges = defaultEdges ?? edges ?? [];
const storeNodes = defaultNodes ?? nodes ?? []; const storeNodes = defaultNodes ?? nodes ?? [];
const storeNodeOrigin = nodeOrigin ?? [0, 0]; const storeNodeOrigin = nodeOrigin ?? [0, 0];
const storeNodeExtent = nodeExtent ?? infiniteExtent;
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
adoptUserNodes(storeNodes, nodeLookup, parentLookup, { adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
nodeOrigin: storeNodeOrigin, nodeOrigin: storeNodeOrigin,
nodeExtent: storeNodeExtent,
elevateNodesOnSelect: false, elevateNodesOnSelect: false,
}); });
@@ -76,7 +82,7 @@ const getInitialState = ({
minZoom: 0.5, minZoom: 0.5,
maxZoom: 2, maxZoom: 2,
translateExtent: infiniteExtent, translateExtent: infiniteExtent,
nodeExtent: infiniteExtent, nodeExtent: storeNodeExtent,
nodesSelectionActive: false, nodesSelectionActive: false,
userSelectionActive: false, userSelectionActive: false,
userSelectionRect: null, userSelectionRect: null,
@@ -53,6 +53,7 @@
export let onMoveEnd: $$Props['onMoveEnd'] = undefined; export let onMoveEnd: $$Props['onMoveEnd'] = undefined;
export let isValidConnection: $$Props['isValidConnection'] = undefined; export let isValidConnection: $$Props['isValidConnection'] = undefined;
export let translateExtent: $$Props['translateExtent'] = undefined; export let translateExtent: $$Props['translateExtent'] = undefined;
export let nodeExtent: $$Props['nodeExtent'] = undefined;
export let onlyRenderVisibleElements: $$Props['onlyRenderVisibleElements'] = undefined; export let onlyRenderVisibleElements: $$Props['onlyRenderVisibleElements'] = undefined;
export let panOnScrollMode: $$Props['panOnScrollMode'] = PanOnScrollMode.Free; export let panOnScrollMode: $$Props['panOnScrollMode'] = PanOnScrollMode.Free;
export let preventScrolling: $$Props['preventScrolling'] = true; export let preventScrolling: $$Props['preventScrolling'] = true;
@@ -102,7 +103,8 @@
width, width,
height, height,
fitView, fitView,
nodeOrigin nodeOrigin,
nodeExtent
}); });
onMount(() => { onMount(() => {
@@ -206,6 +206,12 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
* @example [[-1000, -10000], [1000, 1000]] * @example [[-1000, -10000], [1000, 1000]]
*/ */
translateExtent?: CoordinateExtent; translateExtent?: CoordinateExtent;
/** By default the nodes can be placed anywhere. You can use this prop to set a boundary.
*
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
* @example [[-1000, -10000], [1000, 1000]]
*/
nodeExtent?: CoordinateExtent;
/** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow. /** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
* @default true * @default true
*/ */
+9 -4
View File
@@ -37,7 +37,8 @@ export function createStore({
width, width,
height, height,
fitView: fitViewOnCreate, fitView: fitViewOnCreate,
nodeOrigin nodeOrigin,
nodeExtent
}: { }: {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -45,6 +46,7 @@ export function createStore({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}): SvelteFlowStore { }): SvelteFlowStore {
const store = getInitialStore({ const store = getInitialStore({
nodes, nodes,
@@ -52,7 +54,8 @@ export function createStore({
width, width,
height, height,
fitView: fitViewOnCreate, fitView: fitViewOnCreate,
nodeOrigin nodeOrigin,
nodeExtent
}); });
function setNodeTypes(nodeTypes: NodeTypes) { function setNodeTypes(nodeTypes: NodeTypes) {
@@ -482,7 +485,8 @@ export function createStoreContext({
width, width,
height, height,
fitView, fitView,
nodeOrigin nodeOrigin,
nodeExtent
}: { }: {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -490,8 +494,9 @@ export function createStoreContext({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) { }) {
const store = createStore({ nodes, edges, width, height, fitView, nodeOrigin }); const store = createStore({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent });
setContext(key, { setContext(key, {
getStore: () => store getStore: () => store
+11 -4
View File
@@ -75,7 +75,8 @@ export const getInitialStore = ({
width, width,
height, height,
fitView, fitView,
nodeOrigin nodeOrigin,
nodeExtent
}: { }: {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -83,17 +84,23 @@ export const getInitialStore = ({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) => { }) => {
const nodeLookup: NodeLookup = new Map(); const nodeLookup: NodeLookup = new Map();
const parentLookup = new Map(); const parentLookup = new Map();
const connectionLookup = new Map();
const edgeLookup = new Map();
const storeNodeOrigin = nodeOrigin ?? [0, 0]; const storeNodeOrigin = nodeOrigin ?? [0, 0];
const storeNodeExtent = nodeExtent ?? infiniteExtent;
adoptUserNodes(nodes, nodeLookup, parentLookup, { adoptUserNodes(nodes, nodeLookup, parentLookup, {
nodeExtent: storeNodeExtent,
nodeOrigin: storeNodeOrigin, nodeOrigin: storeNodeOrigin,
elevateNodesOnSelect: false, elevateNodesOnSelect: false,
checkEquality: false checkEquality: false
}); });
const connectionLookup = new Map();
const edgeLookup = new Map();
updateConnectionLookup(connectionLookup, edgeLookup, edges); updateConnectionLookup(connectionLookup, edgeLookup, edges);
let viewport: Viewport = { x: 0, y: 0, zoom: 1 }; let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
@@ -121,7 +128,7 @@ export const getInitialStore = ({
maxZoom: writable<number>(2), maxZoom: writable<number>(2),
nodeOrigin: writable<NodeOrigin>(storeNodeOrigin), nodeOrigin: writable<NodeOrigin>(storeNodeOrigin),
nodeDragThreshold: writable<number>(1), nodeDragThreshold: writable<number>(1),
nodeExtent: writable<CoordinateExtent>(infiniteExtent), nodeExtent: writable<CoordinateExtent>(storeNodeExtent),
translateExtent: writable<CoordinateExtent>(infiniteExtent), translateExtent: writable<CoordinateExtent>(infiniteExtent),
autoPanOnNodeDrag: writable<boolean>(true), autoPanOnNodeDrag: writable<boolean>(true),
autoPanOnConnect: writable<boolean>(true), autoPanOnConnect: writable<boolean>(true),
+3
View File
@@ -1,3 +1,4 @@
import { infiniteExtent } from '..';
import { import {
NodeBase, NodeBase,
CoordinateExtent, CoordinateExtent,
@@ -23,6 +24,7 @@ import { ParentExpandChild } from './types';
const defaultOptions = { const defaultOptions = {
nodeOrigin: [0, 0] as NodeOrigin, nodeOrigin: [0, 0] as NodeOrigin,
nodeExtent: infiniteExtent,
elevateNodesOnSelect: true, elevateNodesOnSelect: true,
defaults: {}, defaults: {},
}; };
@@ -48,6 +50,7 @@ export function updateAbsolutePositions<NodeType extends NodeBase>(
type UpdateNodesOptions<NodeType extends NodeBase> = { type UpdateNodesOptions<NodeType extends NodeBase> = {
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
elevateNodesOnSelect?: boolean; elevateNodesOnSelect?: boolean;
defaults?: Partial<NodeType>; defaults?: Partial<NodeType>;
checkEquality?: boolean; checkEquality?: boolean;