feat(utils): add vanilla drag and panzoom (#3108)

* refactor(panzoom): create vanilla helper

* feat(svelte): add interaction example

* refactor(panzoom): cleanup

* Update function-runner.ts

* refactor(panzoom): cleanup

* refactor(panzoom): rename functions

* feat(utils): add vanilla drag helper (#3107)

* feat(utils): add vanilla drag helper

* refactor(drag): cleanup

* refactor(drag): cleanup

* chore(packages): cleanup

* refactor(panzoom): cleanup and simplify
This commit is contained in:
Moritz Klack
2023-05-31 15:55:36 +02:00
committed by GitHub
parent d8ef041737
commit 1dab08f36e
59 changed files with 3258 additions and 4313 deletions
+54 -41
View File
@@ -1,8 +1,7 @@
import { getContext } from 'svelte';
import { derived, get } from 'svelte/store';
import { zoomIdentity } from 'd3-zoom';
import {
type NodeDragItem,
type UpdateNodePositions,
type NodeDimensionUpdate,
internalsSymbol,
type ViewportHelperFunctionOptions,
@@ -13,10 +12,10 @@ import {
import {
createMarkerIds,
fitView as fitViewUtil,
getD3Transition,
getDimensions,
getElementsToRemove,
getHandleBounds
getHandleBounds,
infiniteExtent
} from '@reactflow/utils';
import { addEdge as addEdgeUtil } from '$lib/utils';
@@ -60,7 +59,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
store.edges.set(addEdgeUtil(edgeParams, edges));
}
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
store.nodes.update((nds) => {
return nds.map((n) => {
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
@@ -77,7 +76,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
return n;
});
});
}
};
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
const viewportNode = document?.querySelector('.svelte-flow__viewport');
@@ -123,21 +122,21 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
return node;
});
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const panZoom = get(store.panZoom);
const fitViewOnInitDone =
get(store.fitViewOnInitDone) ||
(get(store.fitViewOnInit) && !!d3Zoom && !!d3Selection && fitView({ nodes: nextNodes }));
(get(store.fitViewOnInit) && !!panZoom && fitView({ nodes: nextNodes }));
store.fitViewOnInitDone.set(fitViewOnInitDone);
store.nodes.set(nextNodes);
}
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const panZoom = get(store.panZoom);
if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), factor);
if (panZoom) {
panZoom.scaleBy(factor, options);
}
}
@@ -150,28 +149,36 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
}
function setMinZoom(minZoom: number) {
const d3Zoom = get(store.d3).zoom;
if (d3Zoom) {
d3Zoom?.scaleExtent([minZoom, get(store.maxZoom)]);
const panZoom = get(store.panZoom);
if (panZoom) {
panZoom.setScaleExtent([minZoom, get(store.maxZoom)]);
store.minZoom.set(minZoom);
}
}
function setMaxZoom(maxZoom: number) {
const d3Zoom = get(store.d3).zoom;
if (d3Zoom) {
d3Zoom?.scaleExtent([get(store.minZoom), maxZoom]);
const panZoom = get(store.panZoom);
if (panZoom) {
panZoom.setScaleExtent([get(store.minZoom), maxZoom]);
store.maxZoom.set(maxZoom);
}
}
function fitView(options?: FitViewOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
function setTranslateExtent(extent: CoordinateExtent) {
const panZoom = get(store.panZoom);
if (!d3Zoom || !d3Selection) {
if (panZoom) {
panZoom.setTranslateExtent(extent);
store.translateExtent.set(extent);
}
}
function fitView(options?: FitViewOptions) {
const panZoom = get(store.panZoom);
if (!panZoom) {
return false;
}
@@ -184,8 +191,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
height: get(store.height),
minZoom: 0.2,
maxZoom: 2,
d3Selection,
d3Zoom,
panZoom,
nodeOrigin: [0, 0]
},
{}
@@ -203,7 +209,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
return item;
}
function resetSelectedElements() {
function unselectNodesAndEdges() {
store.nodes.update((ns) => ns.map(resetSelectedItem));
store.edges.update((es) => es.map(resetSelectedItem));
}
@@ -267,29 +273,35 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
}
function panBy(delta: XYPosition) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const panZoom = get(store.panZoom);
const transform = get(store.transform);
const width = get(store.width);
const height = get(store.height);
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
return;
if (!panZoom || (!delta.x && !delta.y)) {
return false;
}
const nextTransform = zoomIdentity
.translate(transform[0] + delta.x, transform[1] + delta.y)
.scale(transform[2]);
const nextViewport = panZoom.setViewportConstrained(
{
x: transform[0] + delta.x,
y: transform[1] + delta.y,
zoom: transform[2]
},
[
[0, 0],
[width, height]
],
infiniteExtent
);
const extent: CoordinateExtent = [
[0, 0],
[width, height]
];
const transformChanged =
!!nextViewport &&
(nextViewport.x !== transform[0] ||
nextViewport.y !== transform[1] ||
nextViewport.k !== transform[2]);
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
]);
d3Zoom.transform(d3Selection, constrainedTransform);
return transformChanged;
}
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
@@ -319,7 +331,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
store.snapGrid.set(null);
store.isValidConnection.set(() => true);
resetSelectedElements();
unselectNodesAndEdges();
cancelConnection();
}
@@ -346,7 +358,8 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
fitView,
setMinZoom,
setMaxZoom,
resetSelectedElements,
setTranslateExtent,
unselectNodesAndEdges,
addSelectedNodes,
addSelectedEdges,
panBy,
@@ -1,14 +1,14 @@
import { readable, writable } from 'svelte/store';
import {
SelectionMode,
type D3ZoomInstance,
type D3SelectionInstance,
ConnectionMode,
ConnectionLineType,
type SelectionRect,
type Transform,
type SnapGrid,
type MarkerProps
type MarkerProps,
type PanZoomInstance,
type CoordinateExtent
} from '@reactflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -27,6 +27,7 @@ import type {
Node,
IsValidConnection
} from '$lib/types';
import { infiniteExtent } from '@reactflow/utils';
export const initConnectionData = {
nodeId: null,
@@ -58,12 +59,12 @@ export const initialStoreState = {
width: writable<number>(500),
minZoom: writable<number>(0.5),
maxZoom: writable<number>(2),
nodeExtent: writable<CoordinateExtent>(infiniteExtent),
translateExtent: writable<CoordinateExtent>(infiniteExtent),
autoPanOnNodeDrag: writable<boolean>(true),
fitViewOnInit: writable<boolean>(false),
fitViewOnInitDone: writable<boolean>(false),
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null,
selection: null
}),
panZoom: writable<PanZoomInstance | null>(null),
snapGrid: writable<SnapGrid | null>(null),
dragging: writable<boolean>(false),
selectionRect: writable<SelectionRect | null>(null),
@@ -85,6 +86,7 @@ export const initialStoreState = {
nodesDraggable: writable<boolean>(true),
nodesConnectable: writable<boolean>(true),
elementsSelectable: writable<boolean>(true),
selectNodesOnDrag: writable<boolean>(true),
markers: readable<MarkerProps[]>([]),
defaultMarkerColor: writable<string>('#b1b1b7')
};
+6 -8
View File
@@ -3,7 +3,8 @@ import type {
XYPosition,
ViewportHelperFunctionOptions,
Connection,
NodeDragItem
UpdateNodePositions,
CoordinateExtent
} from '@reactflow/system';
import type { initialStoreState } from './initial-store';
@@ -18,17 +19,14 @@ export type SvelteFlowStoreActions = {
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
setMinZoom: (minZoom: number) => void;
setMaxZoom: (maxZoom: number) => void;
setTranslateExtent: (extent: CoordinateExtent) => void;
fitView: (options?: FitViewOptions) => boolean;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
dragging?: boolean
) => void;
updateNodePositions: UpdateNodePositions;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
unselectNodesAndEdges: () => void;
addSelectedNodes: (ids: string[]) => void;
addSelectedEdges: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
panBy: (delta: XYPosition) => boolean;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
reset(): void;