feat(svelte): add functions to useSvelteFlow

This commit is contained in:
moklick
2023-03-14 17:24:01 +01:00
parent 2e486384a8
commit 5b3c260e45
9 changed files with 102 additions and 25 deletions
@@ -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}
+75 -7
View File
@@ -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]}
@@ -43,7 +43,7 @@
}; };
</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}
+7 -6
View File
@@ -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)}