feat(react/svelte): controlled viewport (#3461)

* feat(react): controlled viewport

* refactor(react): cleanup viewport

* feat(svelte): two way data binding for viewport, cleanup transform <> viewport stuff

* refactor(svelte): viewport stuff
This commit is contained in:
Moritz Klack
2023-09-28 16:19:03 +02:00
committed by GitHub
parent 2215b68438
commit ae33cba887
35 changed files with 351 additions and 95 deletions
@@ -57,9 +57,9 @@ export function getDerivedConnectionProps(
store.connectionLineType,
store.connectionMode,
store.nodes,
store.transform
store.viewport
],
([connection, connectionLineType, connectionMode, nodes, transform]) => {
([connection, connectionLineType, connectionMode, nodes, viewport]) => {
if (!connection.connectionStartHandle?.nodeId) {
return initConnectionProps;
}
@@ -91,8 +91,8 @@ export function getDerivedConnectionProps(
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: ((connection.connectionPosition?.x ?? 0) - transform[0]) / transform[2],
targetY: ((connection.connectionPosition?.y ?? 0) - transform[1]) / transform[2],
targetX: ((connection.connectionPosition?.x ?? 0) - viewport.x) / viewport.zoom,
targetY: ((connection.connectionPosition?.y ?? 0) - viewport.y) / viewport.zoom,
targetPosition: toPosition
};
+3 -3
View File
@@ -10,11 +10,11 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
store.edges,
store.nodes,
store.onlyRenderVisibleElements,
store.transform,
store.viewport,
store.width,
store.height
],
([edges, nodes, onlyRenderVisibleElements, transform, width, height]) => {
([edges, nodes, onlyRenderVisibleElements, viewport, width, height]) => {
const visibleEdges =
onlyRenderVisibleElements && width && height
? edges.filter((edge) => {
@@ -29,7 +29,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
targetNode,
width,
height,
transform
transform: [viewport.x, viewport.y, viewport.zoom]
})
);
})
+5 -5
View File
@@ -22,7 +22,7 @@ import { addEdge as addEdgeUtil } from '$lib/utils';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types';
import { syncNodeStores, syncEdgeStores } from './utils';
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
import { getEdgeTree } from './edge-tree';
import { getVisibleNodes } from './visible-nodes';
import { getDerivedConnectionProps } from './derived-connection-props';
@@ -241,10 +241,11 @@ export function createStore(): SvelteFlowStore {
}
function panBy(delta: XYPosition) {
const viewport = get(store.viewport);
return panBySystem({
delta,
panZoom: get(store.panZoom),
transform: get(store.transform),
transform: [viewport.x, viewport.y, viewport.zoom],
translateExtent: get(store.translateExtent),
width: get(store.width),
height: get(store.height)
@@ -298,6 +299,7 @@ export function createStore(): SvelteFlowStore {
// actions
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
setNodeTypes,
setEdgeTypes,
addEdge,
@@ -305,9 +307,7 @@ export function createStore(): SvelteFlowStore {
updateNodeDimensions,
zoomIn,
zoomOut,
fitView: (options?: FitViewOptions) => {
return fitView(get(store.nodes), options);
},
fitView: (options?: FitViewOptions) => fitView(get(store.nodes), options),
setMinZoom,
setMaxZoom,
setTranslateExtent,
@@ -5,7 +5,6 @@ import {
ConnectionMode,
ConnectionLineType,
type SelectionRect,
type Transform,
type SnapGrid,
type MarkerProps,
type PanZoomInstance,
@@ -14,7 +13,8 @@ import {
type GroupedEdges,
type NodeOrigin,
type OnError,
devWarn
devWarn,
type Viewport
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -72,7 +72,7 @@ export const getInitialStore = () => ({
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>(initialNodeTypes),
edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
transform: writable<Transform>([0, 0, 1]),
viewport: writable<Viewport>({ x: 0, y: 0, zoom: 1 }),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connection: readable<ConnectionProps>(initConnectionProps),
+3 -1
View File
@@ -6,7 +6,8 @@ import type {
Connection,
UpdateNodePositions,
CoordinateExtent,
UpdateConnection
UpdateConnection,
Viewport
} from '@xyflow/system';
import type { getInitialStore } from './initial-store';
@@ -15,6 +16,7 @@ import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/type
export type SvelteFlowStoreActions = {
syncNodeStores: (nodesStore: Writable<Node[]>) => void;
syncEdgeStores: (edgeStore: Writable<Edge[]>) => void;
syncViewport: (viewportStore?: Writable<Viewport>) => void;
setNodeTypes: (nodeTypes: NodeTypes) => void;
setEdgeTypes: (edgeTypes: EdgeTypes) => void;
addEdge: (edge: Edge | Connection) => void;
+49 -1
View File
@@ -6,7 +6,7 @@ import {
type Writable,
get
} from 'svelte/store';
import { updateNodes } from '@xyflow/system';
import { updateNodes, type Transform, type Viewport, type PanZoomInstance } from '@xyflow/system';
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
@@ -56,6 +56,54 @@ export function syncEdgeStores(
edgesStore.update = userEdgesStore.update = (fn: (nds: Edge[]) => Edge[]) => _set(fn(val));
}
// it is possible to pass a viewport store to SvelteFlow for having more control
// if that's the case we need to sync the internal viewport with the user viewport
export const syncViewportStores = (
panZoomStore: Writable<PanZoomInstance | null>,
viewportStore: Writable<Viewport>,
userViewportStore?: Writable<Viewport>
) => {
if (!userViewportStore) {
return;
}
const panZoom = get(panZoomStore);
const viewportStoreSetter = viewportStore.set;
const userViewportStoreSetter = userViewportStore.set;
let val = userViewportStore ? get(userViewportStore) : { x: 0, y: 0, zoom: 1 };
viewportStore.set(val);
viewportStore.set = (vp: Viewport) => {
viewportStoreSetter(vp);
userViewportStoreSetter(vp);
val = vp;
return vp;
};
userViewportStore.set = (vp: Viewport) => {
panZoom?.syncViewport(vp);
viewportStoreSetter(vp);
userViewportStoreSetter(vp);
val = vp;
return vp;
};
viewportStore.update = (fn: (vp: Viewport) => Viewport) => {
viewportStore.set(fn(val));
};
userViewportStore.update = (fn: (vp: Viewport) => Viewport) => {
userViewportStore.set(fn(val));
};
};
export type NodeStoreOptions = {
elevateNodesOnSelect?: boolean;
};
@@ -1,13 +1,14 @@
import { derived } from 'svelte/store';
import { getNodesInside } from '@xyflow/system';
import { getNodesInside, type Transform } from '@xyflow/system';
import type { Node } from '$lib/types';
import type { SvelteFlowStoreState } from './types';
export function getVisibleNodes(store: SvelteFlowStoreState) {
return derived(
[store.nodes, store.onlyRenderVisibleElements, store.width, store.height, store.transform],
([nodes, onlyRenderVisibleElements, width, height, transform]) => {
[store.nodes, store.onlyRenderVisibleElements, store.width, store.height, store.viewport],
([nodes, onlyRenderVisibleElements, width, height, viewport]) => {
const transform: Transform = [viewport.x, viewport.y, viewport.zoom];
return onlyRenderVisibleElements
? getNodesInside<Node>(nodes, { x: 0, y: 0, width, height }, transform, true)
: nodes;