feat(svelte): add functions to useSvelteFlow
This commit is contained in:
@@ -2,17 +2,18 @@
|
|||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import type { PanelProps } from './types';
|
import type { PanelProps } from './types';
|
||||||
|
|
||||||
interface $$Props extends PanelProps {}
|
type $$Props = PanelProps;
|
||||||
|
|
||||||
export let position: $$Props['position'] = 'top-right';
|
export let position: $$Props['position'] = 'top-right';
|
||||||
export let style: $$Props['style'] = '';
|
export let style: $$Props['style'] = undefined;
|
||||||
|
|
||||||
let className: $$Props['class'] = undefined;
|
let className: $$Props['class'] = undefined;
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
$: positionClasses = `${position}`.split('-');
|
$: positionClasses = `${position}`.split('-');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={cc(['svelte-flow__panel', className, ...positionClasses])} {style}>
|
<div class={cc(['svelte-flow__panel', className, ...positionClasses])} {style} {...$$restProps}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { PanelPosition } from '@reactflow/system';
|
import type { PanelPosition } from '@reactflow/system';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
export type PanelProps = {
|
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
'data-testid'?: string;
|
||||||
position?: PanelPosition;
|
position?: PanelPosition;
|
||||||
style?: string;
|
style?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
bind:this={domNode}
|
bind:this={domNode}
|
||||||
style={style}
|
style={style}
|
||||||
class={cc(['svelte-flow', className])}
|
class={cc(['svelte-flow', className])}
|
||||||
data-testid="rf__wrapper"
|
data-testid="svelte-flow__wrapper"
|
||||||
on:dragover
|
on:dragover
|
||||||
on:drop
|
on:drop
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
|||||||
@@ -1,22 +1,47 @@
|
|||||||
import type { Project, Viewport, XYPosition, ZoomInOut } from '@reactflow/system';
|
import { get, writable, type Writable } from 'svelte/store';
|
||||||
|
import type {
|
||||||
|
Project,
|
||||||
|
SetCenterOptions,
|
||||||
|
Viewport,
|
||||||
|
ViewportHelperFunctionOptions,
|
||||||
|
XYPosition,
|
||||||
|
ZoomInOut
|
||||||
|
} from '@reactflow/system';
|
||||||
|
import { zoomIdentity } from 'd3-zoom';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { FitViewOptions } from '$lib/types';
|
import type { FitViewOptions } from '$lib/types';
|
||||||
import { get, writable, type Writable } from 'svelte/store';
|
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import { pointToRendererPoint } from '@reactflow/utils';
|
import { getD3Transition, pointToRendererPoint } from '@reactflow/utils';
|
||||||
|
|
||||||
export function useSvelteFlow(): {
|
export function useSvelteFlow(): {
|
||||||
zoomIn: ZoomInOut;
|
zoomIn: ZoomInOut;
|
||||||
zoomOut: ZoomInOut;
|
zoomOut: ZoomInOut;
|
||||||
|
setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
|
||||||
|
getZoom: () => number;
|
||||||
|
setCenter: (x: number, y: number, options?: SetCenterOptions) => void;
|
||||||
|
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
||||||
|
getViewport: () => Viewport;
|
||||||
fitView: (options?: FitViewOptions) => void;
|
fitView: (options?: FitViewOptions) => void;
|
||||||
|
project: Project;
|
||||||
viewport: Writable<Viewport>;
|
viewport: Writable<Viewport>;
|
||||||
nodes: SvelteFlowStore['nodes'];
|
nodes: SvelteFlowStore['nodes'];
|
||||||
edges: SvelteFlowStore['edges'];
|
edges: SvelteFlowStore['edges'];
|
||||||
project: Project;
|
|
||||||
} {
|
} {
|
||||||
// how to get the new context here? fit view doesn't work, because the store is not updated (uses old nodes store)
|
// how to get the new context here? fit view doesn't work, because the store is not updated (uses old nodes store)
|
||||||
const { zoomIn, zoomOut, fitView, snapGrid: snapGridStore, transform, nodes, edges } = useStore();
|
const {
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
fitView,
|
||||||
|
snapGrid,
|
||||||
|
transform,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
maxZoom,
|
||||||
|
d3,
|
||||||
|
nodes,
|
||||||
|
edges
|
||||||
|
} = useStore();
|
||||||
|
|
||||||
const transformValues = get(transform);
|
const transformValues = get(transform);
|
||||||
const viewportWritable = writable({
|
const viewportWritable = writable({
|
||||||
@@ -36,10 +61,53 @@ export function useSvelteFlow(): {
|
|||||||
return {
|
return {
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
|
setZoom: (zoomLevel, options) => {
|
||||||
|
const { zoom, selection } = get(d3);
|
||||||
|
|
||||||
|
if (zoom && selection) {
|
||||||
|
zoom.scaleTo(getD3Transition(selection, options?.duration), zoomLevel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getZoom: () => get(transform)[2],
|
||||||
|
setViewport: (viewport, options) => {
|
||||||
|
const [x, y, zoom] = get(transform);
|
||||||
|
const { zoom: d3Zoom, selection } = get(d3);
|
||||||
|
|
||||||
|
if (d3Zoom && selection) {
|
||||||
|
const nextTransform = zoomIdentity
|
||||||
|
.translate(viewport.x ?? x, viewport.y ?? y)
|
||||||
|
.scale(viewport.zoom ?? zoom);
|
||||||
|
d3Zoom.transform(getD3Transition(selection, options?.duration), nextTransform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getViewport: () => {
|
||||||
|
const [x, y, zoom] = get(transform);
|
||||||
|
return { x, y, zoom };
|
||||||
|
},
|
||||||
|
setCenter: (x, y, options) => {
|
||||||
|
const _width = get(width);
|
||||||
|
const _height = get(height);
|
||||||
|
const _maxZoom = get(maxZoom);
|
||||||
|
const { zoom, selection } = get(d3);
|
||||||
|
|
||||||
|
if (zoom && selection) {
|
||||||
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : _maxZoom;
|
||||||
|
const centerX = _width / 2 - x * nextZoom;
|
||||||
|
const centerY = _height / 2 - y * nextZoom;
|
||||||
|
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
|
||||||
|
|
||||||
|
zoom.transform(getD3Transition(selection, options?.duration), transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
fitView,
|
fitView,
|
||||||
project: (position: XYPosition) => {
|
project: (position: XYPosition) => {
|
||||||
const snapGrid = get(snapGridStore);
|
const _snapGrid = get(snapGrid);
|
||||||
return pointToRendererPoint(position, get(transform), snapGrid !== null, snapGrid || [1, 1]);
|
return pointToRendererPoint(
|
||||||
|
position,
|
||||||
|
get(transform),
|
||||||
|
_snapGrid !== null,
|
||||||
|
_snapGrid || [1, 1]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
|
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class={cc(['svelte-flow__background', className])} style={style}>
|
<svg class={cc(['svelte-flow__background', className])} {style} data-testid="svelte-flow__background">
|
||||||
<pattern
|
<pattern
|
||||||
id={patternId}
|
id={patternId}
|
||||||
x={$transform[0] % scaledGap[0]}
|
x={$transform[0] % scaledGap[0]}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
// onInteractiveChange?.(!isInteractive);
|
// onInteractiveChange?.(!isInteractive);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel class="svelte-flow__controls" {position}>
|
<Panel class="svelte-flow__controls" {position} data-testid="svelte-flow__controls">
|
||||||
{#if showZoom}
|
{#if showZoom}
|
||||||
<ControlButton
|
<ControlButton
|
||||||
on:click={onZoomInHandler}
|
on:click={onZoomInHandler}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
const defaultWidth = 200;
|
const defaultWidth = 200;
|
||||||
const defaultHeight = 150;
|
const defaultHeight = 150;
|
||||||
const { nodes, transform, width: containerWidth, height: containerHeight, nodeOrigin, id } = useStore();
|
const { nodes, transform, width: containerWidth, height: containerHeight, id } = useStore();
|
||||||
|
|
||||||
|
|
||||||
const nodeColorFunc = getAttrFunction(nodeColor);
|
const nodeColorFunc = getAttrFunction(nodeColor);
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
height: $containerHeight / $transform[2]
|
height: $containerHeight / $transform[2]
|
||||||
};
|
};
|
||||||
$: boundingRect =
|
$: boundingRect =
|
||||||
$nodes.length > 0 ? getBoundsOfRects(getRectOfNodes($nodes, $nodeOrigin), viewBB) : viewBB;
|
$nodes.length > 0 ? getBoundsOfRects(getRectOfNodes($nodes), viewBB) : viewBB;
|
||||||
$: elementWidth = width ?? defaultWidth;
|
$: elementWidth = width ?? defaultWidth;
|
||||||
$: elementHeight = height ?? defaultHeight;
|
$: elementHeight = height ?? defaultHeight;
|
||||||
$: scaledWidth = boundingRect.width / elementWidth;
|
$: scaledWidth = boundingRect.width / elementWidth;
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
{position}
|
{position}
|
||||||
class={cc(['svelte-flow__minimap', className])}
|
class={cc(['svelte-flow__minimap', className])}
|
||||||
style={`background-color: ${bgColor}; ${style}`}
|
style={`background-color: ${bgColor}; ${style}`}
|
||||||
|
data-testid="svelte-flow__minimap"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width={elementWidth}
|
width={elementWidth}
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
|
|
||||||
{#each $nodes as node (node.id)}
|
{#each $nodes as node (node.id)}
|
||||||
{#if node.width && node.height}
|
{#if node.width && node.height}
|
||||||
{@const pos = getNodePositionWithOrigin(node, $nodeOrigin).positionAbsolute}
|
{@const pos = getNodePositionWithOrigin(node).positionAbsolute}
|
||||||
<MinimapNode
|
<MinimapNode
|
||||||
x={pos.x}
|
x={pos.x}
|
||||||
y={pos.y}
|
y={pos.y}
|
||||||
|
|||||||
@@ -127,19 +127,20 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
|
|||||||
store.nodes.set(nextNodes);
|
store.nodes.set(nextNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
|
|
||||||
if (d3Zoom && d3Selection) {
|
if (d3Zoom && d3Selection) {
|
||||||
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
|
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), factor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
||||||
|
zoomBy(1.2, options);
|
||||||
|
}
|
||||||
|
|
||||||
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
zoomBy(1 / 1.2, options);
|
||||||
if (d3Zoom && d3Selection) {
|
|
||||||
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMinZoom(minZoom: number) {
|
function setMinZoom(minZoom: number) {
|
||||||
|
|||||||
@@ -3,14 +3,18 @@
|
|||||||
useSvelteFlow
|
useSvelteFlow
|
||||||
} from '../../lib/index';
|
} from '../../lib/index';
|
||||||
|
|
||||||
const { zoomIn, zoomOut, fitView, viewport, nodes } = useSvelteFlow();
|
const { zoomIn, zoomOut, setZoom, fitView, setCenter, setViewport, getViewport, viewport, nodes } = useSvelteFlow();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<div class="label">Functions:</div>
|
<div class="label">Functions:</div>
|
||||||
<button on:click={() => zoomIn()}>zoom in</button>
|
<button on:click={() => zoomIn()}>zoom in</button>
|
||||||
<button on:click={() => zoomOut({ duration: 1000 })}>zoom out transition</button>
|
<button on:click={() => zoomOut({ duration: 1000 })}>zoom out transition</button>
|
||||||
|
<button on:click={() => setZoom(2)}>set zoom</button>
|
||||||
<button on:click={() => fitView()}>fitView</button>
|
<button on:click={() => fitView()}>fitView</button>
|
||||||
|
<button on:click={() => setCenter(0, 0)}>setCenter 0, 0</button>
|
||||||
|
<button on:click={() => setViewport({ x: 100, y: 100, zoom: 2 })}>setViewport</button>
|
||||||
|
<button on:click={() => console.log(getViewport())}>getViewport</button>
|
||||||
|
|
||||||
<div class="label">Nodes:</div>
|
<div class="label">Nodes:</div>
|
||||||
{#each $nodes as node (node.id)}
|
{#each $nodes as node (node.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user