Merge pull request #4421 from xyflow/refactor/promised-panzoom
Refactor/promised panzoom
This commit is contained in:
@@ -96,9 +96,13 @@ const UseZoomPanHelperFlow = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onNodeClick = useCallback(
|
const onNodeClick = useCallback(
|
||||||
(_: MouseEvent, node: Node) => {
|
async (_: MouseEvent, node: Node) => {
|
||||||
|
console.log('set center start');
|
||||||
|
|
||||||
const { x, y } = node.position;
|
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]
|
[setCenter]
|
||||||
);
|
);
|
||||||
@@ -172,7 +176,15 @@ const UseZoomPanHelperFlow = () => {
|
|||||||
<Panel position="top-right">
|
<Panel position="top-right">
|
||||||
<button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button>
|
<button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button>
|
||||||
<button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button>
|
<button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button>
|
||||||
<button onClick={() => fitView({ duration: 1200, padding: 0.3 })}>fitView</button>
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
console.log('fit view start');
|
||||||
|
await fitView({ duration: 1200, padding: 0.3 });
|
||||||
|
console.log('fit view success');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
fitView
|
||||||
|
</button>
|
||||||
<button onClick={onAddNode}>add node</button>
|
<button onClick={onAddNode}>add node</button>
|
||||||
<button onClick={onResetNodes}>reset nodes</button>
|
<button onClick={onResetNodes}>reset nodes</button>
|
||||||
<button onClick={logNodes}>useNodes</button>
|
<button onClick={logNodes}>useNodes</button>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# @xyflow/react
|
# @xyflow/react
|
||||||
|
|
||||||
|
## 12.0.0-next.27
|
||||||
|
|
||||||
|
- return Promises for `setViewport`, `fitView`, `fitBounds` and `zoomTo` to be able to await viewport update
|
||||||
|
|
||||||
## 12.0.0-next.26
|
## 12.0.0-next.26
|
||||||
|
|
||||||
- add `autoPanSpeed` prop
|
- add `autoPanSpeed` prop
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
|||||||
import {
|
import {
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
|
getFitViewNodes,
|
||||||
fitView,
|
fitView,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
rendererPointToPoint,
|
rendererPointToPoint,
|
||||||
@@ -21,17 +22,33 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
|
|
||||||
return useMemo<ViewportHelperFunctions>(() => {
|
return useMemo<ViewportHelperFunctions>(() => {
|
||||||
return {
|
return {
|
||||||
zoomIn: (options) => store.getState().panZoom?.scaleBy(1.2, { duration: options?.duration }),
|
zoomIn: (options) => {
|
||||||
zoomOut: (options) => store.getState().panZoom?.scaleBy(1 / 1.2, { duration: options?.duration }),
|
const { panZoom } = store.getState();
|
||||||
zoomTo: (zoomLevel, options) => store.getState().panZoom?.scaleTo(zoomLevel, { duration: options?.duration }),
|
|
||||||
|
return panZoom ? panZoom.scaleBy(1.2, { duration: options?.duration }) : Promise.resolve(false);
|
||||||
|
},
|
||||||
|
zoomOut: (options) => {
|
||||||
|
const { panZoom } = store.getState();
|
||||||
|
|
||||||
|
return panZoom ? panZoom.scaleBy(1 / 1.2, { duration: options?.duration }) : Promise.resolve(false);
|
||||||
|
},
|
||||||
|
zoomTo: (zoomLevel, options) => {
|
||||||
|
const { panZoom } = store.getState();
|
||||||
|
|
||||||
|
return panZoom ? panZoom.scaleTo(zoomLevel, { duration: options?.duration }) : Promise.resolve(false);
|
||||||
|
},
|
||||||
getZoom: () => store.getState().transform[2],
|
getZoom: () => store.getState().transform[2],
|
||||||
setViewport: (viewport, options) => {
|
setViewport: async (viewport, options) => {
|
||||||
const {
|
const {
|
||||||
transform: [tX, tY, tZoom],
|
transform: [tX, tY, tZoom],
|
||||||
panZoom,
|
panZoom,
|
||||||
} = store.getState();
|
} = store.getState();
|
||||||
|
|
||||||
panZoom?.setViewport(
|
if (!panZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await panZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: viewport.x ?? tX,
|
x: viewport.x ?? tX,
|
||||||
y: viewport.y ?? tY,
|
y: viewport.y ?? tY,
|
||||||
@@ -39,6 +56,8 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
},
|
},
|
||||||
{ duration: options?.duration }
|
{ duration: options?.duration }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
getViewport: () => {
|
getViewport: () => {
|
||||||
const [x, y, zoom] = store.getState().transform;
|
const [x, y, zoom] = store.getState().transform;
|
||||||
@@ -46,30 +65,33 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
},
|
},
|
||||||
fitView: (options) => {
|
fitView: (options) => {
|
||||||
const { nodeLookup, width, height, minZoom, maxZoom, panZoom } = store.getState();
|
const { nodeLookup, width, height, minZoom, maxZoom, panZoom } = store.getState();
|
||||||
|
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||||
|
|
||||||
if (!panZoom) {
|
return panZoom
|
||||||
return false;
|
? fitView(
|
||||||
}
|
{
|
||||||
|
nodes: fitViewNodes,
|
||||||
return fitView(
|
width,
|
||||||
{
|
height,
|
||||||
nodeLookup,
|
minZoom,
|
||||||
width,
|
maxZoom,
|
||||||
height,
|
panZoom,
|
||||||
minZoom,
|
},
|
||||||
maxZoom,
|
options
|
||||||
panZoom,
|
)
|
||||||
},
|
: Promise.resolve(false);
|
||||||
options
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
setCenter: (x, y, options) => {
|
setCenter: async (x, y, options) => {
|
||||||
const { width, height, maxZoom, panZoom } = store.getState();
|
const { width, height, maxZoom, panZoom } = store.getState();
|
||||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||||
const centerX = width / 2 - x * nextZoom;
|
const centerX = width / 2 - x * nextZoom;
|
||||||
const centerY = height / 2 - y * nextZoom;
|
const centerY = height / 2 - y * nextZoom;
|
||||||
|
|
||||||
panZoom?.setViewport(
|
if (!panZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await panZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: centerX,
|
x: centerX,
|
||||||
y: centerY,
|
y: centerY,
|
||||||
@@ -77,12 +99,20 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
},
|
},
|
||||||
{ duration: options?.duration }
|
{ duration: options?.duration }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
fitBounds: (bounds, options) => {
|
fitBounds: async (bounds, options) => {
|
||||||
const { width, height, minZoom, maxZoom, panZoom } = store.getState();
|
const { width, height, minZoom, maxZoom, panZoom } = store.getState();
|
||||||
const viewport = getViewportForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
|
const viewport = getViewportForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
|
||||||
|
|
||||||
panZoom?.setViewport(viewport, { duration: options?.duration });
|
if (!panZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await panZoom.setViewport(viewport, { duration: options?.duration });
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
||||||
const { transform, snapGrid, domNode } = store.getState();
|
const { transform, snapGrid, domNode } = store.getState();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createWithEqualityFn } from 'zustand/traditional';
|
import { createWithEqualityFn } from 'zustand/traditional';
|
||||||
import {
|
import {
|
||||||
clampPosition,
|
clampPosition,
|
||||||
|
getFitViewNodes,
|
||||||
fitView as fitViewSystem,
|
fitView as fitViewSystem,
|
||||||
adoptUserNodes,
|
adoptUserNodes,
|
||||||
updateAbsolutePositions,
|
updateAbsolutePositions,
|
||||||
@@ -79,7 +80,6 @@ const createStore = ({
|
|||||||
updateNodeInternals: (updates) => {
|
updateNodeInternals: (updates) => {
|
||||||
const {
|
const {
|
||||||
triggerNodeChanges,
|
triggerNodeChanges,
|
||||||
fitView,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
parentLookup,
|
parentLookup,
|
||||||
fitViewOnInit,
|
fitViewOnInit,
|
||||||
@@ -88,6 +88,7 @@ const createStore = ({
|
|||||||
domNode,
|
domNode,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
debug,
|
debug,
|
||||||
|
fitViewSync,
|
||||||
} = get();
|
} = get();
|
||||||
|
|
||||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||||
@@ -106,8 +107,9 @@ const createStore = ({
|
|||||||
|
|
||||||
// we call fitView once initially after all dimensions are set
|
// we call fitView once initially after all dimensions are set
|
||||||
let nextFitViewDone = fitViewDone;
|
let nextFitViewDone = fitViewDone;
|
||||||
|
|
||||||
if (!fitViewDone && fitViewOnInit) {
|
if (!fitViewDone && fitViewOnInit) {
|
||||||
nextFitViewDone = fitView({
|
nextFitViewDone = fitViewSync({
|
||||||
...fitViewOnInitOptions,
|
...fitViewOnInitOptions,
|
||||||
nodes: fitViewOnInitOptions?.nodes,
|
nodes: fitViewOnInitOptions?.nodes,
|
||||||
});
|
});
|
||||||
@@ -287,20 +289,23 @@ const createStore = ({
|
|||||||
nodeExtent,
|
nodeExtent,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
panBy: (delta): boolean => {
|
panBy: (delta): Promise<boolean> => {
|
||||||
const { transform, width, height, panZoom, translateExtent } = get();
|
const { transform, width, height, panZoom, translateExtent } = get();
|
||||||
|
|
||||||
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
||||||
},
|
},
|
||||||
fitView: (options?: FitViewOptions): boolean => {
|
fitView: (options?: FitViewOptions): Promise<boolean> => {
|
||||||
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
||||||
|
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||||
|
|
||||||
return fitViewSystem(
|
return fitViewSystem(
|
||||||
{
|
{
|
||||||
nodeLookup,
|
nodes: fitViewNodes,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
panZoom,
|
panZoom,
|
||||||
@@ -310,6 +315,31 @@ const createStore = ({
|
|||||||
options
|
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: () => {
|
cancelConnection: () => {
|
||||||
set({
|
set({
|
||||||
connection: { ...initialConnection },
|
connection: { ...initialConnection },
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
|||||||
|
|
||||||
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
|
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
|
||||||
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
||||||
export type FitView = (fitViewOptions?: FitViewOptions) => boolean;
|
export type FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
|
||||||
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||||
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
|||||||
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
||||||
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
panBy: PanBy;
|
panBy: PanBy;
|
||||||
fitView: (options?: FitViewOptions) => boolean;
|
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
||||||
|
fitViewSync: (options?: FitViewOptions) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# @xyflow/svelte
|
# @xyflow/svelte
|
||||||
|
|
||||||
|
## 0.1.9
|
||||||
|
|
||||||
|
- return Promises for `setViewport`, `fitView`, `fitBounds` and `zoomTo` to be able to await viewport update
|
||||||
|
|
||||||
## 0.1.8
|
## 0.1.8
|
||||||
|
|
||||||
- `useConnection` returns `ConnectionState` with `toNode` and `toHandle`
|
- `useConnection` returns `ConnectionState` with `toNode` and `toHandle`
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function useSvelteFlow(): {
|
|||||||
* @param zoomLevel - the zoom level to set
|
* @param zoomLevel - the zoom level to set
|
||||||
* @param options.duration - optional duration. If set, a transition will be applied
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
*/
|
*/
|
||||||
setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
|
setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Returns the current zoom level.
|
* Returns the current zoom level.
|
||||||
*
|
*
|
||||||
@@ -94,14 +94,14 @@ export function useSvelteFlow(): {
|
|||||||
* @param y - y position
|
* @param y - y position
|
||||||
* @param options.zoom - optional zoom
|
* @param options.zoom - optional zoom
|
||||||
*/
|
*/
|
||||||
setCenter: (x: number, y: number, options?: SetCenterOptions) => void;
|
setCenter: (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Sets the current viewport.
|
* Sets the current viewport.
|
||||||
*
|
*
|
||||||
* @param viewport - the viewport to set
|
* @param viewport - the viewport to set
|
||||||
* @param options.duration - optional duration. If set, a transition will be applied
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
*/
|
*/
|
||||||
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Returns the current viewport.
|
* Returns the current viewport.
|
||||||
*
|
*
|
||||||
@@ -118,7 +118,7 @@ export function useSvelteFlow(): {
|
|||||||
* @param options.duration - optional duration. If set, a transition will be applied
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
* @param options.nodes - optional nodes to fit the view to
|
* @param options.nodes - optional nodes to fit the view to
|
||||||
*/
|
*/
|
||||||
fitView: (options?: FitViewOptions) => void;
|
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Returns all nodes that intersect with the given node or rect.
|
* Returns all nodes that intersect with the given node or rect.
|
||||||
*
|
*
|
||||||
@@ -153,7 +153,7 @@ export function useSvelteFlow(): {
|
|||||||
* @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to
|
* @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to
|
||||||
* @param options.padding - optional padding
|
* @param options.padding - optional padding
|
||||||
*/
|
*/
|
||||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Deletes nodes and edges.
|
* Deletes nodes and edges.
|
||||||
*
|
*
|
||||||
@@ -298,26 +298,41 @@ export function useSvelteFlow(): {
|
|||||||
getEdge: (id) => get(edgeLookup).get(id),
|
getEdge: (id) => get(edgeLookup).get(id),
|
||||||
getEdges: (ids) => (ids === undefined ? get(edges) : getElements(get(edgeLookup), ids)),
|
getEdges: (ids) => (ids === undefined ? get(edges) : getElements(get(edgeLookup), ids)),
|
||||||
setZoom: (zoomLevel, options) => {
|
setZoom: (zoomLevel, options) => {
|
||||||
get(panZoom)?.scaleTo(zoomLevel, { duration: options?.duration });
|
const currentPanZoom = get(panZoom);
|
||||||
|
return currentPanZoom
|
||||||
|
? currentPanZoom.scaleTo(zoomLevel, { duration: options?.duration })
|
||||||
|
: Promise.resolve(false);
|
||||||
},
|
},
|
||||||
getZoom: () => get(viewport).zoom,
|
getZoom: () => get(viewport).zoom,
|
||||||
setViewport: (vieport, options) => {
|
setViewport: async (nextViewport, options) => {
|
||||||
const currentViewport = get(viewport);
|
const currentViewport = get(viewport);
|
||||||
|
const currentPanZoom = get(panZoom);
|
||||||
|
|
||||||
get(panZoom)?.setViewport(
|
if (!currentPanZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await currentPanZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: vieport.x ?? currentViewport.x,
|
x: nextViewport.x ?? currentViewport.x,
|
||||||
y: vieport.y ?? currentViewport.y,
|
y: nextViewport.y ?? currentViewport.y,
|
||||||
zoom: vieport.zoom ?? currentViewport.zoom
|
zoom: nextViewport.zoom ?? currentViewport.zoom
|
||||||
},
|
},
|
||||||
{ duration: options?.duration }
|
{ duration: options?.duration }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
getViewport: () => get(viewport),
|
getViewport: () => get(viewport),
|
||||||
setCenter: (x, y, options) => {
|
setCenter: async (x, y, options) => {
|
||||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
|
||||||
|
const currentPanZoom = get(panZoom);
|
||||||
|
|
||||||
get(panZoom)?.setViewport(
|
if (!currentPanZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await currentPanZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: get(width) / 2 - x * nextZoom,
|
x: get(width) / 2 - x * nextZoom,
|
||||||
y: get(height) / 2 - y * nextZoom,
|
y: get(height) / 2 - y * nextZoom,
|
||||||
@@ -325,9 +340,17 @@ export function useSvelteFlow(): {
|
|||||||
},
|
},
|
||||||
{ duration: options?.duration }
|
{ duration: options?.duration }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
fitView,
|
fitView,
|
||||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => {
|
fitBounds: async (bounds: Rect, options?: FitBoundsOptions) => {
|
||||||
|
const currentPanZoom = get(panZoom);
|
||||||
|
|
||||||
|
if (!currentPanZoom) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
const viewport = getViewportForBounds(
|
const viewport = getViewportForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
get(width),
|
get(width),
|
||||||
@@ -337,7 +360,9 @@ export function useSvelteFlow(): {
|
|||||||
options?.padding ?? 0.1
|
options?.padding ?? 0.1
|
||||||
);
|
);
|
||||||
|
|
||||||
get(panZoom)?.setViewport(viewport, { duration: options?.duration });
|
await currentPanZoom.setViewport(viewport, { duration: options?.duration });
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
getIntersectingNodes: (
|
getIntersectingNodes: (
|
||||||
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import {
|
|||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
type ConnectionState,
|
type ConnectionState,
|
||||||
type NodeOrigin
|
type NodeOrigin,
|
||||||
|
getFitViewNodes
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
|
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
|
||||||
@@ -106,7 +107,7 @@ export function createStore({
|
|||||||
|
|
||||||
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
|
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
|
||||||
const fitViewOptions = get(store.fitViewOptions);
|
const fitViewOptions = get(store.fitViewOptions);
|
||||||
const fitViewOnInitDone = fitView({
|
const fitViewOnInitDone = fitViewSync({
|
||||||
...fitViewOptions,
|
...fitViewOptions,
|
||||||
nodes: fitViewOptions?.nodes
|
nodes: fitViewOptions?.nodes
|
||||||
});
|
});
|
||||||
@@ -150,12 +151,14 @@ export function createStore({
|
|||||||
const panZoom = get(store.panZoom);
|
const panZoom = get(store.panZoom);
|
||||||
|
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
|
||||||
|
|
||||||
return fitViewSystem(
|
return fitViewSystem(
|
||||||
{
|
{
|
||||||
nodeLookup: get(store.nodeLookup),
|
nodes: fitViewNodes,
|
||||||
width: get(store.width),
|
width: get(store.width),
|
||||||
height: get(store.height),
|
height: get(store.height),
|
||||||
minZoom: get(store.minZoom),
|
minZoom: get(store.minZoom),
|
||||||
@@ -166,20 +169,45 @@ export function createStore({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
|
function fitViewSync(options?: FitViewOptions) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = get(store.panZoom);
|
||||||
|
|
||||||
if (panZoom) {
|
if (!panZoom) {
|
||||||
panZoom.scaleBy(factor, options);
|
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) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return panZoom.scaleBy(factor, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
||||||
zoomBy(1.2, options);
|
return zoomBy(1.2, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
||||||
zoomBy(1 / 1.2, options);
|
return zoomBy(1 / 1.2, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMinZoom(minZoom: number) {
|
function setMinZoom(minZoom: number) {
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ export type SvelteFlowStoreActions = {
|
|||||||
setNodeTypes: (nodeTypes: NodeTypes) => void;
|
setNodeTypes: (nodeTypes: NodeTypes) => void;
|
||||||
setEdgeTypes: (edgeTypes: EdgeTypes) => void;
|
setEdgeTypes: (edgeTypes: EdgeTypes) => void;
|
||||||
addEdge: (edge: Edge | Connection) => void;
|
addEdge: (edge: Edge | Connection) => void;
|
||||||
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
|
zoomIn: (options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
zoomOut: (options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
setMinZoom: (minZoom: number) => void;
|
setMinZoom: (minZoom: number) => void;
|
||||||
setMaxZoom: (maxZoom: number) => void;
|
setMaxZoom: (maxZoom: number) => void;
|
||||||
setTranslateExtent: (extent: CoordinateExtent) => void;
|
setTranslateExtent: (extent: CoordinateExtent) => void;
|
||||||
fitView: (options?: FitViewOptions) => boolean;
|
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
||||||
updateNodePositions: UpdateNodePositions;
|
updateNodePositions: UpdateNodePositions;
|
||||||
updateNodeInternals: (updates: Map<string, InternalNodeUpdate>) => void;
|
updateNodeInternals: (updates: Map<string, InternalNodeUpdate>) => void;
|
||||||
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
|
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
|
||||||
addSelectedNodes: (ids: string[]) => void;
|
addSelectedNodes: (ids: string[]) => void;
|
||||||
addSelectedEdges: (ids: string[]) => void;
|
addSelectedEdges: (ids: string[]) => void;
|
||||||
handleNodeSelection: (id: string) => void;
|
handleNodeSelection: (id: string) => void;
|
||||||
panBy: (delta: XYPosition) => boolean;
|
panBy: (delta: XYPosition) => Promise<boolean>;
|
||||||
updateConnection: UpdateConnection;
|
updateConnection: UpdateConnection;
|
||||||
cancelConnection: () => void;
|
cancelConnection: () => void;
|
||||||
reset(): void;
|
reset(): void;
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ export type OnMove = (event: MouseEvent | TouchEvent | null, viewport: Viewport)
|
|||||||
export type OnMoveStart = OnMove;
|
export type OnMoveStart = OnMove;
|
||||||
export type OnMoveEnd = OnMove;
|
export type OnMoveEnd = OnMove;
|
||||||
|
|
||||||
export type ZoomInOut = (options?: ViewportHelperFunctionOptions) => void;
|
export type ZoomInOut = (options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
export type ZoomTo = (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
|
export type ZoomTo = (zoomLevel: number, options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
export type GetZoom = () => number;
|
export type GetZoom = () => number;
|
||||||
export type GetViewport = () => Viewport;
|
export type GetViewport = () => Viewport;
|
||||||
export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionOptions) => Promise<boolean>;
|
||||||
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void;
|
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>;
|
||||||
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void;
|
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>;
|
||||||
|
|
||||||
export type Connection = {
|
export type Connection = {
|
||||||
source: string;
|
source: string;
|
||||||
@@ -55,7 +55,7 @@ export type OnConnectEnd = (event: MouseEvent | TouchEvent) => void;
|
|||||||
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
||||||
|
|
||||||
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
||||||
nodeLookup: Map<string, InternalNodeBase<NodeType>>;
|
nodes: Map<string, InternalNodeBase<NodeType>>;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
panZoom: PanZoomInstance;
|
panZoom: PanZoomInstance;
|
||||||
@@ -130,7 +130,7 @@ export type SelectionRect = Rect & {
|
|||||||
export type OnError = (id: string, message: string) => void;
|
export type OnError = (id: string, message: string) => void;
|
||||||
|
|
||||||
export type UpdateNodePositions = (dragItems: Map<string, NodeDragItem | InternalNodeBase>, dragging?: boolean) => void;
|
export type UpdateNodePositions = (dragItems: Map<string, NodeDragItem | InternalNodeBase>, dragging?: boolean) => void;
|
||||||
export type PanBy = (delta: XYPosition) => boolean;
|
export type PanBy = (delta: XYPosition) => Promise<boolean>;
|
||||||
|
|
||||||
export const initialConnection: NoConnection = {
|
export const initialConnection: NoConnection = {
|
||||||
inProgress: false,
|
inProgress: false,
|
||||||
|
|||||||
@@ -45,15 +45,15 @@ export type PanZoomInstance = {
|
|||||||
update: (params: PanZoomUpdateOptions) => void;
|
update: (params: PanZoomUpdateOptions) => void;
|
||||||
destroy: () => void;
|
destroy: () => void;
|
||||||
getViewport: () => Viewport;
|
getViewport: () => Viewport;
|
||||||
setViewport: (viewport: Viewport, options?: PanZoomTransformOptions) => ZoomTransform | undefined;
|
setViewport: (viewport: Viewport, options?: PanZoomTransformOptions) => Promise<ZoomTransform | undefined>;
|
||||||
setViewportConstrained: (
|
setViewportConstrained: (
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
extent: CoordinateExtent,
|
extent: CoordinateExtent,
|
||||||
translateExtent: CoordinateExtent
|
translateExtent: CoordinateExtent
|
||||||
) => ZoomTransform | undefined;
|
) => Promise<ZoomTransform | undefined>;
|
||||||
setScaleExtent: (scaleExtent: [number, number]) => void;
|
setScaleExtent: (scaleExtent: [number, number]) => void;
|
||||||
setTranslateExtent: (translateExtent: CoordinateExtent) => void;
|
setTranslateExtent: (translateExtent: CoordinateExtent) => void;
|
||||||
scaleTo: (scale: number, options?: PanZoomTransformOptions) => void;
|
scaleTo: (scale: number, options?: PanZoomTransformOptions) => Promise<boolean>;
|
||||||
scaleBy: (factor: number, options?: PanZoomTransformOptions) => void;
|
scaleBy: (factor: number, options?: PanZoomTransformOptions) => Promise<boolean>;
|
||||||
syncViewport: (viewport: Viewport) => void;
|
syncViewport: (viewport: Viewport) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -233,39 +233,46 @@ export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType
|
|||||||
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
export function getFitViewNodes<
|
||||||
{ nodeLookup, width, height, panZoom, minZoom, maxZoom }: Params,
|
Params extends NodeLookup<InternalNodeBase<NodeBase>>,
|
||||||
options?: Options
|
Options extends FitViewOptionsBase<NodeBase>
|
||||||
) {
|
>(nodeLookup: Params, options?: Pick<Options, 'nodes' | 'includeHiddenNodes'>) {
|
||||||
const filteredNodes: Map<string, InternalNodeBase> = new Map();
|
const fitViewNodes: NodeLookup = new Map();
|
||||||
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
|
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
|
||||||
|
|
||||||
nodeLookup.forEach((n) => {
|
nodeLookup.forEach((n) => {
|
||||||
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
|
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
|
||||||
|
|
||||||
if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
|
if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
|
||||||
filteredNodes.set(n.id, n);
|
fitViewNodes.set(n.id, n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filteredNodes.size > 0) {
|
return fitViewNodes;
|
||||||
const bounds = getInternalNodesBounds(filteredNodes);
|
}
|
||||||
|
|
||||||
const viewport = getViewportForBounds(
|
export async function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
||||||
bounds,
|
{ nodes, width, height, panZoom, minZoom, maxZoom }: Params,
|
||||||
width,
|
options?: Omit<Options, 'nodes' | 'includeHiddenNodes'>
|
||||||
height,
|
): Promise<boolean> {
|
||||||
options?.minZoom ?? minZoom,
|
if (nodes.size === 0) {
|
||||||
options?.maxZoom ?? maxZoom,
|
return Promise.resolve(false);
|
||||||
options?.padding ?? 0.1
|
|
||||||
);
|
|
||||||
|
|
||||||
panZoom.setViewport(viewport, { duration: options?.duration });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
|||||||
return { changes, updatedInternals };
|
return { changes, updatedInternals };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function panBy({
|
export async function panBy({
|
||||||
delta,
|
delta,
|
||||||
panZoom,
|
panZoom,
|
||||||
transform,
|
transform,
|
||||||
@@ -339,12 +339,12 @@ export function panBy({
|
|||||||
translateExtent: CoordinateExtent;
|
translateExtent: CoordinateExtent;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}) {
|
}): Promise<boolean> {
|
||||||
if (!panZoom || (!delta.x && !delta.y)) {
|
if (!panZoom || (!delta.x && !delta.y)) {
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextViewport = panZoom.setViewportConstrained(
|
const nextViewport = await panZoom.setViewportConstrained(
|
||||||
{
|
{
|
||||||
x: transform[0] + delta.x,
|
x: transform[0] + delta.x,
|
||||||
y: transform[1] + delta.y,
|
y: transform[1] + delta.y,
|
||||||
@@ -361,7 +361,7 @@ export function panBy({
|
|||||||
!!nextViewport &&
|
!!nextViewport &&
|
||||||
(nextViewport.x !== transform[0] || nextViewport.y !== transform[1] || nextViewport.k !== transform[2]);
|
(nextViewport.x !== transform[0] || nextViewport.y !== transform[1] || nextViewport.k !== transform[2]);
|
||||||
|
|
||||||
return transformChanged;
|
return Promise.resolve(transformChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) {
|
export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoPan() {
|
async function autoPan() {
|
||||||
if (!containerBounds) {
|
if (!containerBounds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -208,10 +208,11 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
lastPos.x = (lastPos.x ?? 0) - xMovement / transform[2];
|
lastPos.x = (lastPos.x ?? 0) - xMovement / transform[2];
|
||||||
lastPos.y = (lastPos.y ?? 0) - yMovement / transform[2];
|
lastPos.y = (lastPos.y ?? 0) - yMovement / transform[2];
|
||||||
|
|
||||||
if (panBy({ x: xMovement, y: yMovement })) {
|
if (await panBy({ x: xMovement, y: yMovement })) {
|
||||||
updateNodes(lastPos as XYPosition, null);
|
updateNodes(lastPos as XYPosition, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
autoPanId = requestAnimationFrame(autoPan);
|
autoPanId = requestAnimationFrame(autoPan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,15 @@ export function XYPanZoom({
|
|||||||
|
|
||||||
function setTransform(transform: ZoomTransform, options?: PanZoomTransformOptions) {
|
function setTransform(transform: ZoomTransform, options?: PanZoomTransformOptions) {
|
||||||
if (d3Selection) {
|
if (d3Selection) {
|
||||||
d3ZoomInstance?.transform(getD3Transition(d3Selection, options?.duration), transform);
|
return new Promise<boolean>((resolve) => {
|
||||||
|
d3ZoomInstance?.transform(
|
||||||
|
getD3Transition(d3Selection, options?.duration, () => resolve(true)),
|
||||||
|
transform
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public functions
|
// public functions
|
||||||
@@ -181,27 +188,27 @@ export function XYPanZoom({
|
|||||||
d3ZoomInstance.on('zoom', null);
|
d3ZoomInstance.on('zoom', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setViewportConstrained(
|
async function setViewportConstrained(
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
extent: CoordinateExtent,
|
extent: CoordinateExtent,
|
||||||
translateExtent: CoordinateExtent
|
translateExtent: CoordinateExtent
|
||||||
): ZoomTransform | undefined {
|
): Promise<ZoomTransform | undefined> {
|
||||||
const nextTransform = viewportToTransform(viewport);
|
const nextTransform = viewportToTransform(viewport);
|
||||||
const contrainedTransform = d3ZoomInstance?.constrain()(nextTransform, extent, translateExtent);
|
const contrainedTransform = d3ZoomInstance?.constrain()(nextTransform, extent, translateExtent);
|
||||||
|
|
||||||
if (contrainedTransform) {
|
if (contrainedTransform) {
|
||||||
setTransform(contrainedTransform);
|
await setTransform(contrainedTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contrainedTransform;
|
return new Promise((resolve) => resolve(contrainedTransform));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setViewport(viewport: Viewport, options?: PanZoomTransformOptions) {
|
async function setViewport(viewport: Viewport, options?: PanZoomTransformOptions) {
|
||||||
const nextTransform = viewportToTransform(viewport);
|
const nextTransform = viewportToTransform(viewport);
|
||||||
|
|
||||||
setTransform(nextTransform, options);
|
await setTransform(nextTransform, options);
|
||||||
|
|
||||||
return nextTransform;
|
return new Promise<ZoomTransform>((resolve) => resolve(nextTransform));
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncViewport(viewport: Viewport) {
|
function syncViewport(viewport: Viewport) {
|
||||||
@@ -228,14 +235,28 @@ export function XYPanZoom({
|
|||||||
|
|
||||||
function scaleTo(zoom: number, options?: PanZoomTransformOptions) {
|
function scaleTo(zoom: number, options?: PanZoomTransformOptions) {
|
||||||
if (d3Selection) {
|
if (d3Selection) {
|
||||||
d3ZoomInstance?.scaleTo(getD3Transition(d3Selection, options?.duration), zoom);
|
return new Promise<boolean>((resolve) => {
|
||||||
|
d3ZoomInstance?.scaleTo(
|
||||||
|
getD3Transition(d3Selection, options?.duration, () => resolve(true)),
|
||||||
|
zoom
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scaleBy(factor: number, options?: PanZoomTransformOptions) {
|
function scaleBy(factor: number, options?: PanZoomTransformOptions) {
|
||||||
if (d3Selection) {
|
if (d3Selection) {
|
||||||
d3ZoomInstance?.scaleBy(getD3Transition(d3Selection, options?.duration), factor);
|
return new Promise<boolean>((resolve) => {
|
||||||
|
d3ZoomInstance?.scaleBy(
|
||||||
|
getD3Transition(d3Selection, options?.duration, () => resolve(true)),
|
||||||
|
factor
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setScaleExtent(scaleExtent: [number, number]) {
|
function setScaleExtent(scaleExtent: [number, number]) {
|
||||||
|
|||||||
@@ -21,8 +21,15 @@ export const isWrappedWithClass = (event: any, className: string | undefined) =>
|
|||||||
export const isRightClickPan = (panOnDrag: boolean | number[], usedButton: number) =>
|
export const isRightClickPan = (panOnDrag: boolean | number[], usedButton: number) =>
|
||||||
usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2);
|
usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2);
|
||||||
|
|
||||||
export const getD3Transition = (selection: D3SelectionInstance, duration = 0) =>
|
export const getD3Transition = (selection: D3SelectionInstance, duration = 0, onEnd = () => {}) => {
|
||||||
typeof duration === 'number' && duration > 0 ? selection.transition().duration(duration) : selection;
|
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) => {
|
export const wheelDelta = (event: any) => {
|
||||||
const factor = event.ctrlKey && isMacOs() ? 10 : 1;
|
const factor = event.ctrlKey && isMacOs() ? 10 : 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user