feat(svelte): add SvelteFlowProvider and useSvelteFlow hook
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
|
||||
const routes = [
|
||||
'overview',
|
||||
'stress'
|
||||
'stress',
|
||||
'usesvelteflow'
|
||||
];
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
export let selected: $$Props['selected'] = false;
|
||||
export let label: $$Props['label'] = undefined;
|
||||
|
||||
const { edgeTypes, edges } = useStore();
|
||||
const { edges, edgeTypes } = useStore();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
import type { KeyHandlerProps } from './types';
|
||||
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
|
||||
|
||||
const { selectionKeyPressed, deleteKeyPressed } = useStore();
|
||||
|
||||
type $$Props = KeyHandlerProps;
|
||||
|
||||
export let selectionKey: $$Props['selectionKey'] = 'Shift';
|
||||
export let deleteKey: $$Props['deleteKey'] = 'Backspace';
|
||||
|
||||
const { selectionKeyPressed, deleteKeyPressed } = useStore();
|
||||
|
||||
function isKeyObject(key?: KeyDefinition): key is KeyDefinitionObject {
|
||||
return typeof key === 'object';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
import { createStore, key } from '$lib/store';
|
||||
|
||||
const store = createStore();
|
||||
|
||||
setContext(key, {
|
||||
getStore: () => store
|
||||
});
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as SvelteFlowProvider } from './SvelteFlowProvider.svelte';
|
||||
export type { SvelteFlowProviderProps } from './types';
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { SvelteFlowProps } from '$lib/container/SvelteFlow/types';
|
||||
|
||||
export type SvelteFlowProviderProps = Pick<
|
||||
SvelteFlowProps,
|
||||
'nodes' | 'edges' | 'fitView' | 'nodeTypes'
|
||||
>;
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { getPositionWithOrigin } from '@reactflow/utils';
|
||||
|
||||
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
||||
import { useStore } from '$lib/store';
|
||||
import { getPositionWithOrigin } from '@reactflow/utils';
|
||||
import { Position } from '@reactflow/system';
|
||||
|
||||
const { nodes, nodeOrigin, updateNodeDimensions } = useStore();
|
||||
|
||||
const resizeObserver: ResizeObserver | null =
|
||||
typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
@@ -16,7 +16,6 @@
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true
|
||||
}));
|
||||
|
||||
updateNodeDimensions(updates);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { setContext, onMount } from 'svelte';
|
||||
import { onMount, setContext } from 'svelte';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { key, createStore } from '$lib/store';
|
||||
import { Zoom } from '$lib/container/Zoom';
|
||||
import { Pane } from '$lib/container/Pane';
|
||||
import { Viewport } from '$lib/container/Viewport';
|
||||
@@ -12,6 +11,7 @@
|
||||
import { NodeSelection } from '$lib/components/NodeSelection';
|
||||
import { KeyHandler } from '$lib/components/KeyHandler';
|
||||
import { ConnectionLine } from '$lib/components/ConnectionLine';
|
||||
import { createStore, key, useStore } from '$lib/store';
|
||||
import type { SvelteFlowProps, SvelteFlowEvents } from './types';
|
||||
|
||||
type $$Props = SvelteFlowProps;
|
||||
@@ -40,9 +40,9 @@
|
||||
nodes,
|
||||
edges,
|
||||
fitView,
|
||||
nodeTypes
|
||||
});
|
||||
|
||||
// we overwrite the context to be able to use the nodes and edges stores passed by the user
|
||||
setContext(key, {
|
||||
getStore: () => store
|
||||
});
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
$: {
|
||||
if (nodeTypes !== undefined) {
|
||||
store.setNodeTypes(nodeTypes);
|
||||
store.setNodeTypes(nodeTypes);
|
||||
}
|
||||
|
||||
if (edgeTypes !== undefined) {
|
||||
|
||||
25
packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte
Normal file
25
packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { hasContext } from 'svelte';
|
||||
import { key } from '$lib/store';
|
||||
|
||||
import SvelteFlow from './SvelteFlow.svelte';
|
||||
import { SvelteFlowProvider } from '$lib/components/SvelteFlowProvider';
|
||||
import type { SvelteFlowProps } from './types';
|
||||
|
||||
type $$Props = SvelteFlowProps;
|
||||
|
||||
// @todo how to do this typing right?
|
||||
const props = $$props as SvelteFlowProps;
|
||||
</script>
|
||||
|
||||
{#if hasContext(key)}
|
||||
<SvelteFlow {...props}>
|
||||
<slot />
|
||||
</SvelteFlow>
|
||||
{:else}
|
||||
<SvelteFlowProvider>
|
||||
<SvelteFlow {...props}>
|
||||
<slot />
|
||||
</SvelteFlow>
|
||||
</SvelteFlowProvider>
|
||||
{/if}
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as SvelteFlow } from './SvelteFlow.svelte';
|
||||
export { default as SvelteFlow } from './Wrapper.svelte';
|
||||
export * from './types';
|
||||
|
||||
19
packages/svelte/src/lib/hooks/useSvelteFlow.ts
Normal file
19
packages/svelte/src/lib/hooks/useSvelteFlow.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { ZoomInOut } from '@reactflow/system';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
import type { FitViewOptions } from '$lib/types';
|
||||
|
||||
export function useSvelteFlow(): {
|
||||
zoomIn: ZoomInOut;
|
||||
zoomOut: ZoomInOut;
|
||||
fitView: (options?: FitViewOptions) => void;
|
||||
} {
|
||||
// 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 } = useStore();
|
||||
|
||||
return {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitView
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import { SvelteFlow } from '$lib/container/SvelteFlow';
|
||||
export * from '$lib/container/SvelteFlow';
|
||||
export * from '$lib/container/Panel';
|
||||
|
||||
export * from '$lib/components/SvelteFlowProvider';
|
||||
|
||||
export * from '$lib/plugins/Controls';
|
||||
export * from '$lib/plugins/Background';
|
||||
export * from '$lib/plugins/Minimap';
|
||||
@@ -10,4 +12,6 @@ export * from '$lib/plugins/Minimap';
|
||||
export * from '$lib/types';
|
||||
export * from '$lib/utils';
|
||||
|
||||
export * from '$lib/hooks/useSvelteFlow';
|
||||
|
||||
export default SvelteFlow;
|
||||
|
||||
@@ -2,11 +2,9 @@ import { getContext } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { zoomIdentity } from 'd3-zoom';
|
||||
import {
|
||||
type Transform,
|
||||
type NodeDragItem,
|
||||
type NodeDimensionUpdate,
|
||||
internalsSymbol,
|
||||
type NodeOrigin,
|
||||
type ViewportHelperFunctionOptions,
|
||||
type Connection,
|
||||
type XYPosition,
|
||||
@@ -30,30 +28,15 @@ import {
|
||||
initialStoreState
|
||||
} from './initial-store';
|
||||
import type { SvelteFlowStore } from './types';
|
||||
import type { SvelteFlowProps } from '$lib/container/SvelteFlow/types';
|
||||
|
||||
export const key = Symbol();
|
||||
|
||||
type CreateStoreProps = {
|
||||
nodes: SvelteFlowProps['nodes'];
|
||||
edges: SvelteFlowProps['edges'];
|
||||
fitView?: boolean;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
transform?: Transform;
|
||||
nodeTypes?: NodeTypes;
|
||||
edgeTypes?: EdgeTypes;
|
||||
id?: string;
|
||||
};
|
||||
type CreateStoreParams = Pick<SvelteFlowStore, 'nodes' | 'edges'> & { fitView?: boolean };
|
||||
|
||||
export function createStore({
|
||||
fitView: fitViewOnInit = false,
|
||||
nodes,
|
||||
edges
|
||||
}: CreateStoreProps): SvelteFlowStore {
|
||||
export function createStore(params?: CreateStoreParams): SvelteFlowStore {
|
||||
const store = {
|
||||
...initialStoreState,
|
||||
nodes,
|
||||
edges
|
||||
...(params !== undefined ? params : {})
|
||||
};
|
||||
|
||||
let fitViewOnInitDone = false;
|
||||
@@ -105,7 +88,6 @@ export function createStore({
|
||||
|
||||
const style = window.getComputedStyle(viewportNode);
|
||||
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
||||
|
||||
const nextNodes = get(store.nodes).map((node) => {
|
||||
const update = updates.find((u) => u.id === node.id);
|
||||
|
||||
@@ -139,7 +121,7 @@ export function createStore({
|
||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||
|
||||
fitViewOnInitDone =
|
||||
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
|
||||
fitViewOnInitDone || (!!params?.fitView && !!d3Zoom && !!d3Selection && fitView());
|
||||
|
||||
store.nodes.set(nextNodes);
|
||||
}
|
||||
@@ -333,7 +315,13 @@ export function createStore({
|
||||
}
|
||||
|
||||
export function useStore(): SvelteFlowStore {
|
||||
const { getStore } = getContext<{ getStore: () => SvelteFlowStore }>(key);
|
||||
const store = getContext<{ getStore: () => SvelteFlowStore }>(key);
|
||||
|
||||
return getStore();
|
||||
if (!store) {
|
||||
throw new Error(
|
||||
'In order to use useStore you need to wrap your component in a <SvelteFlowProvider />'
|
||||
);
|
||||
}
|
||||
|
||||
return store.getStore();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import OutputNode from '$lib/components/nodes/OutputNode.svelte';
|
||||
import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
|
||||
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
|
||||
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
|
||||
import type { ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted } from '$lib/types';
|
||||
import type { ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted, Edge, Node } from '$lib/types';
|
||||
|
||||
export const initConnectionData = {
|
||||
nodeId: null,
|
||||
@@ -40,11 +40,14 @@ export const initialEdgeTypes = {
|
||||
|
||||
export const initialStoreState = {
|
||||
id: writable<string | null>(null),
|
||||
nodes: writable<Node[]>([]),
|
||||
edges: writable<Edge[]>([]),
|
||||
edgesLayouted: readable<EdgeLayouted[]>([]),
|
||||
height: writable<number>(500),
|
||||
width: writable<number>(500),
|
||||
minZoom: writable<number>(0.5),
|
||||
maxZoom: writable<number>(2),
|
||||
fitViewOnInit: false,
|
||||
nodeOrigin: writable<NodeOrigin>([0, 0]),
|
||||
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
|
||||
zoom: null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ShortcutModifierDefinition } from '@svelte-put/shortcut';
|
||||
import type { HandleProps, HandleType, XYPosition } from '@reactflow/system';
|
||||
import type { FitViewOptionsBase, HandleProps, HandleType, XYPosition } from '@reactflow/system';
|
||||
|
||||
import type { Node } from './nodes';
|
||||
|
||||
export type KeyModifier = ShortcutModifierDefinition;
|
||||
export type KeyDefinitionObject = { key: string; modifier?: KeyModifier };
|
||||
@@ -17,3 +19,5 @@ export type HandleComponentProps = HandleProps & {
|
||||
class?: string;
|
||||
style?: string;
|
||||
};
|
||||
|
||||
export type FitViewOptions = FitViewOptionsBase<Node>;
|
||||
|
||||
@@ -2,5 +2,21 @@
|
||||
import { Header } from '../example-components/Header'
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
<slot />
|
||||
<div class="app">
|
||||
<Header />
|
||||
<div class="flow">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flow {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
11
packages/svelte/src/routes/usesvelteflow/+page.svelte
Normal file
11
packages/svelte/src/routes/usesvelteflow/+page.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
SvelteFlowProvider,
|
||||
} from '../../lib/index';
|
||||
|
||||
import Flow from './Flow.svelte';
|
||||
</script>
|
||||
|
||||
<SvelteFlowProvider>
|
||||
<Flow />
|
||||
</SvelteFlowProvider>
|
||||
65
packages/svelte/src/routes/usesvelteflow/Flow.svelte
Normal file
65
packages/svelte/src/routes/usesvelteflow/Flow.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import SvelteFlow, {
|
||||
Controls,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Minimap,
|
||||
Panel,
|
||||
createNodes,
|
||||
createEdges,
|
||||
useSvelteFlow
|
||||
} from '../../lib/index';
|
||||
|
||||
const nodes = createNodes([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Input Node' },
|
||||
position: { x: 150, y: 5 }
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'default',
|
||||
data: { label: 'Node' },
|
||||
position: { x: 0, y: 150 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'output',
|
||||
data: { label: 'Output Node' },
|
||||
position: { x: 300, y: 150 }
|
||||
}
|
||||
]);
|
||||
|
||||
const edges = createEdges([
|
||||
{
|
||||
id: '1-2',
|
||||
type: 'default',
|
||||
source: '1',
|
||||
target: '2',
|
||||
label: 'Edge Text'
|
||||
},
|
||||
{
|
||||
id: '1-3',
|
||||
type: 'smoothstep',
|
||||
source: '1',
|
||||
target: '3'
|
||||
}
|
||||
]);
|
||||
|
||||
const svelteFlow = useSvelteFlow();
|
||||
</script>
|
||||
|
||||
<SvelteFlow
|
||||
{nodes}
|
||||
{edges}
|
||||
>
|
||||
<Controls />
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<Minimap />
|
||||
<Panel position="top-right">
|
||||
<button on:click={() => svelteFlow.zoomIn()}>zoom in</button>
|
||||
<button on:click={() => svelteFlow.zoomOut({ duration: 1000 })}>zoom out transition</button>
|
||||
<button on:click={() => svelteFlow.fitView()}>fitView</button>
|
||||
</Panel>
|
||||
</SvelteFlow>
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Selection as D3Selection } from 'd3';
|
||||
import { zoomIdentity } from 'd3-zoom';
|
||||
|
||||
import { boxToRect, clamp, devWarn, getBoundsOfBoxes, getOverlappingArea, rectToBox } from './utils';
|
||||
@@ -11,10 +10,11 @@ import {
|
||||
type XYPosition,
|
||||
type Rect,
|
||||
type NodeOrigin,
|
||||
BaseNode,
|
||||
BaseEdge,
|
||||
FitViewParamsBase,
|
||||
FitViewOptionsBase,
|
||||
type BaseNode,
|
||||
type BaseEdge,
|
||||
type FitViewParamsBase,
|
||||
type FitViewOptionsBase,
|
||||
type D3SelectionInstance,
|
||||
} from '@reactflow/system';
|
||||
|
||||
export const isEdgeBase = <NodeType extends BaseNode = BaseNode, EdgeType extends BaseEdge = BaseEdge>(
|
||||
@@ -304,7 +304,7 @@ export const getTransformForBounds = (
|
||||
return [x, y, clampedZoom];
|
||||
};
|
||||
|
||||
export const getD3Transition = (selection: D3Selection<Element, unknown, null, undefined>, duration = 0) => {
|
||||
export const getD3Transition = (selection: D3SelectionInstance, duration = 0) => {
|
||||
return selection.transition().duration(duration);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user