diff --git a/.changeset/slimy-impalas-yell.md b/.changeset/slimy-impalas-yell.md
deleted file mode 100644
index e6aea28e..00000000
--- a/.changeset/slimy-impalas-yell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@xyflow/system': major
----
-
-Return Promise from transitionable panzoom actions if a transition is applied. Promise is resolved when transition ends.
diff --git a/examples/react/src/examples/Stress/index.tsx b/examples/react/src/examples/Stress/index.tsx
index 5fd6f9e9..9020ddd6 100644
--- a/examples/react/src/examples/Stress/index.tsx
+++ b/examples/react/src/examples/Stress/index.tsx
@@ -224,7 +224,6 @@ const StressFlow = () => {
onEdgesChange={onEdgeChange}
minZoom={0.2}
fitView
- panOnDrag={false}
>
diff --git a/examples/react/src/examples/UseReactFlow/index.tsx b/examples/react/src/examples/UseReactFlow/index.tsx
index 2177d0cc..cf6ae59f 100644
--- a/examples/react/src/examples/UseReactFlow/index.tsx
+++ b/examples/react/src/examples/UseReactFlow/index.tsx
@@ -96,9 +96,13 @@ const UseZoomPanHelperFlow = () => {
);
const onNodeClick = useCallback(
- (_: MouseEvent, node: Node) => {
+ async (_: MouseEvent, node: Node) => {
+ console.log('set center start');
+
const { x, y } = node.position;
- setCenter(x, y, { zoom: 1, duration: 1200 });
+ await setCenter(x, y, { zoom: 1, duration: 1200 });
+
+ console.log('set center success');
},
[setCenter]
);
@@ -172,7 +176,15 @@ const UseZoomPanHelperFlow = () => {
-
+
diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts
index 2bfea817..a495ec89 100644
--- a/packages/react/src/hooks/useViewportHelper.ts
+++ b/packages/react/src/hooks/useViewportHelper.ts
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
import {
pointToRendererPoint,
getViewportForBounds,
+ getFitViewNodes,
fitView,
type XYPosition,
rendererPointToPoint,
@@ -64,11 +65,12 @@ const useViewportHelper = (): ViewportHelperFunctions => {
},
fitView: (options) => {
const { nodeLookup, width, height, minZoom, maxZoom, panZoom } = store.getState();
+ const fitViewNodes = getFitViewNodes(nodeLookup, options);
return panZoom
? fitView(
{
- nodeLookup,
+ nodes: fitViewNodes,
width,
height,
minZoom,
diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts
index 11245bb5..062340b7 100644
--- a/packages/react/src/store/index.ts
+++ b/packages/react/src/store/index.ts
@@ -1,6 +1,7 @@
import { createWithEqualityFn } from 'zustand/traditional';
import {
clampPosition,
+ getFitViewNodes,
fitView as fitViewSystem,
adoptUserNodes,
updateAbsolutePositions,
@@ -79,7 +80,6 @@ const createStore = ({
updateNodeInternals: (updates) => {
const {
triggerNodeChanges,
- fitView,
nodeLookup,
parentLookup,
fitViewOnInit,
@@ -88,6 +88,7 @@ const createStore = ({
domNode,
nodeOrigin,
debug,
+ fitViewSync,
} = get();
const { changes, updatedInternals } = updateNodeInternalsSystem(
@@ -106,8 +107,9 @@ const createStore = ({
// we call fitView once initially after all dimensions are set
let nextFitViewDone = fitViewDone;
+
if (!fitViewDone && fitViewOnInit) {
- nextFitViewDone = fitView({
+ nextFitViewDone = fitViewSync({
...fitViewOnInitOptions,
nodes: fitViewOnInitOptions?.nodes,
});
@@ -289,6 +291,7 @@ const createStore = ({
},
panBy: (delta): Promise => {
const { transform, width, height, panZoom, translateExtent } = get();
+
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
},
fitView: (options?: FitViewOptions): Promise => {
@@ -298,9 +301,11 @@ const createStore = ({
return Promise.resolve(false);
}
+ const fitViewNodes = getFitViewNodes(nodeLookup, options);
+
return fitViewSystem(
{
- nodeLookup,
+ nodes: fitViewNodes,
width,
height,
panZoom,
@@ -310,6 +315,31 @@ const createStore = ({
options
);
},
+ // we can't call an asnychronous function in updateNodeInternals
+ // for that we created this sync version of fitView
+ fitViewSync: (options?: FitViewOptions): boolean => {
+ const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
+
+ if (!panZoom) {
+ return false;
+ }
+
+ const fitViewNodes = getFitViewNodes(nodeLookup, options);
+
+ fitViewSystem(
+ {
+ nodes: fitViewNodes,
+ width,
+ height,
+ panZoom,
+ minZoom,
+ maxZoom,
+ },
+ options
+ );
+
+ return fitViewNodes.size > 0;
+ },
cancelConnection: () => {
set({
connection: { ...initialConnection },
diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts
index 30da6e64..0e89d5c6 100644
--- a/packages/react/src/types/store.ts
+++ b/packages/react/src/types/store.ts
@@ -169,6 +169,7 @@ export type ReactFlowActions = {
triggerEdgeChanges: (changes: EdgeChange[]) => void;
panBy: PanBy;
fitView: (options?: FitViewOptions) => Promise;
+ fitViewSync: (options?: FitViewOptions) => boolean;
};
export type ReactFlowState = ReactFlowStore<
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts
index e52fc8a8..bae08a24 100644
--- a/packages/svelte/src/lib/store/index.ts
+++ b/packages/svelte/src/lib/store/index.ts
@@ -18,7 +18,8 @@ import {
type CoordinateExtent,
type UpdateConnection,
type ConnectionState,
- type NodeOrigin
+ type NodeOrigin,
+ getFitViewNodes
} from '@xyflow/system';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
@@ -90,7 +91,7 @@ export function createStore({
store.nodes.update((nds) => nds);
};
- async function updateNodeInternals(updates: Map) {
+ function updateNodeInternals(updates: Map) {
const nodeLookup = get(store.nodeLookup);
const { changes, updatedInternals } = updateNodeInternalsSystem(
updates,
@@ -106,7 +107,7 @@ export function createStore({
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
const fitViewOptions = get(store.fitViewOptions);
- const fitViewOnInitDone = await fitView({
+ const fitViewOnInitDone = fitViewSync({
...fitViewOptions,
nodes: fitViewOptions?.nodes
});
@@ -153,9 +154,11 @@ export function createStore({
return Promise.resolve(false);
}
+ const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
+
return fitViewSystem(
{
- nodeLookup: get(store.nodeLookup),
+ nodes: fitViewNodes,
width: get(store.width),
height: get(store.height),
minZoom: get(store.minZoom),
@@ -166,6 +169,30 @@ export function createStore({
);
}
+ function fitViewSync(options?: FitViewOptions) {
+ const panZoom = get(store.panZoom);
+
+ if (!panZoom) {
+ return false;
+ }
+
+ const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
+
+ fitViewSystem(
+ {
+ nodes: fitViewNodes,
+ width: get(store.width),
+ height: get(store.height),
+ minZoom: get(store.minZoom),
+ maxZoom: get(store.maxZoom),
+ panZoom
+ },
+ options
+ );
+
+ return fitViewNodes.size > 0;
+ }
+
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
const panZoom = get(store.panZoom);
if (!panZoom) {
diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts
index f0a0a504..f712e1ea 100644
--- a/packages/system/src/types/general.ts
+++ b/packages/system/src/types/general.ts
@@ -55,7 +55,7 @@ export type OnConnectEnd = (event: MouseEvent | TouchEvent) => void;
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
export type FitViewParamsBase = {
- nodeLookup: Map>;
+ nodes: Map>;
width: number;
height: number;
panZoom: PanZoomInstance;
diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts
index 8d98fa40..62c3d9da 100644
--- a/packages/system/src/utils/graph.ts
+++ b/packages/system/src/utils/graph.ts
@@ -233,39 +233,46 @@ export const getConnectedEdges = nodeIds.has(edge.source) || nodeIds.has(edge.target));
};
-export async function fitView, Options extends FitViewOptionsBase>(
- { nodeLookup, width, height, panZoom, minZoom, maxZoom }: Params,
- options?: Options
-): Promise {
- const filteredNodes: Map = new Map();
+export function getFitViewNodes<
+ Params extends NodeLookup>,
+ Options extends FitViewOptionsBase
+>(nodeLookup: Params, options?: Pick) {
+ const fitViewNodes: NodeLookup = new Map();
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
nodeLookup.forEach((n) => {
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
- filteredNodes.set(n.id, n);
+ fitViewNodes.set(n.id, n);
}
});
- if (filteredNodes.size > 0) {
- const bounds = getInternalNodesBounds(filteredNodes);
+ return fitViewNodes;
+}
- const viewport = getViewportForBounds(
- bounds,
- width,
- height,
- options?.minZoom ?? minZoom,
- options?.maxZoom ?? maxZoom,
- options?.padding ?? 0.1
- );
-
- await panZoom.setViewport(viewport, { duration: options?.duration });
-
- return Promise.resolve(true);
+export async function fitView, Options extends FitViewOptionsBase>(
+ { nodes, width, height, panZoom, minZoom, maxZoom }: Params,
+ options?: Omit
+): Promise {
+ if (nodes.size === 0) {
+ return Promise.resolve(false);
}
- return Promise.resolve(false);
+ const bounds = getInternalNodesBounds(nodes);
+
+ const viewport = getViewportForBounds(
+ bounds,
+ width,
+ height,
+ options?.minZoom ?? minZoom,
+ options?.maxZoom ?? maxZoom,
+ options?.padding ?? 0.1
+ );
+
+ await panZoom.setViewport(viewport, { duration: options?.duration });
+
+ return Promise.resolve(true);
}
/**
diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts
index 27022760..54489a00 100644
--- a/packages/system/src/xydrag/XYDrag.ts
+++ b/packages/system/src/xydrag/XYDrag.ts
@@ -212,6 +212,7 @@ export function XYDrag voi
updateNodes(lastPos as XYPosition, null);
}
}
+
autoPanId = requestAnimationFrame(autoPan);
}
diff --git a/packages/system/src/xypanzoom/utils.ts b/packages/system/src/xypanzoom/utils.ts
index 5a3e687f..fbd0324d 100644
--- a/packages/system/src/xypanzoom/utils.ts
+++ b/packages/system/src/xypanzoom/utils.ts
@@ -21,8 +21,15 @@ export const isWrappedWithClass = (event: any, className: string | undefined) =>
export const isRightClickPan = (panOnDrag: boolean | number[], usedButton: number) =>
usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2);
-export const getD3Transition = (selection: D3SelectionInstance, duration = 0, onEnd = () => {}) =>
- typeof duration === 'number' && duration > 0 ? selection.transition().duration(duration).on('end', onEnd) : selection;
+export const getD3Transition = (selection: D3SelectionInstance, duration = 0, onEnd = () => {}) => {
+ const hasDuration = typeof duration === 'number' && duration > 0;
+
+ if (!hasDuration) {
+ onEnd();
+ }
+
+ return hasDuration ? selection.transition().duration(duration).on('end', onEnd) : selection;
+};
export const wheelDelta = (event: any) => {
const factor = event.ctrlKey && isMacOs() ? 10 : 1;