use generics across the library

This commit is contained in:
peterkogo
2025-04-17 15:36:39 +02:00
parent cf1e0bee81
commit 353c661dd8
27 changed files with 198 additions and 152 deletions
+12 -8
View File
@@ -1,9 +1,10 @@
import { XYDrag, type OnDrag } from '@xyflow/system'; import { XYDrag, type NodeBase, type OnDrag, type XYDragParams } from '@xyflow/system';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { Node, Edge, NodeTargetEventWithPointer } from '$lib/types';
export type UseDragParams = { export type UseDragParams<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
disabled?: boolean; disabled?: boolean;
noDragClass?: string; noDragClass?: string;
handleSelector?: string; handleSelector?: string;
@@ -16,7 +17,10 @@ export type UseDragParams = {
onNodeMouseDown?: (id: string) => void; onNodeMouseDown?: (id: string) => void;
}; };
export default function drag(domNode: Element, params: UseDragParams) { export default function drag<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
domNode: Element,
params: UseDragParams<NodeType, EdgeType>
) {
const { store, onDrag, onDragStart, onDragStop, onNodeMouseDown } = params; const { store, onDrag, onDragStart, onDragStop, onNodeMouseDown } = params;
const dragInstance = XYDrag({ const dragInstance = XYDrag({
onDrag, onDrag,
@@ -27,7 +31,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
const { snapGrid, viewport } = store; const { snapGrid, viewport } = store;
return { return {
nodes: store.nodes, nodes: store.nodes satisfies NodeBase[],
nodeLookup: store.nodeLookup, nodeLookup: store.nodeLookup,
edges: store.edges, edges: store.edges,
nodeExtent: store.nodeExtent, nodeExtent: store.nodeExtent,
@@ -46,9 +50,9 @@ export default function drag(domNode: Element, params: UseDragParams) {
panBy: store.panBy panBy: store.panBy
}; };
} }
}); } as XYDragParams<NodeTargetEventWithPointer<MouseEvent | TouchEvent, NodeType>>);
function updateDrag(domNode: Element, params: UseDragParams) { function updateDrag(domNode: Element, params: UseDragParams<NodeType, EdgeType>) {
if (params.disabled) { if (params.disabled) {
dragInstance.destroy(); dragInstance.destroy();
return; return;
@@ -67,7 +71,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
updateDrag(domNode, params); updateDrag(domNode, params);
return { return {
update(params: UseDragParams) { update(params: UseDragParams<NodeType, EdgeType>) {
updateDrag(domNode, params); updateDrag(domNode, params);
}, },
destroy() { destroy() {
@@ -1,4 +1,4 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import { import {
ConnectionLineType, ConnectionLineType,
@@ -9,6 +9,7 @@
} from '@xyflow/system'; } from '@xyflow/system';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { Node, Edge } from '$lib/types';
let { let {
store = $bindable(), store = $bindable(),
@@ -17,7 +18,7 @@
style, style,
LineComponent LineComponent
}: { }: {
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
type: ConnectionLineType; type: ConnectionLineType;
containerStyle?: string; containerStyle?: string;
style?: string; style?: string;
@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { setContext } from 'svelte'; import { setContext } from 'svelte';
import { elementSelectionKeys, getMarkerId } from '@xyflow/system'; import { elementSelectionKeys, getMarkerId } from '@xyflow/system';
import { BezierEdgeInternal } from '$lib/components/edges'; import { BezierEdgeInternal } from '$lib/components/edges';
import type { EdgeLayouted, Edge, EdgeEvents } from '$lib/types'; import type { Node, EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
const { const {
@@ -15,7 +15,10 @@
onedgecontextmenu, onedgecontextmenu,
onedgepointerenter, onedgepointerenter,
onedgepointerleave onedgepointerleave
}: { store: SvelteFlowStore; edge: EdgeLayouted } & EdgeEvents = $props(); }: {
store: SvelteFlowStore<NodeType, EdgeType>;
edge: EdgeLayouted<EdgeType>;
} & EdgeEvents<EdgeType> = $props();
let { let {
source, source,
@@ -76,7 +79,7 @@
function onmouseevent<T = MouseEvent>( function onmouseevent<T = MouseEvent>(
event: T, event: T,
callback: ({ edge, event }: { edge: Edge; event: T }) => void callback: ({ edge, event }: { edge: EdgeType; event: T }) => void
) { ) {
const edge = store.edgeLookup.get(id); const edge = store.edgeLookup.get(id);
@@ -1,4 +1,4 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { import {
shortcut, shortcut,
type ShortcutEventDetail, type ShortcutEventDetail,
@@ -7,7 +7,7 @@
import { isInputDOMNode, isMacOs } from '@xyflow/system'; import { isInputDOMNode, isMacOs } from '@xyflow/system';
import type { KeyHandlerProps } from './types'; import type { KeyHandlerProps } from './types';
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types'; import type { Node, Edge, KeyDefinition, KeyDefinitionObject } from '$lib/types';
import { useSvelteFlow } from '$lib/hooks/useSvelteFlow.svelte'; import { useSvelteFlow } from '$lib/hooks/useSvelteFlow.svelte';
let { let {
@@ -17,9 +17,9 @@
deleteKey = 'Backspace', deleteKey = 'Backspace',
panActivationKey = ' ', panActivationKey = ' ',
zoomActivationKey = isMacOs() ? 'Meta' : 'Control' zoomActivationKey = isMacOs() ? 'Meta' : 'Control'
}: KeyHandlerProps = $props(); }: KeyHandlerProps<NodeType, EdgeType> = $props();
let { deleteElements } = useSvelteFlow(); let { deleteElements } = useSvelteFlow<NodeType, EdgeType>();
function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject { function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject {
return key !== null && typeof key === 'object'; return key !== null && typeof key === 'object';
@@ -1,8 +1,8 @@
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { KeyDefinition } from '$lib/types'; import type { Node, Edge, KeyDefinition } from '$lib/types';
export type KeyHandlerProps = { export type KeyHandlerProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
selectionKey?: KeyDefinition | KeyDefinition[] | null; selectionKey?: KeyDefinition | KeyDefinition[] | null;
multiSelectionKey?: KeyDefinition | KeyDefinition[] | null; multiSelectionKey?: KeyDefinition | KeyDefinition[] | null;
deleteKey?: KeyDefinition | KeyDefinition[] | null; deleteKey?: KeyDefinition | KeyDefinition[] | null;
@@ -1,4 +1,4 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { arrowKeyDiffs, getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system'; import { arrowKeyDiffs, getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system';
import { Selection } from '$lib/components/Selection'; import { Selection } from '$lib/components/Selection';
@@ -6,6 +6,7 @@
import type { NodeSelectionProps } from './types'; import type { NodeSelectionProps } from './types';
import { toPxString } from '$lib/utils'; import { toPxString } from '$lib/utils';
import type { Node, Edge } from '$lib/types';
let { let {
store = $bindable(), store = $bindable(),
@@ -14,7 +15,7 @@
onnodedragstop, onnodedragstop,
onselectionclick, onselectionclick,
onselectioncontextmenu onselectioncontextmenu
}: NodeSelectionProps = $props(); }: NodeSelectionProps<NodeType, EdgeType> = $props();
let ref = $state<HTMLDivElement>(); let ref = $state<HTMLDivElement>();
@@ -63,13 +64,13 @@
disabled: false, disabled: false,
store, store,
onDrag: (event, _, __, nodes) => { onDrag: (event, _, __, nodes) => {
onnodedrag?.({ event, targetNode: null, nodes }); onnodedrag?.({ event, targetNode: null, nodes: nodes as NodeType[] });
}, },
onDragStart: (event, _, __, nodes) => { onDragStart: (event, _, __, nodes) => {
onnodedragstart?.({ event, targetNode: null, nodes }); onnodedragstart?.({ event, targetNode: null, nodes: nodes as NodeType[] });
}, },
onDragStop: (event, _, __, nodes) => { onDragStop: (event, _, __, nodes) => {
onnodedragstop?.({ event, targetNode: null, nodes }); onnodedragstop?.({ event, targetNode: null, nodes: nodes as NodeType[] });
} }
}} }}
{oncontextmenu} {oncontextmenu}
@@ -1,5 +1,7 @@
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { NodeEvents, NodeSelectionEvents } from '$lib/types'; import type { Node, Edge, NodeEvents, NodeSelectionEvents } from '$lib/types';
export type NodeSelectionProps = { store: SvelteFlowStore } & NodeSelectionEvents & export type NodeSelectionProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
Pick<NodeEvents, 'onnodedrag' | 'onnodedragstart' | 'onnodedragstop'>; store: SvelteFlowStore<NodeType, EdgeType>;
} & NodeSelectionEvents<NodeType> &
Pick<NodeEvents<NodeType>, 'onnodedrag' | 'onnodedragstart' | 'onnodedragstop'>;
@@ -1,4 +1,4 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { setContext, onDestroy } from 'svelte'; import { setContext, onDestroy } from 'svelte';
import { import {
arrowKeyDiffs, arrowKeyDiffs,
@@ -13,7 +13,7 @@
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import type { ConnectableContext, NodeWrapperProps } from './types'; import type { ConnectableContext, NodeWrapperProps } from './types';
import type { NodeEvents } from '$lib/types'; import type { Node, Edge, NodeEvents } from '$lib/types';
import { toPxString } from '$lib/utils'; import { toPxString } from '$lib/utils';
let { let {
@@ -29,7 +29,7 @@
onnodepointerleave, onnodepointerleave,
onnodepointermove, onnodepointermove,
onnodecontextmenu onnodecontextmenu
}: NodeWrapperProps & NodeEvents = $props(); }: NodeWrapperProps<NodeType, EdgeType> & NodeEvents<NodeType> = $props();
let { let {
data = {}, data = {},
@@ -55,7 +55,8 @@
dragHandle, dragHandle,
internals: { internals: {
z: zIndex = 0, z: zIndex = 0,
positionAbsolute: { x: positionX, y: positionY } positionAbsolute: { x: positionX, y: positionY },
userNode
} }
} = $derived(node); } = $derived(node);
@@ -162,7 +163,7 @@
store.handleNodeSelection(id); store.handleNodeSelection(id);
} }
onnodeclick?.({ node, event }); onnodeclick?.({ node: userNode, event });
} }
function onKeyDown(event: KeyboardEvent) { function onKeyDown(event: KeyboardEvent) {
@@ -205,13 +206,17 @@
nodeClickDistance, nodeClickDistance,
onNodeMouseDown: store.handleNodeSelection, onNodeMouseDown: store.handleNodeSelection,
onDrag: (event, _, targetNode, nodes) => { onDrag: (event, _, targetNode, nodes) => {
onnodedrag?.({ event, targetNode, nodes }); onnodedrag?.({ event, targetNode: targetNode as NodeType, nodes: nodes as NodeType[] });
}, },
onDragStart: (event, _, targetNode, nodes) => { onDragStart: (event, _, targetNode, nodes) => {
onnodedragstart?.({ event, targetNode, nodes }); onnodedragstart?.({
event,
targetNode: targetNode as NodeType,
nodes: nodes as NodeType[]
});
}, },
onDragStop: (event, _, targetNode, nodes) => { onDragStop: (event, _, targetNode, nodes) => {
onnodedragstop?.({ event, targetNode, nodes }); onnodedragstop?.({ event, targetNode: targetNode as NodeType, nodes: nodes as NodeType[] });
}, },
store store
}} }}
@@ -230,10 +235,18 @@
style:visibility={initialized ? 'visible' : 'hidden'} style:visibility={initialized ? 'visible' : 'hidden'}
style="{style};width:{inlineDimensions.width};height:{inlineDimensions.height}" style="{style};width:{inlineDimensions.width};height:{inlineDimensions.height}"
onclick={onSelectNodeHandler} onclick={onSelectNodeHandler}
onpointerenter={onnodepointerenter ? (event) => onnodepointerenter({ node, event }) : undefined} onpointerenter={onnodepointerenter
onpointerleave={onnodepointerleave ? (event) => onnodepointerleave({ node, event }) : undefined} ? (event) => onnodepointerenter({ node: userNode, event })
onpointermove={onnodepointermove ? (event) => onnodepointermove({ node, event }) : undefined} : undefined}
oncontextmenu={onnodecontextmenu ? (event) => onnodecontextmenu({ node, event }) : undefined} onpointerleave={onnodepointerleave
? (event) => onnodepointerleave({ node: userNode, event })
: undefined}
onpointermove={onnodepointermove
? (event) => onnodepointermove({ node: userNode, event })
: undefined}
oncontextmenu={onnodecontextmenu
? (event) => onnodecontextmenu({ node: userNode, event })
: undefined}
onkeydown={focusable ? onKeyDown : undefined} onkeydown={focusable ? onKeyDown : undefined}
tabIndex={focusable ? 0 : undefined} tabIndex={focusable ? 0 : undefined}
role={focusable ? 'button' : undefined} role={focusable ? 'button' : undefined}
@@ -1,13 +1,13 @@
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { InternalNode } from '$lib/types'; import type { Node, Edge, InternalNode } from '$lib/types';
export type ConnectableContext = { export type ConnectableContext = {
value: boolean; value: boolean;
}; };
export type NodeWrapperProps = { export type NodeWrapperProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
node: InternalNode; node: InternalNode<NodeType>;
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
nodeClickDistance?: number; nodeClickDistance?: number;
resizeObserver?: ResizeObserver | null; resizeObserver?: ResizeObserver | null;
}; };
@@ -1,14 +1,15 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { onDestroy, setContext } from 'svelte'; import { onDestroy, setContext } from 'svelte';
import { createStore, key } from '$lib/store'; import { createStore, key } from '$lib/store';
import type { SvelteFlowProviderProps } from './types'; import type { SvelteFlowProviderProps } from './types';
import type { ProviderContext, SvelteFlowStore } from '$lib/store/types'; import type { ProviderContext, SvelteFlowStore } from '$lib/store/types';
import type { Node, Edge } from '$lib/types';
let { children }: SvelteFlowProviderProps = $props(); let { children }: SvelteFlowProviderProps = $props();
let store = $state.raw( let store = $state.raw(
createStore({ createStore<NodeType, EdgeType>({
props: {}, props: {},
nodes: [], nodes: [],
edges: [] edges: []
@@ -20,10 +21,10 @@
getStore() { getStore() {
return store; return store;
}, },
setStore: (newStore: SvelteFlowStore) => { setStore: (newStore: SvelteFlowStore<NodeType, EdgeType>) => {
store = newStore; store = newStore;
} }
} satisfies ProviderContext); } satisfies ProviderContext<NodeType, EdgeType>);
onDestroy(() => { onDestroy(() => {
store.reset(); store.reset();
@@ -1,13 +1,5 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { NodeOrigin } from '@xyflow/system';
import type { Edge, Node } from '$lib/types';
export type SvelteFlowProviderProps = { export type SvelteFlowProviderProps = {
initialNodes?: Node[];
initialEdges?: Edge[];
initialWidth?: number;
initialHeight?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
children?: Snippet; children?: Snippet;
}; };
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { EdgeWrapper } from '$lib/components/EdgeWrapper'; import { EdgeWrapper } from '$lib/components/EdgeWrapper';
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition'; import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
import type { EdgeEvents } from '$lib/types'; import type { Node, Edge, EdgeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
let { let {
@@ -10,7 +10,7 @@
onedgecontextmenu, onedgecontextmenu,
onedgepointerenter, onedgepointerenter,
onedgepointerleave onedgepointerleave
}: { store: SvelteFlowStore } & EdgeEvents = $props(); }: { store: SvelteFlowStore<NodeType, EdgeType> } & EdgeEvents<EdgeType> = $props();
</script> </script>
<svg class="svelte-flow__marker"> <svg class="svelte-flow__marker">
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { NodeWrapper } from '$lib/components/NodeWrapper'; import { NodeWrapper } from '$lib/components/NodeWrapper';
import type { NodeEvents } from '$lib/types'; import type { Node, Edge, NodeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
let { let {
@@ -17,7 +17,10 @@
onnodedrag, onnodedrag,
onnodedragstart, onnodedragstart,
onnodedragstop onnodedragstop
}: { store: SvelteFlowStore; nodeClickDistance?: number } & NodeEvents = $props(); }: {
store: SvelteFlowStore<NodeType, EdgeType>;
nodeClickDistance?: number;
} & NodeEvents<NodeType> = $props();
const resizeObserver: ResizeObserver | null = const resizeObserver: ResizeObserver | null =
typeof ResizeObserver === 'undefined' typeof ResizeObserver === 'undefined'
@@ -38,7 +38,7 @@
} }
</script> </script>
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { SelectionMode, getEventPosition, getNodesInside } from '@xyflow/system'; import { SelectionMode, getEventPosition, getNodesInside } from '@xyflow/system';
import type { Node, Edge } from '$lib/types'; import type { Node, Edge } from '$lib/types';
@@ -51,7 +51,7 @@
onpaneclick, onpaneclick,
onpanecontextmenu, onpanecontextmenu,
children children
}: PaneProps = $props(); }: PaneProps<NodeType, EdgeType> = $props();
// svelte-ignore non_reactive_update // svelte-ignore non_reactive_update
let container: HTMLDivElement; let container: HTMLDivElement;
@@ -1,9 +1,9 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { PaneEvents } from '$lib/types'; import type { Node, Edge, PaneEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
export type PaneProps = { export type PaneProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
panOnDrag?: boolean | number[]; panOnDrag?: boolean | number[];
selectionOnDrag?: boolean; selectionOnDrag?: boolean;
children: Snippet; children: Snippet;
@@ -3,7 +3,7 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { type SvelteFlowRestProps } from '$lib/store/types'; import { type SvelteFlowRestProps } from '$lib/store/types';
import { toPxString } from '$lib/utils'; import { toPxString } from '$lib/utils';
import type { Node, Edge, NodeTypes } from '$lib/types'; import type { Node, Edge } from '$lib/types';
let { let {
width, width,
@@ -45,9 +45,9 @@ import type { EdgeEvents, NodeEvents, NodeSelectionEvents, PaneEvents } from '$l
export type SvelteFlowProps< export type SvelteFlowProps<
NodeType extends Node = Node, NodeType extends Node = Node,
EdgeType extends Edge = Edge EdgeType extends Edge = Edge
> = NodeEvents & > = NodeEvents<NodeType> &
NodeSelectionEvents & NodeSelectionEvents<NodeType> &
EdgeEvents & EdgeEvents<EdgeType> &
PaneEvents & { PaneEvents & {
/** The id of the flow /** The id of the flow
* *
@@ -1,8 +1,12 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { Node, Edge } from '$lib/types';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
let { store = $bindable(), children }: { store: SvelteFlowStore; children: Snippet } = $props(); let {
store = $bindable(),
children
}: { store: SvelteFlowStore<NodeType, EdgeType>; children: Snippet } = $props();
</script> </script>
<div <div
@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import { PanOnScrollMode, type PanZoomInstance, type Transform } from '@xyflow/system'; import { PanOnScrollMode, type PanZoomInstance, type Transform } from '@xyflow/system';
import zoom from '$lib/actions/zoom'; import zoom from '$lib/actions/zoom';
import type { ZoomProps } from './types'; import type { ZoomProps } from './types';
import type { Node, Edge } from '$lib/types';
let { let {
store = $bindable(), store = $bindable(),
@@ -19,7 +20,7 @@
onmoveend, onmoveend,
oninit, oninit,
children children
}: ZoomProps = $props(); }: ZoomProps<NodeType, EdgeType> = $props();
let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag); let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag);
let panOnScrollActive = $derived(store.panActivationKeyPressed || panOnScroll); let panOnScrollActive = $derived(store.panActivationKeyPressed || panOnScroll);
@@ -1,9 +1,10 @@
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { Edge, Node } from '$lib/types';
import type { OnMoveStart, OnMove, OnMoveEnd, PanOnScrollMode } from '@xyflow/system'; import type { OnMoveStart, OnMove, OnMoveEnd, PanOnScrollMode } from '@xyflow/system';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
export type ZoomProps = { export type ZoomProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
store: SvelteFlowStore; store: SvelteFlowStore<NodeType, EdgeType>;
panOnScrollMode: PanOnScrollMode; panOnScrollMode: PanOnScrollMode;
preventScrolling: boolean; preventScrolling: boolean;
zoomOnScroll: boolean; zoomOnScroll: boolean;
+6 -2
View File
@@ -3,9 +3,13 @@ import type { StoreContext, SvelteFlowStore } from '../store/types';
import { key } from '../store'; import { key } from '../store';
import { derivedWarning } from './derivedWarning.svelte'; import { derivedWarning } from './derivedWarning.svelte';
import type { Node, Edge } from '$lib/types';
export function useStore(): SvelteFlowStore { export function useStore<
const storeContext = getContext<StoreContext>(key); NodeType extends Node = Node,
EdgeType extends Edge = Edge
>(): SvelteFlowStore<NodeType, EdgeType> {
const storeContext = getContext<StoreContext<NodeType, EdgeType>>(key);
if (!storeContext) { if (!storeContext) {
throw new Error( throw new Error(
@@ -32,7 +32,7 @@ import { untrack } from 'svelte';
* *
* @returns helper functions * @returns helper functions
*/ */
export function useSvelteFlow(): { export function useSvelteFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): {
/** /**
* Zooms viewport in by 1.2. * Zooms viewport in by 1.2.
* *
@@ -51,33 +51,33 @@ export function useSvelteFlow(): {
* @param id - the node id * @param id - the node id
* @returns the node or undefined if no node was found * @returns the node or undefined if no node was found
*/ */
getInternalNode: (id: string) => InternalNode | undefined; getInternalNode: (id: string) => InternalNode<NodeType> | undefined;
/** /**
* Returns a node by id. * Returns a node by id.
* *
* @param id - the node id * @param id - the node id
* @returns the node or undefined if no node was found * @returns the node or undefined if no node was found
*/ */
getNode: (id: string) => Node | undefined; getNode: (id: string) => NodeType | undefined;
/** /**
* Returns nodes. * Returns nodes.
* *
* @returns nodes array * @returns nodes array
*/ */
getNodes: (ids?: string[]) => Node[]; getNodes: (ids?: string[]) => NodeType[];
/** /**
* Returns an edge by id. * Returns an edge by id.
* *
* @param id - the edge id * @param id - the edge id
* @returns the edge or undefined if no edge was found * @returns the edge or undefined if no edge was found
*/ */
getEdge: (id: string) => Edge | undefined; getEdge: (id: string) => EdgeType | undefined;
/** /**
* Returns edges. * Returns edges.
* *
* @returns edges array * @returns edges array
*/ */
getEdges: (ids?: string[]) => Edge[]; getEdges: (ids?: string[]) => EdgeType[];
/** /**
* Sets the current zoom level. * Sets the current zoom level.
* *
@@ -133,10 +133,10 @@ export function useSvelteFlow(): {
* @returns an array of intersecting nodes * @returns an array of intersecting nodes
*/ */
getIntersectingNodes: ( getIntersectingNodes: (
nodeOrRect: Node | { id: Node['id'] } | Rect, nodeOrRect: NodeType | { id: NodeType['id'] } | Rect,
partially?: boolean, partially?: boolean,
nodesToIntersect?: Node[] nodesToIntersect?: NodeType[]
) => Node[]; ) => NodeType[];
/** /**
* Checks if the given node or rect intersects with the passed rect. * Checks if the given node or rect intersects with the passed rect.
* *
@@ -147,7 +147,7 @@ export function useSvelteFlow(): {
* @returns true if the node or rect intersects with the given area * @returns true if the node or rect intersects with the given area
*/ */
isNodeIntersecting: ( isNodeIntersecting: (
nodeOrRect: Node | { id: Node['id'] } | Rect, nodeOrRect: NodeType | { id: NodeType['id'] } | Rect,
area: Rect, area: Rect,
partially?: boolean partially?: boolean
) => boolean; ) => boolean;
@@ -170,9 +170,9 @@ export function useSvelteFlow(): {
nodes, nodes,
edges edges
}: { }: {
nodes?: (Node | { id: Node['id'] })[]; nodes?: (Partial<NodeType> & { id: string })[];
edges?: (Edge | { id: Edge['id'] })[]; edges?: (Partial<EdgeType> & { id: string })[];
}) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[] }>; }) => Promise<{ deletedNodes: NodeType[]; deletedEdges: EdgeType[] }>;
/** /**
* Converts a screen / client position to a flow position. * Converts a screen / client position to a flow position.
* *
@@ -209,12 +209,12 @@ export function useSvelteFlow(): {
*/ */
// updateNode: ( // updateNode: (
// id: string, // id: string,
// nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>), // nodeUpdate: Partial<NodeType> | ((node: NodeType) => Partial<NodeTyp>),
// options?: { replace: boolean } // options?: { replace: boolean }
// ) => void; // ) => void;
updateNode: ( updateNode: (
id: string, id: string,
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>), nodeUpdate: Partial<NodeType> | ((node: NodeType) => NodeType),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
/** /**
@@ -229,7 +229,7 @@ export function useSvelteFlow(): {
*/ */
updateNodeData: ( updateNodeData: (
id: string, id: string,
dataUpdate: object | ((node: Node) => object), dataUpdate: Partial<NodeType['data']> | ((node: NodeType) => Partial<NodeType['data']>),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
/** /**
@@ -249,10 +249,10 @@ export function useSvelteFlow(): {
*/ */
updateEdge: ( updateEdge: (
id: string, id: string,
edgeUpdate: Partial<Edge> | ((edge: Edge) => Partial<Edge>), edgeUpdate: Partial<EdgeType> | ((edge: EdgeType) => EdgeType),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; toObject: () => { nodes: NodeType[]; edges: EdgeType[]; viewport: Viewport };
/** /**
* Returns the bounds of the given nodes or node ids. * Returns the bounds of the given nodes or node ids.
* *
@@ -260,7 +260,7 @@ export function useSvelteFlow(): {
* *
* @returns the bounds of the given nodes * @returns the bounds of the given nodes
*/ */
getNodesBounds: (nodes: (Node | InternalNode | string)[]) => Rect; getNodesBounds: (nodes: (NodeType | InternalNode<NodeType> | string)[]) => Rect;
/** Gets all connections for a given handle belonging to a specific node. /** Gets all connections for a given handle belonging to a specific node.
* *
* @param type - handle type 'source' or 'target' * @param type - handle type 'source' or 'target'
@@ -282,9 +282,9 @@ export function useSvelteFlow(): {
derivedWarning('useSvelteFlow'); derivedWarning('useSvelteFlow');
} }
const store = useStore(); const store = useStore<NodeType, EdgeType>();
const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => { const getNodeRect = (node: NodeType | { id: NodeType['id'] }): Rect | null => {
const nodeToUse = isNode(node) ? node : store.nodeLookup.get(node.id)!; const nodeToUse = isNode(node) ? node : store.nodeLookup.get(node.id)!;
const position = nodeToUse.parentId const position = nodeToUse.parentId
? evaluateAbsolutePosition( ? evaluateAbsolutePosition(
@@ -308,13 +308,13 @@ export function useSvelteFlow(): {
function updateNode( function updateNode(
id: string, id: string,
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>), nodeUpdate: Partial<NodeType> | ((node: NodeType) => NodeType),
options: { replace: boolean } = { replace: false } options: { replace: boolean } = { replace: false }
) { ) {
store.nodes = untrack(() => store.nodes).map((node) => { store.nodes = untrack(() => store.nodes).map((node) => {
if (node.id === id) { if (node.id === id) {
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate; const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node) : nodeUpdate;
return options?.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode }; return options?.replace && isNode<NodeType>(nextNode) ? nextNode : { ...node, ...nextNode };
} }
return node; return node;
@@ -323,13 +323,13 @@ export function useSvelteFlow(): {
function updateEdge( function updateEdge(
id: string, id: string,
edgeUpdate: Partial<Edge> | ((edge: Edge) => Partial<Edge>), edgeUpdate: Partial<EdgeType> | ((edge: EdgeType) => EdgeType),
options: { replace: boolean } = { replace: false } options: { replace: boolean } = { replace: false }
) { ) {
store.edges = untrack(() => store.edges).map((edge) => { store.edges = untrack(() => store.edges).map((edge) => {
if (edge.id === id) { if (edge.id === id) {
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge) : edgeUpdate; const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge) : edgeUpdate;
return options.replace && isEdge(nextEdge) ? nextEdge : { ...edge, ...nextEdge }; return options.replace && isEdge<EdgeType>(nextEdge) ? nextEdge : { ...edge, ...nextEdge };
} }
return edge; return edge;
@@ -411,9 +411,9 @@ export function useSvelteFlow(): {
return Promise.resolve(true); return Promise.resolve(true);
}, },
getIntersectingNodes: ( getIntersectingNodes: (
nodeOrRect: Node | { id: Node['id'] } | Rect, nodeOrRect: NodeType | { id: NodeType['id'] } | Rect,
partially = true, partially = true,
nodesToIntersect?: Node[] nodesToIntersect?: NodeType[]
) => { ) => {
const isRect = isRectObject(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect); const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
@@ -436,7 +436,7 @@ export function useSvelteFlow(): {
}); });
}, },
isNodeIntersecting: ( isNodeIntersecting: (
nodeOrRect: Node | { id: Node['id'] } | Rect, nodeOrRect: NodeType | { id: NodeType['id'] } | Rect,
area: Rect, area: Rect,
partially = true partially = true
) => { ) => {
@@ -453,7 +453,10 @@ export function useSvelteFlow(): {
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height; return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
}, },
deleteElements: async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => { deleteElements: async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => {
const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({ const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove<
NodeType,
EdgeType
>({
nodesToRemove, nodesToRemove,
edgesToRemove, edgesToRemove,
nodes: store.nodes, nodes: store.nodes,
@@ -537,8 +540,10 @@ export function useSvelteFlow(): {
} }
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate; const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
updateNode(id, (node) => ({
updateNode(id, { data: options?.replace ? nextData : { ...node.data, ...nextData } }); ...node,
data: options?.replace ? nextData : { ...node.data, ...nextData }
}));
}, },
updateEdge, updateEdge,
getNodesBounds: (nodes) => { getNodesBounds: (nodes) => {
@@ -549,8 +554,14 @@ export function useSvelteFlow(): {
}; };
} }
function getElements(lookup: Map<string, InternalNode>, ids: string[]): Node[]; function getElements<NodeType extends Node = Node>(
function getElements(lookup: Map<string, Edge>, ids: string[]): Edge[]; lookup: Map<string, InternalNode<NodeType>>,
ids: string[]
): NodeType[];
function getElements<EdgeType extends Edge = Edge>(
lookup: Map<string, EdgeType>,
ids: string[]
): EdgeType[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function getElements(lookup: Map<string, any>, ids: string[]): any[] { function getElements(lookup: Map<string, any>, ids: string[]): any[] {
const result = []; const result = [];
@@ -143,7 +143,7 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
connectionLookup: ConnectionLookup = new Map(); connectionLookup: ConnectionLookup = new Map();
edgeLookup: EdgeLookup<EdgeType> = new Map(); edgeLookup: EdgeLookup<EdgeType> = new Map();
_prevVisibleEdges = new Map<string, EdgeLayouted>(); _prevVisibleEdges = new Map<string, EdgeLayouted<EdgeType>>();
visible = $derived.by(() => { visible = $derived.by(() => {
const { const {
// We need to access this._nodes to trigger on changes // We need to access this._nodes to trigger on changes
@@ -158,8 +158,8 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
defaultEdgeOptions defaultEdgeOptions
} = this; } = this;
let visibleNodes: Map<string, InternalNode>; let visibleNodes: Map<string, InternalNode<NodeType>>;
let visibleEdges: Map<string, EdgeLayouted>; let visibleEdges: Map<string, EdgeLayouted<EdgeType>>;
const options = { const options = {
edges, edges,
+1 -1
View File
@@ -116,6 +116,6 @@ export type StoreContext<NodeType extends Node = Node, EdgeType extends Edge = E
export type ProviderContext< export type ProviderContext<
NodeType extends Node = Node, NodeType extends Node = Node,
EdgeType extends Edge = Edge EdgeType extends Edge = Edge
> = StoreContext & { > = StoreContext<NodeType, EdgeType> & {
setStore: (store: SvelteFlowStore<NodeType, EdgeType>) => void; setStore: (store: SvelteFlowStore<NodeType, EdgeType>) => void;
}; };
@@ -10,13 +10,13 @@ import {
type Transform type Transform
} from '@xyflow/system'; } from '@xyflow/system';
export function getVisibleNodes( export function getVisibleNodes<NodeType extends Node = Node>(
nodeLookup: NodeLookup, nodeLookup: NodeLookup<InternalNode<NodeType>>,
transform: Transform, transform: Transform,
width: number, width: number,
height: number height: number
) { ) {
const visibleNodes = new Map<string, InternalNode>(); const visibleNodes = new Map<string, InternalNode<NodeType>>();
getNodesInside(nodeLookup, { x: 0, y: 0, width: width, height: height }, transform, true).forEach( getNodesInside(nodeLookup, { x: 0, y: 0, width: width, height: height }, transform, true).forEach(
(node) => { (node) => {
visibleNodes.set(node.id, node); visibleNodes.set(node.id, node);
@@ -29,7 +29,7 @@ export interface EdgeLayoutBaseOptions<NodeType extends Node = Node, EdgeType ex
edges: EdgeType[]; edges: EdgeType[];
defaultEdgeOptions: DefaultEdgeOptions; defaultEdgeOptions: DefaultEdgeOptions;
elevateEdgesOnSelect: boolean; elevateEdgesOnSelect: boolean;
previousEdges: Map<string, EdgeLayouted>; previousEdges: Map<string, EdgeLayouted<EdgeType>>;
nodeLookup: NodeLookup<InternalNode<NodeType>>; nodeLookup: NodeLookup<InternalNode<NodeType>>;
connectionMode: ConnectionMode; connectionMode: ConnectionMode;
onerror: OnError; onerror: OnError;
@@ -44,7 +44,10 @@ export interface EdgeLayoutAllOptions<NodeType extends Node = Node, EdgeType ext
height: never; height: never;
} }
export interface EdgeLayoutOnlyVisibleOptions extends EdgeLayoutBaseOptions { export interface EdgeLayoutOnlyVisibleOptions<
NodeType extends Node = Node,
EdgeType extends Edge = Edge
> extends EdgeLayoutBaseOptions<NodeType, EdgeType> {
visibleNodes: Map<string, InternalNode>; visibleNodes: Map<string, InternalNode>;
transform: Transform; transform: Transform;
width: number; width: number;
@@ -54,9 +57,11 @@ export interface EdgeLayoutOnlyVisibleOptions extends EdgeLayoutBaseOptions {
export type EdgeLayoutOptions<NodeType extends Node = Node, EdgeType extends Edge = Edge> = export type EdgeLayoutOptions<NodeType extends Node = Node, EdgeType extends Edge = Edge> =
| EdgeLayoutAllOptions<NodeType, EdgeType> | EdgeLayoutAllOptions<NodeType, EdgeType>
| EdgeLayoutOnlyVisibleOptions; | EdgeLayoutOnlyVisibleOptions<NodeType, EdgeType>;
export function getLayoutedEdges(options: EdgeLayoutOptions): Map<string, EdgeLayouted> { export function getLayoutedEdges<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
options: EdgeLayoutOptions<NodeType, EdgeType>
): Map<string, EdgeLayouted<EdgeType>> {
const { const {
edges, edges,
defaultEdgeOptions, defaultEdgeOptions,
@@ -67,7 +72,7 @@ export function getLayoutedEdges(options: EdgeLayoutOptions): Map<string, EdgeLa
onlyRenderVisible, onlyRenderVisible,
elevateEdgesOnSelect elevateEdgesOnSelect
} = options; } = options;
const layoutedEdges = new Map<string, EdgeLayouted>(); const layoutedEdges = new Map<string, EdgeLayouted<EdgeType>>();
for (const edge of edges) { for (const edge of edges) {
const sourceNode = nodeLookup.get(edge.source); const sourceNode = nodeLookup.get(edge.source);
const targetNode = nodeLookup.get(edge.target); const targetNode = nodeLookup.get(edge.target);
+2 -2
View File
@@ -151,11 +151,11 @@ export type EdgeTypes = Record<
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>; export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
export type EdgeLayouted = Edge & export type EdgeLayouted<EdgeType extends Edge = Edge> = EdgeType &
EdgePosition & { EdgePosition & {
sourceNode?: Node; sourceNode?: Node;
targetNode?: Node; targetNode?: Node;
sourceHandleId?: string | null; sourceHandleId?: string | null;
targetHandleId?: string | null; targetHandleId?: string | null;
edge: Edge; edge: EdgeType;
}; };
+24 -24
View File
@@ -1,46 +1,46 @@
import type { Edge } from './edges'; import type { Edge } from './edges';
import type { Node } from './nodes'; import type { Node } from './nodes';
export type NodeEventWithPointer<T = PointerEvent> = ({ export type NodeEventWithPointer<T = PointerEvent, NodeType extends Node = Node> = ({
node, node,
event event
}: { }: {
node: Node; node: NodeType;
event: T; event: T;
}) => void; }) => void;
export type NodesEventWithPointer<T = PointerEvent> = ({ export type NodesEventWithPointer<T = PointerEvent, NodeType extends Node = Node> = ({
nodes, nodes,
event event
}: { }: {
nodes: Node[]; nodes: NodeType[];
event: T; event: T;
}) => void; }) => void;
export type NodeTargetEventWithPointer<T = PointerEvent> = ({ export type NodeTargetEventWithPointer<T = PointerEvent, NodeType extends Node = Node> = ({
targetNode, targetNode,
nodes, nodes,
event event
}: { }: {
targetNode: Node | null; targetNode: NodeType | null;
nodes: Node[]; nodes: NodeType[];
event: T; event: T;
}) => void; }) => void;
export type NodeEvents = { export type NodeEvents<NodeType extends Node = Node> = {
onnodeclick?: NodeEventWithPointer<MouseEvent | TouchEvent>; onnodeclick?: NodeEventWithPointer<MouseEvent | TouchEvent, NodeType>;
onnodecontextmenu?: NodeEventWithPointer<MouseEvent>; onnodecontextmenu?: NodeEventWithPointer<MouseEvent, NodeType>;
onnodedrag?: NodeTargetEventWithPointer<MouseEvent | TouchEvent>; onnodedrag?: NodeTargetEventWithPointer<MouseEvent | TouchEvent, NodeType>;
onnodedragstart?: NodeTargetEventWithPointer<MouseEvent | TouchEvent>; onnodedragstart?: NodeTargetEventWithPointer<MouseEvent | TouchEvent, NodeType>;
onnodedragstop?: NodeTargetEventWithPointer<MouseEvent | TouchEvent>; onnodedragstop?: NodeTargetEventWithPointer<MouseEvent | TouchEvent, NodeType>;
onnodepointerenter?: NodeEventWithPointer; onnodepointerenter?: NodeEventWithPointer<PointerEvent, NodeType>;
onnodepointerleave?: NodeEventWithPointer; onnodepointerleave?: NodeEventWithPointer<PointerEvent, NodeType>;
onnodepointermove?: NodeEventWithPointer; onnodepointermove?: NodeEventWithPointer<PointerEvent, NodeType>;
}; };
export type NodeSelectionEvents = { export type NodeSelectionEvents<NodeType extends Node = Node> = {
onselectioncontextmenu?: NodesEventWithPointer<MouseEvent>; onselectioncontextmenu?: NodesEventWithPointer<MouseEvent, NodeType>;
onselectionclick?: NodesEventWithPointer<MouseEvent>; onselectionclick?: NodesEventWithPointer<MouseEvent, NodeType>;
}; };
export type PaneEvents = { export type PaneEvents = {
@@ -48,9 +48,9 @@ export type PaneEvents = {
onpanecontextmenu?: ({ event }: { event: MouseEvent }) => void; onpanecontextmenu?: ({ event }: { event: MouseEvent }) => void;
}; };
export type EdgeEvents = { export type EdgeEvents<EdgeType extends Edge = Edge> = {
onedgeclick?: ({ edge, event }: { edge: Edge; event: MouseEvent }) => void; onedgeclick?: ({ edge, event }: { edge: EdgeType; event: MouseEvent }) => void;
onedgecontextmenu?: ({ edge, event }: { edge: Edge; event: MouseEvent }) => void; onedgecontextmenu?: ({ edge, event }: { edge: EdgeType; event: MouseEvent }) => void;
onedgepointerenter?: ({ edge, event }: { edge: Edge; event: PointerEvent }) => void; onedgepointerenter?: ({ edge, event }: { edge: EdgeType; event: PointerEvent }) => void;
onedgepointerleave?: ({ edge, event }: { edge: Edge; event: PointerEvent }) => void; onedgepointerleave?: ({ edge, event }: { edge: EdgeType; event: PointerEvent }) => void;
}; };