+
{@render children?.()}
diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
index acaf86c6..d235ee72 100644
--- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
+++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
@@ -9,10 +9,12 @@
import { useHandleEdgeSelect } from '$lib/hooks/useHandleEdgeSelect';
import type { EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
+ import type { SvelteFlowStore } from '$lib/store/types';
const {
id,
type = 'default',
+ store,
source,
target,
data = {},
@@ -42,24 +44,24 @@
onedgecontextmenu,
onedgemouseenter,
onedgemouseleave
- }: EdgeLayouted & EdgeEvents = $props();
+ }: { store: SvelteFlowStore } & EdgeLayouted & EdgeEvents = $props();
setContext('svelteflow__edge_id', id);
- const { edgeLookup, edgeTypes, flowId, elementsSelectable } = useStore();
-
let edgeType = $derived(type ?? 'default');
- let EdgeComponent = $derived($edgeTypes[edgeType] ?? BezierEdgeInternal);
+ let EdgeComponent = $derived(store.edgeTypes[edgeType] ?? BezierEdgeInternal);
let markerStartUrl = $derived(
- markerStart ? `url('#${getMarkerId(markerStart, $flowId)}')` : undefined
+ markerStart ? `url('#${getMarkerId(markerStart, store.flowId)}')` : undefined
);
- let markerEndUrl = $derived(markerEnd ? `url('#${getMarkerId(markerEnd, $flowId)}')` : undefined);
- let isSelectable = $derived(selectable ?? $elementsSelectable);
+ let markerEndUrl = $derived(
+ markerEnd ? `url('#${getMarkerId(markerEnd, store.flowId)}')` : undefined
+ );
+ let isSelectable = $derived(selectable ?? store.elementsSelectable);
const handleEdgeSelect = useHandleEdgeSelect();
function onclick(event: MouseEvent | TouchEvent) {
- const edge = $edgeLookup.get(id);
+ const edge = store.edgeLookup.get(id);
if (edge) {
handleEdgeSelect(id);
@@ -71,7 +73,7 @@
event: MouseEvent,
callback: ({ edge, event }: { edge: Edge; event: MouseEvent }) => void
) {
- const edge = $edgeLookup.get(id);
+ const edge = store.edgeLookup.get(id);
if (edge) {
callback({ event, edge });
diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte
index f0721989..eb847c44 100644
--- a/packages/svelte/src/lib/components/Handle/Handle.svelte
+++ b/packages/svelte/src/lib/components/Handle/Handle.svelte
@@ -39,28 +39,8 @@
isConnectableProp !== undefined ? isConnectableProp : isConnectableContext.value
);
- const {
- connectionMode,
- domNode,
- nodeLookup,
- connectionRadius,
- viewport,
- isValidConnection: isValidConnectionStore,
- lib,
- addEdge,
- onedgecreate,
- panBy,
- cancelConnection,
- updateConnection,
- autoPanOnConnect,
- edges,
- connectionLookup,
- onconnect: onConnectAction,
- onconnectstart: onConnectStartAction,
- onconnectend: onConnectEndAction,
- flowId,
- connection
- } = useStore();
+ let store = useStore();
+ let { viewport, edges } = store;
function onPointerDown(event: MouseEvent | TouchEvent) {
const isMouseTriggered = isMouseEvent(event);
@@ -70,39 +50,39 @@
handleId,
nodeId,
isTarget,
- connectionRadius: $connectionRadius,
- domNode: $domNode,
- nodeLookup: $nodeLookup,
- connectionMode: $connectionMode,
- lib: $lib,
- autoPanOnConnect: $autoPanOnConnect,
- flowId: $flowId,
- isValidConnection: isValidConnectionProp ?? $isValidConnectionStore,
- updateConnection,
- cancelConnection,
- panBy,
+ connectionRadius: store.connectionRadius,
+ domNode: store.domNode,
+ nodeLookup: store.nodeLookup,
+ connectionMode: store.connectionMode,
+ lib: 'svelte',
+ autoPanOnConnect: store.autoPanOnConnect,
+ flowId: store.flowId,
+ isValidConnection: isValidConnectionProp ?? store.isValidConnection,
+ updateConnection: store.updateConnection,
+ cancelConnection: store.cancelConnection,
+ panBy: store.panBy,
onConnect: (connection) => {
- const edge = $onedgecreate ? $onedgecreate(connection) : connection;
+ const edge = store.onedgecreate ? store.onedgecreate(connection) : connection;
if (!edge) {
return;
}
- addEdge(edge);
- $onConnectAction?.(connection);
+ store.addEdge(edge);
+ store.onconnect?.(connection);
},
onConnectStart: (event, startParams) => {
- $onConnectStartAction?.(event, {
+ store.onconnectstart?.(event, {
nodeId: startParams.nodeId,
handleId: startParams.handleId,
handleType: startParams.handleType
});
},
onConnectEnd: (event, connectionState) => {
- $onConnectEndAction?.(event, connectionState);
+ store.onconnectend?.(event, connectionState);
},
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
- getFromHandle: () => $connection.fromHandle
+ getFromHandle: () => store.connection.fromHandle
});
}
}
@@ -113,7 +93,7 @@
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
$edges;
if (onconnect || ondisconnect) {
- let connections = $connectionLookup.get(`${nodeId}-${type}-${handleId}`);
+ let connections = store.connectionLookup.get(`${nodeId}-${type}-${handleId}`);
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
const _connections = connections ?? new Map();
@@ -128,7 +108,7 @@
let [connectionInProcess, connectingFrom, connectingTo, isPossibleEndHandle, valid] = $derived.by(
() => {
- const { fromHandle, toHandle, isValid } = $connection;
+ const { fromHandle, toHandle, isValid } = store.connection;
const connectionInProcess = !!fromHandle;
@@ -139,7 +119,7 @@
toHandle?.nodeId === nodeId && toHandle?.type === type && toHandle?.id === handleId;
const isPossibleEndHandle =
- $connectionMode === ConnectionMode.Strict
+ store.connectionMode === ConnectionMode.Strict
? fromHandle?.type !== type
: nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id;
@@ -158,7 +138,7 @@ The Handle component is the part of a node that can be used to connect nodes.
data-handleid={handleId}
data-nodeid={nodeId}
data-handlepos={position}
- data-id="{$flowId}-{nodeId}-{handleId}-{type}"
+ data-id="{store.flowId}-{nodeId}-{handleId}-{type}"
class={cc([
'svelte-flow__handle',
`svelte-flow__handle-${position}`,
diff --git a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte
index 721365e7..ccbbf90a 100644
--- a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte
+++ b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte
@@ -12,6 +12,7 @@
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
let {
+ store,
selectionKey = 'Shift',
multiSelectionKey = isMacOs() ? 'Meta' : 'Control',
deleteKey = 'Backspace',
@@ -19,18 +20,7 @@
zoomActivationKey = isMacOs() ? 'Meta' : 'Control'
}: KeyHandlerProps = $props();
- const {
- selectionKeyPressed,
- multiselectionKeyPressed,
- deleteKeyPressed,
- panActivationKeyPressed,
- zoomActivationKeyPressed,
- selectionRect,
- onbeforedelete,
- ondelete,
- nodes: _nodes,
- edges: _edges
- } = useStore();
+ const { nodes: _nodes, edges: _edges } = store;
function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject {
return key !== null && typeof key === 'object';
@@ -67,12 +57,12 @@
}
function resetKeysAndSelection() {
- selectionRect.set(null);
- selectionKeyPressed.set(false);
- multiselectionKeyPressed.set(false);
- deleteKeyPressed.set(false);
- panActivationKeyPressed.set(false);
- zoomActivationKeyPressed.set(false);
+ store.selectionRect = null;
+ store.selectionKeyPressed = false;
+ store.multiselectionKeyPressed = false;
+ store.deleteKeyPressed = false;
+ store.panActivationKeyPressed = false;
+ store.zoomActivationKeyPressed = false;
}
async function handleDelete() {
@@ -86,14 +76,14 @@
edgesToRemove: selectedEdges,
nodes,
edges,
- onBeforeDelete: get(onbeforedelete)
+ onBeforeDelete: store.onbeforedelete
});
if (matchingNodes.length || matchingEdges.length) {
_nodes.update((nds) => nds.filter((node) => !matchingNodes.some((mN) => mN.id === node.id)));
_edges.update((eds) => eds.filter((edge) => !matchingEdges.some((mE) => mE.id === edge.id)));
- get(ondelete)?.({
+ store.ondelete?.({
nodes: matchingNodes,
edges: matchingEdges
});
@@ -105,19 +95,19 @@
on:blur={resetKeysAndSelection}
on:contextmenu={resetKeysAndSelection}
use:shortcut={{
- trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(true)),
+ trigger: getShortcutTrigger(selectionKey, () => (store.selectionKeyPressed = true)),
type: 'keydown'
}}
use:shortcut={{
- trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(false)),
+ trigger: getShortcutTrigger(selectionKey, () => (store.selectionKeyPressed = false)),
type: 'keyup'
}}
use:shortcut={{
- trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(true)),
+ trigger: getShortcutTrigger(multiSelectionKey, () => (store.multiselectionKeyPressed = true)),
type: 'keydown'
}}
use:shortcut={{
- trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(false)),
+ trigger: getShortcutTrigger(multiSelectionKey, () => (store.multiselectionKeyPressed = false)),
type: 'keyup'
}}
use:shortcut={{
@@ -127,30 +117,30 @@
detail.originalEvent.metaKey ||
detail.originalEvent.shiftKey;
if (!isModifierKey && !isInputDOMNode(detail.originalEvent)) {
- deleteKeyPressed.set(true);
+ store.deleteKeyPressed = true;
handleDelete();
}
}),
type: 'keydown'
}}
use:shortcut={{
- trigger: getShortcutTrigger(deleteKey, () => deleteKeyPressed.set(false)),
+ trigger: getShortcutTrigger(deleteKey, () => (store.deleteKeyPressed = false)),
type: 'keyup'
}}
use:shortcut={{
- trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(true)),
+ trigger: getShortcutTrigger(panActivationKey, () => (store.panActivationKeyPressed = true)),
type: 'keydown'
}}
use:shortcut={{
- trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(false)),
+ trigger: getShortcutTrigger(panActivationKey, () => (store.panActivationKeyPressed = false)),
type: 'keyup'
}}
use:shortcut={{
- trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(true)),
+ trigger: getShortcutTrigger(zoomActivationKey, () => (store.zoomActivationKeyPressed = true)),
type: 'keydown'
}}
use:shortcut={{
- trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(false)),
+ trigger: getShortcutTrigger(zoomActivationKey, () => (store.zoomActivationKeyPressed = false)),
type: 'keyup'
}}
/>
diff --git a/packages/svelte/src/lib/components/KeyHandler/types.ts b/packages/svelte/src/lib/components/KeyHandler/types.ts
index 0df03d76..a7461883 100644
--- a/packages/svelte/src/lib/components/KeyHandler/types.ts
+++ b/packages/svelte/src/lib/components/KeyHandler/types.ts
@@ -1,6 +1,8 @@
+import type { SvelteFlowStore } from '$lib/store/types';
import type { KeyDefinition } from '$lib/types';
export type KeyHandlerProps = {
+ store: SvelteFlowStore;
selectionKey?: KeyDefinition | KeyDefinition[] | null;
multiSelectionKey?: KeyDefinition | KeyDefinition[] | null;
deleteKey?: KeyDefinition | KeyDefinition[] | null;
diff --git a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte
index f9e9cf88..0a8a8957 100644
--- a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte
+++ b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte
@@ -1,13 +1,13 @@
-{#if $selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
+{#if store.selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
;
diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte
index 308d0167..03e6cc70 100644
--- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte
+++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte
@@ -1,17 +1,16 @@
diff --git a/packages/svelte/src/lib/components/ViewportPortal/ViewportPortal.svelte b/packages/svelte/src/lib/components/ViewportPortal/ViewportPortal.svelte
index 2262c26b..d6aeb46a 100644
--- a/packages/svelte/src/lib/components/ViewportPortal/ViewportPortal.svelte
+++ b/packages/svelte/src/lib/components/ViewportPortal/ViewportPortal.svelte
@@ -6,9 +6,9 @@
let { children }: { children?: Snippet } = $props();
- const { domNode } = useStore();
+ const store = useStore();
-
+
{@render children?.()}
diff --git a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte
index 2fab5055..5c2ab8b5 100644
--- a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte
+++ b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte
@@ -1,29 +1,59 @@
@@ -32,8 +62,9 @@
- {#each $visibleEdges as edge (edge.id)}
+ {#each layoutedEdges as edge (edge.id)}
{/each}
- {#if $visibleEdges.length > 0}
+ {#if layoutedEdges.length > 0}
{
- $edgesInitialized = true;
+ store.edgesInitialized = true;
}}
onDestroy={() => {
- $edgesInitialized = false;
+ store.edgesInitialized = false;
}}
/>
{/if}
diff --git a/packages/svelte/src/lib/container/EdgeRenderer/MarkerDefinition/MarkerDefinition.svelte b/packages/svelte/src/lib/container/EdgeRenderer/MarkerDefinition/MarkerDefinition.svelte
index 3a1509a9..9280614c 100644
--- a/packages/svelte/src/lib/container/EdgeRenderer/MarkerDefinition/MarkerDefinition.svelte
+++ b/packages/svelte/src/lib/container/EdgeRenderer/MarkerDefinition/MarkerDefinition.svelte
@@ -2,11 +2,11 @@
import { useStore } from '$lib/store';
import { Marker } from '$lib/container/EdgeRenderer/MarkerDefinition';
- const { markers } = useStore();
+ const store = useStore();
- {#each $markers as marker (marker.id)}
+ {#each store.markers as marker (marker.id)}
{/each}
diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte
index 8d72d86e..138a9d1d 100644
--- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte
+++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte
@@ -4,9 +4,10 @@
import type { NodeEvents } from '$lib/types';
import { NodeWrapper } from '$lib/components/NodeWrapper';
- import { useStore } from '$lib/store';
+ import type { SvelteFlowStore } from '$lib/store/types';
let {
+ store,
nodeClickDistance,
onnodeclick,
onnodecontextmenu,
@@ -16,16 +17,9 @@
onnodedrag,
onnodedragstart,
onnodedragstop
- }: { nodeClickDistance?: number } & NodeEvents = $props();
+ }: { store: SvelteFlowStore; nodeClickDistance?: number } & NodeEvents = $props();
- const {
- visibleNodes,
- nodesDraggable,
- nodesConnectable,
- elementsSelectable,
- updateNodeInternals,
- parentLookup
- } = useStore();
+ const { nodes } = store;
const resizeObserver: ResizeObserver | null =
typeof ResizeObserver === 'undefined'
@@ -43,7 +37,7 @@
});
});
- updateNodeInternals(updates);
+ store.updateNodeInternals(updates);
});
onDestroy(() => {
@@ -52,41 +46,46 @@
- {#each $visibleNodes as node (node.id)}
+ {#each $nodes as node (node.id)}
+ {@const internalNode = store.nodeLookup.get(node.id)}
e.id);
selectedNodes = getNodesInside(
- $nodeLookup,
+ store.nodeLookup,
nextUserSelectRect,
[$viewport.x, $viewport.y, $viewport.zoom],
- $selectionMode === SelectionMode.Partial,
+ store.selectionMode === SelectionMode.Partial,
true
);
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
@@ -163,8 +153,8 @@
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
}
- selectionRectMode.set('user');
- selectionRect.set(nextUserSelectRect);
+ store.selectionRectMode = 'user';
+ store.selectionRect = nextUserSelectRect;
}
function onPointerUp(event: PointerEvent) {
@@ -176,18 +166,18 @@
// We only want to trigger click functions when in selection mode if
// the user did not move the mouse.
- if (!isSelecting && $selectionRectMode === 'user' && event.target === container) {
+ if (!isSelecting && store.selectionRectMode === 'user' && event.target === container) {
onClick?.(event);
}
- selectionRect.set(null);
+ store.selectionRect = null;
if (selectedNodes.length > 0) {
- $selectionRectMode = 'nodes';
+ store.selectionRectMode = 'nodes';
}
// If the user kept holding the selectionKey during the selection,
// we need to reset the selectionInProgress, so the next click event is not prevented
- if ($selectionKeyPressed) {
+ if (store.selectionKeyPressed) {
selectionInProgress = false;
}
@@ -210,7 +200,7 @@
bind:this={container}
class="svelte-flow__pane"
class:draggable={panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0))}
- class:dragging={$dragging}
+ class:dragging={store.dragging}
class:selection={isSelecting}
onclick={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
onpointerdown={hasActiveSelection ? onPointerDown : undefined}
diff --git a/packages/svelte/src/lib/container/Pane/types.ts b/packages/svelte/src/lib/container/Pane/types.ts
index a6312a51..e4bcf2cc 100644
--- a/packages/svelte/src/lib/container/Pane/types.ts
+++ b/packages/svelte/src/lib/container/Pane/types.ts
@@ -1,11 +1,10 @@
import type { Snippet } from 'svelte';
-import type { Writable } from 'svelte/store';
-import type { Edge, Node, PaneEvents } from '$lib/types';
+import type { PaneEvents } from '$lib/types';
+import type { SvelteFlowStore } from '$lib/store/types';
export type PaneProps = {
+ store: SvelteFlowStore;
panOnDrag?: boolean | number[];
selectionOnDrag?: boolean;
- nodes: Writable;
- edges: Writable;
children: Snippet;
} & PaneEvents;
diff --git a/packages/svelte/src/lib/container/Panel/Panel.svelte b/packages/svelte/src/lib/container/Panel/Panel.svelte
index 43bdb9ef..83df374c 100644
--- a/packages/svelte/src/lib/container/Panel/Panel.svelte
+++ b/packages/svelte/src/lib/container/Panel/Panel.svelte
@@ -11,7 +11,7 @@
...restProps
}: PanelProps = $props();
- const { selectionRectMode } = useStore();
+ const store = useStore();
let positionClasses = $derived(`${position}`.split('-'));
@@ -19,7 +19,7 @@
{@render children?.()}
diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
index 516e69cd..4b815e69 100644
--- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
+++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
@@ -1,8 +1,8 @@
(obj: T) => Object.keys(obj) as Array;
-
-type UnwrapWritable = T extends Writable ? U : T;
-
-// @todo there must be a better way to define the types here..
-export type UpdatableStoreProps = {
- flowId?: UnwrapWritable;
- connectionLineType?: UnwrapWritable;
- connectionRadius?: UnwrapWritable;
- selectionMode?: UnwrapWritable;
- snapGrid?: UnwrapWritable;
- defaultMarkerColor?: UnwrapWritable;
- nodesDraggable?: UnwrapWritable;
- nodesConnectable?: UnwrapWritable;
- elementsSelectable?: UnwrapWritable;
- onlyRenderVisibleElements?: UnwrapWritable;
- isValidConnection?: UnwrapWritable;
- autoPanOnConnect?: UnwrapWritable;
- autoPanOnNodeDrag?: UnwrapWritable;
- connectionMode?: UnwrapWritable;
- onerror?: UnwrapWritable;
- ondelete?: UnwrapWritable;
- onedgecreate?: UnwrapWritable;
- nodeDragThreshold?: UnwrapWritable;
- onconnect?: UnwrapWritable;
- onconnectstart?: UnwrapWritable;
- onconnectend?: UnwrapWritable;
- onbeforedelete?: UnwrapWritable;
- nodeOrigin?: UnwrapWritable;
-};
-
-export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) {
- getKeys(keys).forEach((prop) => {
- const update = keys[prop];
- if (update !== undefined) {
- // @ts-expect-error @todo: how to fix this TS error?
- store[prop].set(update);
- }
- });
-}
diff --git a/packages/svelte/src/lib/container/Viewport/Viewport.svelte b/packages/svelte/src/lib/container/Viewport/Viewport.svelte
index 4b6769db..f994db1d 100644
--- a/packages/svelte/src/lib/container/Viewport/Viewport.svelte
+++ b/packages/svelte/src/lib/container/Viewport/Viewport.svelte
@@ -1,15 +1,13 @@
{@render children()}
diff --git a/packages/svelte/src/lib/container/Zoom/Zoom.svelte b/packages/svelte/src/lib/container/Zoom/Zoom.svelte
index e60cb5f5..1f57042a 100644
--- a/packages/svelte/src/lib/container/Zoom/Zoom.svelte
+++ b/packages/svelte/src/lib/container/Zoom/Zoom.svelte
@@ -1,12 +1,13 @@
{
+ store.dragging = dragging;
+ },
+ setPanZoomInstance: (instance: PanZoomInstance) => {
+ store.panZoom = instance;
+ },
onPanZoomStart: onMoveStart,
onPanZoom: onMove,
onPanZoomEnd: onMoveEnd,
@@ -66,13 +57,13 @@
panOnDrag: panOnDragActive,
panOnScrollSpeed: 0.5,
panOnScrollMode: panOnScrollMode || PanOnScrollMode.Free,
- zoomActivationKeyPressed: $zoomActivationKeyPressed,
+ zoomActivationKeyPressed: store.zoomActivationKeyPressed,
preventScrolling: typeof preventScrolling === 'boolean' ? preventScrolling : true,
noPanClassName: 'nopan',
noWheelClassName: 'nowheel',
- userSelectionActive: !!$selectionRect,
- translateExtent: $translateExtent,
- lib: $lib,
+ userSelectionActive: !!store.selectionRect,
+ translateExtent: store.translateExtent,
+ lib: 'svelte',
paneClickDistance,
onTransformChange
}}
diff --git a/packages/svelte/src/lib/container/Zoom/types.ts b/packages/svelte/src/lib/container/Zoom/types.ts
index 02b2bd0a..5f95920c 100644
--- a/packages/svelte/src/lib/container/Zoom/types.ts
+++ b/packages/svelte/src/lib/container/Zoom/types.ts
@@ -1,7 +1,9 @@
+import type { SvelteFlowStore } from '$lib/store/types';
import type { OnMoveStart, OnMove, OnMoveEnd, PanOnScrollMode, Viewport } from '@xyflow/system';
import type { Snippet } from 'svelte';
export type ZoomProps = {
+ store: SvelteFlowStore;
initialViewport?: Viewport;
panOnScrollMode: PanOnScrollMode;
onMove?: OnMove;
diff --git a/packages/svelte/src/lib/hooks/derivedWarning.svelte.ts b/packages/svelte/src/lib/hooks/derivedWarning.svelte.ts
new file mode 100644
index 00000000..3a1a4b62
--- /dev/null
+++ b/packages/svelte/src/lib/hooks/derivedWarning.svelte.ts
@@ -0,0 +1,19 @@
+import { key } from '$lib/store';
+import type { StoreContext } from '$lib/store/types';
+import { getContext } from 'svelte';
+
+export function derivedWarning(functionName: string) {
+ const storeContext = getContext
(key);
+
+ if (!storeContext) {
+ throw new Error(
+ `In order to use ${functionName}() you need to wrap your component in a `
+ );
+ }
+
+ if (storeContext.provider && !$effect.tracking()) {
+ console.warn(
+ `Use $derived(${functionName}()), when not calling inside a child of the component.`
+ );
+ }
+}
diff --git a/packages/svelte/src/lib/hooks/index.ts b/packages/svelte/src/lib/hooks/index.ts
new file mode 100644
index 00000000..84f29965
--- /dev/null
+++ b/packages/svelte/src/lib/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useSvelteFlow.svelte';
diff --git a/packages/svelte/src/lib/hooks/useStore.ts b/packages/svelte/src/lib/hooks/useStore.ts
new file mode 100644
index 00000000..f91c9628
--- /dev/null
+++ b/packages/svelte/src/lib/hooks/useStore.ts
@@ -0,0 +1,21 @@
+import { getContext } from 'svelte';
+import type { StoreContext, SvelteFlowStore } from '../store/types';
+
+import { key } from '../store';
+import { derivedWarning } from './derivedWarning.svelte';
+
+export function useStore(): SvelteFlowStore {
+ const storeContext = getContext(key);
+
+ if (!storeContext) {
+ throw new Error(
+ 'In order to use useStore you need to wrap your component in a '
+ );
+ }
+
+ if (process.env.NODE_ENV === 'development') {
+ derivedWarning('useStore');
+ }
+
+ return storeContext.getStore();
+}
diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts
similarity index 82%
rename from packages/svelte/src/lib/hooks/useSvelteFlow.ts
rename to packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts
index 6ee27fb2..43e60164 100644
--- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts
+++ b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts
@@ -22,7 +22,8 @@ import {
import { useStore } from '$lib/store';
import type { Edge, FitViewOptions, InternalNode, Node } from '$lib/types';
-import { isNode } from '$lib/utils';
+import { isEdge, isNode } from '$lib/utils';
+import { derivedWarning } from './derivedWarning.svelte';
/**
* Hook for accessing the ReactFlow instance.
@@ -232,6 +233,21 @@ export function useSvelteFlow(): {
*
* @returns the nodes, edges and the viewport as a JSON object
*/
+ /**
+ * Updates an edge.
+ *
+ * @param id - id of the edge to update
+ * @param edgeUpdate - the edge update as an object or a function that receives the current edge and returns the edge update
+ * @param options.replace - if true, the edge is replaced with the edge update, otherwise the changes get merged
+ *
+ * @example
+ * updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }));
+ */
+ updateEdge: (
+ id: string,
+ edgeUpdate: Partial | ((edge: Edge) => Partial),
+ options?: { replace: boolean }
+ ) => void;
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
/**
* Returns the bounds of the given nodes or node ids.
@@ -258,37 +274,22 @@ export function useSvelteFlow(): {
id?: string | null;
}) => HandleConnection[];
} {
- const {
- zoomIn,
- zoomOut,
- fitView,
- onbeforedelete,
- snapGrid,
- viewport,
- width,
- height,
- minZoom,
- maxZoom,
- panZoom,
- nodes,
- edges,
- domNode,
- nodeLookup,
- nodeOrigin,
- edgeLookup,
- connectionLookup
- } = useStore();
+ if (process.env.NODE_ENV === 'development') {
+ derivedWarning('useSvelteFlow');
+ }
+
+ const store = useStore();
+ const { viewport, nodes, edges } = store;
const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => {
- const $nodeLookup = get(nodeLookup);
- const nodeToUse = isNode(node) ? node : $nodeLookup.get(node.id)!;
+ const nodeToUse = isNode(node) ? node : store.nodeLookup.get(node.id)!;
const position = nodeToUse.parentId
? evaluateAbsolutePosition(
nodeToUse.position,
nodeToUse.measured,
nodeToUse.parentId,
- $nodeLookup,
- get(nodeOrigin)
+ store.nodeLookup,
+ store.nodeOrigin
)
: nodeToUse.position;
@@ -308,7 +309,7 @@ export function useSvelteFlow(): {
nodeUpdate: Partial | ((node: Node) => Partial),
options: { replace: boolean } = { replace: false }
) => {
- const node = get(nodeLookup).get(id)?.internals.userNode;
+ const node = store.nodeLookup.get(id)?.internals.userNode;
if (!node) {
return;
@@ -328,36 +329,64 @@ export function useSvelteFlow(): {
);
} else {
Object.assign(node, nextNode);
- nodes.update((nds) => nds);
+ nodes.set(get(nodes));
}
};
- const getInternalNode = (id: string) => get(nodeLookup).get(id);
+ const updateEdge = (
+ id: string,
+ edgeUpdate: Partial | ((edge: Edge) => Partial),
+ options: { replace: boolean } = { replace: false }
+ ) => {
+ const edge = store.edgeLookup.get(id);
+
+ if (!edge) {
+ return;
+ }
+
+ const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge as Edge) : edgeUpdate;
+
+ if (options.replace) {
+ edges.update((edgs) =>
+ edgs.map((edge) => {
+ if (edge.id === id) {
+ return isEdge(nextEdge) ? nextEdge : { ...edge, ...nextEdge };
+ }
+
+ return edge;
+ })
+ );
+ } else {
+ Object.assign(edge, nextEdge);
+ edges.set(get(edges));
+ }
+ };
+
+ const getInternalNode = (id: string) => store.nodeLookup.get(id);
return {
- zoomIn,
- zoomOut,
+ zoomIn: store.zoomIn,
+ zoomOut: store.zoomOut,
getInternalNode,
getNode: (id) => getInternalNode(id)?.internals.userNode,
- getNodes: (ids) => (ids === undefined ? get(nodes) : getElements(get(nodeLookup), ids)),
- getEdge: (id) => get(edgeLookup).get(id),
- getEdges: (ids) => (ids === undefined ? get(edges) : getElements(get(edgeLookup), ids)),
+ getNodes: (ids) => (ids === undefined ? get(nodes) : getElements(store.nodeLookup, ids)),
+ getEdge: (id) => store.edgeLookup.get(id),
+ getEdges: (ids) => (ids === undefined ? get(edges) : getElements(store.edgeLookup, ids)),
setZoom: (zoomLevel, options) => {
- const currentPanZoom = get(panZoom);
- return currentPanZoom
- ? currentPanZoom.scaleTo(zoomLevel, { duration: options?.duration })
+ const panZoom = store.panZoom;
+ return panZoom
+ ? panZoom.scaleTo(zoomLevel, { duration: options?.duration })
: Promise.resolve(false);
},
getZoom: () => get(viewport).zoom,
setViewport: async (nextViewport, options) => {
const currentViewport = get(viewport);
- const currentPanZoom = get(panZoom);
- if (!currentPanZoom) {
+ if (!store.panZoom) {
return Promise.resolve(false);
}
- await currentPanZoom.setViewport(
+ await store.panZoom.setViewport(
{
x: nextViewport.x ?? currentViewport.x,
y: nextViewport.y ?? currentViewport.y,
@@ -370,8 +399,8 @@ export function useSvelteFlow(): {
},
getViewport: () => get(viewport),
setCenter: async (x, y, options) => {
- const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
- const currentPanZoom = get(panZoom);
+ const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom;
+ const currentPanZoom = store.panZoom;
if (!currentPanZoom) {
return Promise.resolve(false);
@@ -379,8 +408,8 @@ export function useSvelteFlow(): {
await currentPanZoom.setViewport(
{
- x: get(width) / 2 - x * nextZoom,
- y: get(height) / 2 - y * nextZoom,
+ x: store.width / 2 - x * nextZoom,
+ y: store.height / 2 - y * nextZoom,
zoom: nextZoom
},
{ duration: options?.duration }
@@ -388,24 +417,22 @@ export function useSvelteFlow(): {
return Promise.resolve(true);
},
- fitView,
+ fitView: store.fitView,
fitBounds: async (bounds: Rect, options?: FitBoundsOptions) => {
- const currentPanZoom = get(panZoom);
-
- if (!currentPanZoom) {
+ if (!store.panZoom) {
return Promise.resolve(false);
}
const viewport = getViewportForBounds(
bounds,
- get(width),
- get(height),
- get(minZoom),
- get(maxZoom),
+ store.width,
+ store.height,
+ store.minZoom,
+ store.maxZoom,
options?.padding ?? 0.1
);
- await currentPanZoom.setViewport(viewport, { duration: options?.duration });
+ await store.panZoom.setViewport(viewport, { duration: options?.duration });
return Promise.resolve(true);
},
@@ -422,7 +449,7 @@ export function useSvelteFlow(): {
}
return (nodesToIntersect || get(nodes)).filter((n) => {
- const internalNode = get(nodeLookup).get(n.id);
+ const internalNode = store.nodeLookup.get(n.id);
if (!internalNode || (!isRect && n.id === nodeOrRect.id)) {
return false;
}
@@ -457,7 +484,7 @@ export function useSvelteFlow(): {
edgesToRemove,
nodes: get(nodes),
edges: get(edges),
- onBeforeDelete: get(onbeforedelete)
+ onBeforeDelete: store.onbeforedelete
});
if (matchingNodes) {
@@ -481,15 +508,13 @@ export function useSvelteFlow(): {
position: XYPosition,
options: { snapToGrid: boolean } = { snapToGrid: true }
) => {
- const _domNode = get(domNode);
-
- if (!_domNode) {
+ if (!store.domNode) {
return position;
}
- const _snapGrid = options.snapToGrid ? get(snapGrid) : false;
+ const _snapGrid = options.snapToGrid ? store.snapGrid : false;
const { x, y, zoom } = get(viewport);
- const { x: domX, y: domY } = _domNode.getBoundingClientRect();
+ const { x: domX, y: domY } = store.domNode.getBoundingClientRect();
const correctedPosition = {
x: position.x - domX,
y: position.y - domY
@@ -508,14 +533,12 @@ export function useSvelteFlow(): {
* @returns
*/
flowToScreenPosition: (position: XYPosition) => {
- const _domNode = get(domNode);
-
- if (!_domNode) {
+ if (!store.domNode) {
return position;
}
const { x, y, zoom } = get(viewport);
- const { x: domX, y: domY } = _domNode.getBoundingClientRect();
+ const { x: domX, y: domY } = store.domNode.getBoundingClientRect();
const rendererPosition = rendererPointToPoint(position, [x, y, zoom]);
return {
@@ -539,7 +562,7 @@ export function useSvelteFlow(): {
},
updateNode,
updateNodeData: (id, dataUpdate, options) => {
- const node = get(nodeLookup).get(id)?.internals.userNode;
+ const node = store.nodeLookup.get(id)?.internals.userNode;
if (!node) {
return;
@@ -551,18 +574,12 @@ export function useSvelteFlow(): {
nodes.update((nds) => nds);
},
+ updateEdge,
getNodesBounds: (nodes) => {
- const _nodeLookup = get(nodeLookup);
- const _nodeOrigin = get(nodeOrigin);
-
- return getNodesBounds(nodes, { nodeLookup: _nodeLookup, nodeOrigin: _nodeOrigin });
+ return getNodesBounds(nodes, { nodeLookup: store.nodeLookup, nodeOrigin: store.nodeOrigin });
},
getHandleConnections: ({ type, id, nodeId }) =>
- Array.from(
- get(connectionLookup)
- .get(`${nodeId}-${type}-${id ?? null}`)
- ?.values() ?? []
- ),
+ Array.from(store.connectionLookup.get(`${nodeId}-${type}-${id ?? null}`)?.values() ?? []),
viewport
};
}
diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts
index 112af9fa..4920729f 100644
--- a/packages/svelte/src/lib/index.ts
+++ b/packages/svelte/src/lib/index.ts
@@ -26,7 +26,7 @@ export { useStore } from '$lib/store';
export * from '$lib/utils';
//hooks
-export * from '$lib/hooks/useSvelteFlow';
+export * from '$lib/hooks/useSvelteFlow.svelte';
export * from '$lib/hooks/useUpdateNodeInternals';
export * from '$lib/hooks/useConnection';
export * from '$lib/hooks/useNodesEdges';
diff --git a/packages/svelte/src/lib/plugins/Background/Background.svelte b/packages/svelte/src/lib/plugins/Background/Background.svelte
index a9f49b63..13e46d2a 100644
--- a/packages/svelte/src/lib/plugins/Background/Background.svelte
+++ b/packages/svelte/src/lib/plugins/Background/Background.svelte
@@ -27,13 +27,17 @@
class: className
}: BackgroundProps = $props();
- const { viewport, flowId } = useStore();
+ const store = useStore();
+ const { viewport } = store;
+
+ $inspect($viewport);
+
const patternSize = size ?? defaultSize[variant!];
const isDots = variant === BackgroundVariant.Dots;
const isCross = variant === BackgroundVariant.Cross;
const gapXY: number[] = Array.isArray(gap!) ? gap! : [gap!, gap!];
- let patternId = $derived(`background-pattern-${$flowId}-${id ?? ''}`);
+ let patternId = $derived(`background-pattern-${store.flowId}-${id ?? ''}`);
let scaledGap = $derived([gapXY[0] * $viewport.zoom || 1, gapXY[1] * $viewport.zoom || 1]);
let scaledSize = $derived(patternSize * $viewport.zoom);
let patternDimensions = $derived(
diff --git a/packages/svelte/src/lib/plugins/Controls/Controls.svelte b/packages/svelte/src/lib/plugins/Controls/Controls.svelte
index d164852b..c86c8d5e 100644
--- a/packages/svelte/src/lib/plugins/Controls/Controls.svelte
+++ b/packages/svelte/src/lib/plugins/Controls/Controls.svelte
@@ -32,17 +32,8 @@
after
}: ControlsProps = $props();
- const {
- viewport,
- minZoom,
- maxZoom,
- nodesDraggable,
- nodesConnectable,
- elementsSelectable,
- zoomIn,
- zoomOut,
- fitView
- } = useStore();
+ const store = useStore();
+ const { viewport } = store;
const buttonProps = {
bgColor: buttonBgColor,
@@ -52,29 +43,31 @@
borderColor: buttonBorderColor
};
- let isInteractive = $state($nodesDraggable || $nodesConnectable || $elementsSelectable);
- let minZoomReached = $derived($viewport.zoom <= $minZoom);
- let maxZoomReached = $derived($viewport.zoom >= $maxZoom);
+ let isInteractive = $state(
+ store.nodesDraggable || store.nodesConnectable || store.elementsSelectable
+ );
+ let minZoomReached = $derived($viewport.zoom <= store.minZoom);
+ let maxZoomReached = $derived($viewport.zoom >= store.maxZoom);
let orientationClass = $derived(orientation === 'horizontal' ? 'horizontal' : 'vertical');
const onZoomInHandler = () => {
- zoomIn();
+ store.zoomIn();
};
const onZoomOutHandler = () => {
- zoomOut();
+ store.zoomOut();
};
const onFitViewHandler = () => {
- fitView(fitViewOptions);
+ store.fitView(fitViewOptions);
};
const onToggleInteractivity = () => {
isInteractive = !isInteractive;
- nodesDraggable.set(isInteractive);
- nodesConnectable.set(isInteractive);
- elementsSelectable.set(isInteractive);
+ store.nodesDraggable = isInteractive;
+ store.nodesConnectable = isInteractive;
+ store.elementsSelectable = isInteractive;
};
diff --git a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte
index 84ecccf6..97412edb 100644
--- a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte
+++ b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte
@@ -42,16 +42,10 @@
class: className
}: MiniMapProps = $props();
- const {
- nodes,
- nodeLookup,
- viewport,
- width: containerWidth,
- height: containerHeight,
- flowId,
- panZoom,
- translateExtent
- } = useStore();
+ let store = useStore();
+ const { viewport } = store;
+
+ let nodes = $derived(store.nodes);
const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor);
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
@@ -59,17 +53,20 @@
const shapeRendering =
// @ts-expect-error - TS doesn't know about chrome
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
- const labelledBy = `svelte-flow__minimap-desc-${$flowId}`;
+
+ let labelledBy = $derived(`svelte-flow__minimap-desc-${store.flowId}`);
// TODO: simplify this
let viewBB = $derived({
x: -$viewport.x / $viewport.zoom,
y: -$viewport.y / $viewport.zoom,
- width: $containerWidth / $viewport.zoom,
- height: $containerHeight / $viewport.zoom
+ width: store.width / $viewport.zoom,
+ height: store.height / $viewport.zoom
});
let boundingRect = $derived(
- $nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds($nodeLookup), viewBB) : viewBB
+ store.nodeLookup.size > 0
+ ? getBoundsOfRects(getInternalNodesBounds(store.nodeLookup), viewBB)
+ : viewBB
);
let scaledWidth = $derived(boundingRect.width / width);
let scaledHeight = $derived(boundingRect.height / height);
@@ -91,7 +88,7 @@
class={cc(['svelte-flow__minimap', className])}
data-testid="svelte-flow__minimap"
>
- {#if $panZoom}
+ {#if store.panZoom}