cleaning up Svelte 4 leftovers

This commit is contained in:
peterkogo
2024-12-09 15:59:06 +01:00
parent d0fce3984d
commit 1d321ebcd6
14 changed files with 86 additions and 586 deletions
@@ -124,16 +124,22 @@
]); ]);
function updateNode() { function updateNode() {
nodes[0].position.x += 20; // We might as well just use updateNode() here
// nodes = $nodes; // this is just to show what is required to update a node
const newNode = { ...nodes[0] };
newNode.position.x += 20;
nodes[0] = newNode;
nodes = [...nodes];
} }
function updateEdge() { function updateEdge() {
edges[0].type = edges[0].type === 'default' ? 'smoothstep' : 'default'; // We might as well just use updateEdge() here
// edges = $edges; // this is just to show what is required to update an edge
const newEdge = { ...edges[0] };
newEdge.type = newEdge.type === 'default' ? 'smoothstep' : 'default';
edges[0] = newEdge;
edges = [...edges];
} }
// $inspect(nodes);
</script> </script>
<SvelteFlow <SvelteFlow
@@ -152,38 +158,38 @@
initialViewport={{ x: 100, y: 100, zoom: 2 }} initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]} snapGrid={[25, 25]}
oninit={() => console.log('on init')} oninit={() => console.log('on init')}
on:nodeclick={(event) => console.log('on node click', event)} onnodeclick={(event) => console.log('on node click', event)}
on:nodemouseenter={(event) => console.log('on node enter', event)} onnodemouseenter={(event) => console.log('on node enter', event)}
on:nodemouseleave={(event) => console.log('on node leave', event)} onnodemouseleave={(event) => console.log('on node leave', event)}
on:edgeclick={(event) => console.log('edge click', event)} onedgeclick={(event) => console.log('edge click', event)}
on:edgemouseenter={(event) => console.log('edge enter', event)} onedgemouseenter={(event) => console.log('edge enter', event)}
on:edgemouseleave={(event) => console.log('edge leave', event)} onedgemouseleave={(event) => console.log('edge leave', event)}
onconnectstart={(event) => console.log('on connect start', event)} onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)} onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)} onconnectend={(event) => console.log('on connect end', event)}
on:paneclick={(event) => console.log('on pane click', event)} onpaneclick={(event) => console.log('on pane click', event)}
on:panecontextmenu={(event) => { onpanecontextmenu={(event) => {
console.log('on pane contextmenu', event); console.log('on pane contextmenu', event);
}} }}
on:nodedrag={(event) => { onnodedrag={(event) => {
console.log('on node drag', event); console.log('on node drag', event);
}} }}
on:nodedragstart={(event) => { onnodedragstart={(event) => {
console.log('on node drag start', event); console.log('on node drag start', event);
}} }}
on:nodedragstop={(event) => { onnodedragstop={({ event }) => {
console.log('on node drag stop', event); console.log('on node drag stop', event);
}} }}
on:nodecontextmenu={(event) => { onnodecontextmenu={({ event }) => {
event.detail.event.preventDefault(); event.preventDefault();
console.log('on node contextmenu', event); console.log('on node contextmenu', event);
}} }}
on:edgecontextmenu={({ detail: { event, edge } }) => { onedgecontextmenu={({ event, edge }) => {
event.preventDefault(); event.preventDefault();
console.log('on edge contextmenu', edge); console.log('on edge contextmenu', edge);
}} }}
on:selectionclick={(event) => console.log('on selection click', event)} onselectionclick={(event) => console.log('on selection click', event)}
on:selectioncontextmenu={(event) => console.log('on selection contextmenu', event)} onselectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={async ({ nodes, edges }) => { onbeforedelete={async ({ nodes, edges }) => {
console.log('on before delete', nodes, edges); console.log('on before delete', nodes, edges);
const deleteElements = confirm('Are you sure you want to delete the selected elements?'); const deleteElements = confirm('Are you sure you want to delete the selected elements?');
@@ -199,8 +205,7 @@
{#snippet before()} {#snippet before()}
<ControlButton>xy</ControlButton> <ControlButton>xy</ControlButton>
{/snippet} {/snippet}
<ControlButton aria-label="log" on:click={() => console.log('control button')} <ControlButton aria-label="log" onclick={() => console.log('control button')}>log</ControlButton
>log</ControlButton
> >
</Controls> </Controls>
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
+1 -1
View File
@@ -5,7 +5,7 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess({ script: true }),
compilerOptions: { compilerOptions: {
runes: true runes: true
}, },
+4 -1
View File
@@ -8,7 +8,10 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true,
"target": "ES2022",
"verbatimModuleSyntax": true,
"isolatedModules": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// //
+2 -2
View File
@@ -83,13 +83,13 @@
"typescript": "^5.5.0" "typescript": "^5.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" "svelte": "^5.0.0"
}, },
"files": [ "files": [
"dist" "dist"
], ],
"typesVersions": { "typesVersions": {
">4.0": { ">5.0": {
"index": [ "index": [
"./dist/lib/index.d.ts" "./dist/lib/index.d.ts"
] ]
@@ -7,7 +7,6 @@
if (event.target !== container) { if (event.target !== container) {
return; return;
} }
handler?.(event); handler?.(event);
}; };
} }
@@ -17,7 +16,7 @@
const isSelected = ids.includes(item.id); const isSelected = ids.includes(item.id);
if (item.selected !== isSelected) { if (item.selected !== isSelected) {
item.selected = isSelected; return { ...item, selected: isSelected };
} }
return item; return item;
@@ -33,7 +32,6 @@
getConnectedEdges getConnectedEdges
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore } from '$lib/store';
import type { Node, Edge, InternalNode } from '$lib/types'; import type { Node, Edge, InternalNode } from '$lib/types';
import type { PaneProps } from './types'; import type { PaneProps } from './types';
let { let {
@@ -45,8 +43,6 @@
children children
}: PaneProps = $props(); }: PaneProps = $props();
// const { nodes, edges } = store;
// svelte-ignore non_reactive_update // svelte-ignore non_reactive_update
let container: HTMLDivElement; let container: HTMLDivElement;
let containerBounds: DOMRect | null = null; let containerBounds: DOMRect | null = null;
@@ -143,14 +139,14 @@
prevSelectedNodeIds.length !== selectedNodeIds.length || prevSelectedNodeIds.length !== selectedNodeIds.length ||
selectedNodeIds.some((id) => !prevSelectedNodeIds.includes(id)) selectedNodeIds.some((id) => !prevSelectedNodeIds.includes(id))
) { ) {
// store.nodes.update((nodes) => nodes.map(toggleSelected(selectedNodeIds))); store.nodes = store.nodes.map(toggleSelected(selectedNodeIds));
} }
if ( if (
prevSelectedEdgeIds.length !== selectedEdgeIds.length || prevSelectedEdgeIds.length !== selectedEdgeIds.length ||
selectedEdgeIds.some((id) => !prevSelectedEdgeIds.includes(id)) selectedEdgeIds.some((id) => !prevSelectedEdgeIds.includes(id))
) { ) {
// store.edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds))); store.edges = store.edges.map(toggleSelected(selectedEdgeIds));
} }
store.selectionRectMode = 'user'; store.selectionRectMode = 'user';
@@ -1,6 +1,3 @@
<!-- TODO: why is this neccessary for types to work?? -->
<svelte:options runes={true} />
<script lang="ts"> <script lang="ts">
import { getContext, setContext, onDestroy } from 'svelte'; import { getContext, setContext, onDestroy } from 'svelte';
import cc from 'classcat'; import cc from 'classcat';
@@ -19,7 +16,7 @@
import { key, createStore } from '$lib/store'; import { key, createStore } from '$lib/store';
import type { SvelteFlowProps } from './types'; import type { SvelteFlowProps } from './types';
import { useColorModeClass } from '$lib/hooks/useColorModeClass'; import { useColorModeClass } from '$lib/hooks/useColorModeClass';
import { type ProviderContext, type ContainerSignals, type StoreContext } from '$lib/store/types'; import { type ProviderContext, type StoreContext } from '$lib/store/types';
let { let {
style, style,
@@ -62,7 +59,6 @@
panOnScroll = false, panOnScroll = false,
panOnDrag = true, panOnDrag = true,
selectionOnDrag = true, selectionOnDrag = true,
defaultEdgeOptions,
connectionLineContainerStyle = '', connectionLineContainerStyle = '',
connectionLineStyle = '', connectionLineStyle = '',
attributionPosition, attributionPosition,
@@ -101,11 +97,13 @@
} }
}); });
// Set store for provider context
const providerContext = getContext<ProviderContext>(key); const providerContext = getContext<ProviderContext>(key);
if (providerContext) { if (providerContext) {
providerContext.setStore(store); providerContext.setStore(store);
} }
// Overwrite store context to give children direct access
setContext(key, { setContext(key, {
provider: false, provider: false,
getStore() { getStore() {
@@ -31,7 +31,8 @@ import type {
OnDelete, OnDelete,
OnEdgeCreate, OnEdgeCreate,
OnBeforeDelete, OnBeforeDelete,
IsValidConnection IsValidConnection,
DefaultNodeOptions
} from '$lib/types'; } from '$lib/types';
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
@@ -301,6 +302,22 @@ export type SvelteFlowProps = NodeEvents &
* } * }
*/ */
defaultEdgeOptions?: DefaultEdgeOptions; defaultEdgeOptions?: DefaultEdgeOptions;
/** Defaults to be applied to all new nodes that are added to the flow.
*
* Properties on a new nodes will override these defaults if they exist.
* @example
* const defaultNodeOptions = {
* type: 'customNodeType',
* position: {x: 0, y: 0}
* data: { label: 'custom label' },
* hidden: false,
* deletable: true,
* selected: false,
* focusable: true,
* zIndex: 12,
* }
*/
defaultNodeOptions?: DefaultNodeOptions;
/** Sets a fixed width for the flow */ /** Sets a fixed width for the flow */
width?: number; width?: number;
/** Sets a fixed height for the flow */ /** Sets a fixed height for the flow */
@@ -1,24 +0,0 @@
import { getContext } from 'svelte';
import type { StoreContext, SvelteFlowStore } from './types';
import { key } from './';
export function useStore(): SvelteFlowStore {
const storeContext = getContext<StoreContext>(key);
if (!storeContext) {
throw new Error(
'In order to use useStore you need to wrap your component in a <SvelteFlowProvider />'
);
}
if (process.env.NODE_ENV === 'development') {
if (storeContext.provider && !$effect.tracking()) {
console.warn(
'Use $derived(useStore()), when not calling inside a child of the <SvelteFlow /> component.'
);
}
}
return storeContext.getStore();
}
+13 -20
View File
@@ -21,14 +21,12 @@ import {
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types'; import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store.svelte'; import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store.svelte';
import { type StoreSignals, type SvelteFlowStore, type SvelteFlowStoreActions } from './types'; import { type StoreSignals, type SvelteFlowStore, type SvelteFlowStoreActions } from './types';
import { syncNodeStores, syncEdgeStores } from './utils';
import drag from '$lib/actions/drag';
// import { getVisibleEdges } from './visible-edges'; // import { getVisibleEdges } from './visible-edges';
// import { getVisibleNodes } from './visible-nodes'; // import { getVisibleNodes } from './visible-nodes';
export const key = Symbol(); export const key = Symbol();
export { useStore } from './hook.svelte'; export { useStore } from '../hooks/useStore';
export function createStore(signals: StoreSignals): SvelteFlowStore { export function createStore(signals: StoreSignals): SvelteFlowStore {
const store = getInitialStore(signals); const store = getInitialStore(signals);
@@ -82,7 +80,9 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
...store.fitViewOptions, ...store.fitViewOptions,
nodes: store.fitViewOptions?.nodes nodes: store.fitViewOptions?.nodes
}); });
store.fitViewOnInitDone = fitViewOnInitDone; if (fitViewOnInitDone) {
store.fitViewOnInitDone = fitViewOnInitDone;
}
} }
const newNodes = new Map<string, Node>(); const newNodes = new Map<string, Node>();
@@ -115,9 +115,7 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
newNodes.set(change.id, node); newNodes.set(change.id, node);
} }
// store.nodes.update((nds) => nds);
store.nodes = store.nodes.map((node) => newNodes.get(node.id) ?? node); store.nodes = store.nodes.map((node) => newNodes.get(node.id) ?? node);
// signals.updateNodes((node) => newNodes.get(node.id) ?? node);
if (!store.nodesInitialized) { if (!store.nodesInitialized) {
store.nodesInitialized = true; store.nodesInitialized = true;
@@ -244,24 +242,23 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
const isMultiSelection = store.multiselectionKeyPressed; const isMultiSelection = store.multiselectionKeyPressed;
// store.nodes.update((ns) => // store.nodes.update((ns) =>
store.nodes.forEach((node) => { store.nodes = store.nodes.map((node) => {
const nodeWillBeSelected = ids.includes(node.id); const nodeWillBeSelected = ids.includes(node.id);
const selected = isMultiSelection ? node.selected || nodeWillBeSelected : nodeWillBeSelected; const selected = isMultiSelection ? node.selected || nodeWillBeSelected : nodeWillBeSelected;
// we need to mutate the node here in order to have the correct selected state in the drag handler if (node.selected !== selected) {
node.selected = selected; // we need to mutate the node here in order to have the correct selected state in the drag handler
node.selected = selected;
return { ...node };
}
// return node; return node;
}); });
// );
if (!isMultiSelection) { if (!isMultiSelection) {
// store.edges.forEach((es) => store.edges = store.edges.map((edge) => {
store.edges.forEach((edge) => { return edge.selected ? { ...edge, selected: false } : edge;
edge.selected = false;
// return edge;
}); });
// );
} }
} }
@@ -319,7 +316,6 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
}); });
} }
// const _connection = writable<ConnectionState>(initialConnection);
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => { const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
store.rawConnection = { ...newConnection }; store.rawConnection = { ...newConnection };
}; };
@@ -338,9 +334,6 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
} }
const storeWithActions = Object.assign(store, { const storeWithActions = Object.assign(store, {
// syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
// syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
// syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
setNodeTypes, setNodeTypes,
setEdgeTypes, setEdgeTypes,
addEdge, addEdge,
@@ -49,7 +49,9 @@ import type {
OnDelete, OnDelete,
OnEdgeCreate, OnEdgeCreate,
OnBeforeDelete, OnBeforeDelete,
IsValidConnection IsValidConnection,
Edge,
Node
} from '$lib/types'; } from '$lib/types';
import type { StoreSignals } from './types'; import type { StoreSignals } from './types';
@@ -69,6 +71,8 @@ export const initialEdgeTypes = {
}; };
export const getInitialStore = (signals: StoreSignals) => { export const getInitialStore = (signals: StoreSignals) => {
// We use a class here, because Svelte adds getters & setter for us.
// Inline classes have some performance implications but we just call it once.
class SvelteFlowStore { class SvelteFlowStore {
get nodes() { get nodes() {
return signals.nodes; return signals.nodes;
@@ -86,6 +90,7 @@ export const getInitialStore = (signals: StoreSignals) => {
parentLookup: ParentLookup = new Map(); parentLookup: ParentLookup = new Map();
connectionLookup: ConnectionLookup = new Map(); connectionLookup: ConnectionLookup = new Map();
edgeLookup: EdgeLookup = new Map(); edgeLookup: EdgeLookup = new Map();
adoptNodes: true = $derived.by(() => { adoptNodes: true = $derived.by(() => {
adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, { adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, {
nodeExtent: this.nodeExtent, nodeExtent: this.nodeExtent,
@@ -100,7 +105,7 @@ export const getInitialStore = (signals: StoreSignals) => {
return true; return true;
}); });
domNode: HTMLDivElement | null = $derived(signals.domNode ?? signals.props.domNode ?? null); domNode: HTMLDivElement | null = $derived(signals.domNode ?? signals.domNode ?? null);
width: number = $derived(signals.width ?? signals.props.width ?? 0); width: number = $derived(signals.width ?? signals.props.width ?? 0);
height: number = $derived(signals.height ?? signals.props.height ?? 0); height: number = $derived(signals.height ?? signals.props.height ?? 0);
@@ -112,6 +117,9 @@ export const getInitialStore = (signals: StoreSignals) => {
nodeExtent: CoordinateExtent = $derived(signals.props.nodeExtent ?? infiniteExtent); nodeExtent: CoordinateExtent = $derived(signals.props.nodeExtent ?? infiniteExtent);
translateExtent: CoordinateExtent = $derived(signals.props.translateExtent ?? infiniteExtent); translateExtent: CoordinateExtent = $derived(signals.props.translateExtent ?? infiniteExtent);
defaultEdgeOptions: Partial<Edge> = $derived(signals.props.defaultEdgeOptions ?? {});
defaultNodeOptions: Partial<Node> = $derived(signals.props.defaultNodeOptions ?? {});
nodeDragThreshold: number = $derived(signals.props.nodeDragThreshold ?? 1); nodeDragThreshold: number = $derived(signals.props.nodeDragThreshold ?? 1);
autoPanOnNodeDrag: boolean = $derived(signals.props.autoPanOnNodeDrag ?? true); autoPanOnNodeDrag: boolean = $derived(signals.props.autoPanOnNodeDrag ?? true);
autoPanOnConnect: boolean = $derived(signals.props.autoPanOnConnect ?? true); autoPanOnConnect: boolean = $derived(signals.props.autoPanOnConnect ?? true);
@@ -207,6 +215,7 @@ export const getInitialStore = (signals: StoreSignals) => {
colorMode: ColorMode = $derived(signals.props.colorMode ?? 'light'); colorMode: ColorMode = $derived(signals.props.colorMode ?? 'light');
constructor() { constructor() {
// Process intial fitView here
if (signals.props.fitView && !signals.props.initialViewport && this.width && this.height) { if (signals.props.fitView && !signals.props.initialViewport && this.width && this.height) {
const bounds = getInternalNodesBounds(this.nodeLookup, { const bounds = getInternalNodesBounds(this.nodeLookup, {
filter: (node) => filter: (node) =>
@@ -221,95 +230,4 @@ export const getInitialStore = (signals: StoreSignals) => {
} }
} }
return new SvelteFlowStore(); return new SvelteFlowStore();
// return new Store();
// const nodeLookup: NodeLookup = new Map();
// const parentLookup = new Map();
// const connectionLookup = new Map();
// const edgeLookup = new Map();
// const storeNodeOrigin = nodeOrigin ?? [0, 0];
// const storeNodeExtent = nodeExtent ?? infiniteExtent;
// adoptUserNodes(nodes, nodeLookup, parentLookup, {
// nodeExtent: storeNodeExtent,
// nodeOrigin: storeNodeOrigin,
// elevateNodesOnSelect: false,
// checkEquality: false
// });
// updateConnectionLookup(connectionLookup, edgeLookup, edges);
// let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
// if (fitView && width && height) {
// const bounds = getInternalNodesBounds(nodeLookup, {
// filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight))
// });
// viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
// }
// return {
// flowId: writable<string | null>(null),
// nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin, storeNodeExtent),
// nodeLookup: readable<NodeLookup<InternalNode>>(nodeLookup),
// parentLookup: readable<ParentLookup<InternalNode>>(parentLookup),
// edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
// visibleNodes: readable<InternalNode[]>([]),
// edges: createEdgesStore(edges, connectionLookup, edgeLookup),
// visibleEdges: readable<EdgeLayouted[]>([]),
// connectionLookup: readable<ConnectionLookup>(connectionLookup),
// height: writable<number>(500),
// width: writable<number>(500),
// minZoom: writable<number>(0.5),
// maxZoom: writable<number>(2),
// nodeOrigin: writable<NodeOrigin>(storeNodeOrigin),
// nodeDragThreshold: writable<number>(1),
// nodeExtent: writable<CoordinateExtent>(storeNodeExtent),
// translateExtent: writable<CoordinateExtent>(infiniteExtent),
// autoPanOnNodeDrag: writable<boolean>(true),
// autoPanOnConnect: writable<boolean>(true),
// fitViewOnInit: writable<boolean>(false),
// fitViewOnInitDone: writable<boolean>(false),
// fitViewOptions: writable<FitViewOptions>(undefined),
// panZoom: writable<PanZoomInstance | null>(null),
// snapGrid: writable<SnapGrid | null>(null),
// dragging: writable<boolean>(false),
// selectionRect: writable<SelectionRect | null>(null),
// selectionKeyPressed: writable<boolean>(false),
// multiselectionKeyPressed: writable<boolean>(false),
// deleteKeyPressed: writable<boolean>(false),
// panActivationKeyPressed: writable<boolean>(false),
// zoomActivationKeyPressed: writable<boolean>(false),
// selectionRectMode: writable<string | null>(null),
// selectionMode: writable<SelectionMode>(SelectionMode.Partial),
// nodeTypes: writable<NodeTypes>(initialNodeTypes),
// edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
// viewport: writable<Viewport>(viewport),
// connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
// domNode: writable<HTMLDivElement | null>(null),
// connection: readable<ConnectionState>(initialConnection),
// connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
// connectionRadius: writable<number>(20),
// isValidConnection: writable<IsValidConnection>(() => true),
// nodesDraggable: writable<boolean>(true),
// nodesConnectable: writable<boolean>(true),
// elementsSelectable: writable<boolean>(true),
// selectNodesOnDrag: writable<boolean>(true),
// markers: readable<MarkerProps[]>([]),
// defaultMarkerColor: writable<string>('#b1b1b7'),
// lib: readable<string>('svelte'),
// onlyRenderVisibleElements: writable<boolean>(false),
// onerror: writable<OnError>(devWarn),
// ondelete: writable<OnDelete>(undefined),
// onedgecreate: writable<OnEdgeCreate>(undefined),
// onconnect: writable<OnConnect>(undefined),
// onconnectstart: writable<OnConnectStart>(undefined),
// onconnectend: writable<OnConnectEnd>(undefined),
// onbeforedelete: writable<OnBeforeDelete>(undefined),
// nodesInitialized: writable<boolean>(false),
// edgesInitialized: writable<boolean>(false),
// viewportInitialized: writable<boolean>(false),
// initialized: readable<boolean>(false)
// };
}; };
@@ -1,178 +0,0 @@
import { readable, writable } from 'svelte/store';
import {
infiniteExtent,
SelectionMode,
ConnectionMode,
ConnectionLineType,
devWarn,
adoptUserNodes,
getViewportForBounds,
updateConnectionLookup,
initialConnection,
type SelectionRect,
type SnapGrid,
type MarkerProps,
type PanZoomInstance,
type CoordinateExtent,
type NodeOrigin,
type OnError,
type Viewport,
type ConnectionLookup,
type OnConnect,
type OnConnectStart,
type OnConnectEnd,
type NodeLookup,
type EdgeLookup,
type ConnectionState,
type ParentLookup,
getInternalNodesBounds
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import InputNode from '$lib/components/nodes/InputNode.svelte';
import OutputNode from '$lib/components/nodes/OutputNode.svelte';
import GroupNode from '$lib/components/nodes/GroupNode.svelte';
import {
BezierEdgeInternal,
SmoothStepEdgeInternal,
StraightEdgeInternal,
StepEdgeInternal
} from '$lib/components/edges';
import type {
NodeTypes,
EdgeTypes,
EdgeLayouted,
Node,
Edge,
FitViewOptions,
OnDelete,
OnEdgeCreate,
OnBeforeDelete,
IsValidConnection,
InternalNode
} from '$lib/types';
import { createNodesStore, createEdgesStore } from './utils';
export const initialNodeTypes = {
input: InputNode,
output: OutputNode,
default: DefaultNode,
group: GroupNode
};
export const initialEdgeTypes = {
straight: StraightEdgeInternal,
smoothstep: SmoothStepEdgeInternal,
default: BezierEdgeInternal,
step: StepEdgeInternal
};
export const getInitialStore = ({
nodes = [],
edges = [],
width,
height,
fitView,
nodeOrigin,
nodeExtent
}: {
nodes?: Node[];
edges?: Edge[];
width?: number;
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) => {
const nodeLookup: NodeLookup = new Map();
const parentLookup = new Map();
const connectionLookup = new Map();
const edgeLookup = new Map();
const storeNodeOrigin = nodeOrigin ?? [0, 0];
const storeNodeExtent = nodeExtent ?? infiniteExtent;
adoptUserNodes(nodes, nodeLookup, parentLookup, {
nodeExtent: storeNodeExtent,
nodeOrigin: storeNodeOrigin,
elevateNodesOnSelect: false,
checkEquality: false
});
updateConnectionLookup(connectionLookup, edgeLookup, edges);
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
if (fitView && width && height) {
const bounds = getInternalNodesBounds(nodeLookup, {
filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight))
});
viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
}
return {
flowId: writable<string | null>(null),
nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin, storeNodeExtent),
nodeLookup: readable<NodeLookup<InternalNode>>(nodeLookup),
parentLookup: readable<ParentLookup<InternalNode>>(parentLookup),
edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
visibleNodes: readable<InternalNode[]>([]),
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
visibleEdges: readable<EdgeLayouted[]>([]),
connectionLookup: readable<ConnectionLookup>(connectionLookup),
height: writable<number>(500),
width: writable<number>(500),
minZoom: writable<number>(0.5),
maxZoom: writable<number>(2),
nodeOrigin: writable<NodeOrigin>(storeNodeOrigin),
nodeDragThreshold: writable<number>(1),
nodeExtent: writable<CoordinateExtent>(storeNodeExtent),
translateExtent: writable<CoordinateExtent>(infiniteExtent),
autoPanOnNodeDrag: writable<boolean>(true),
autoPanOnConnect: writable<boolean>(true),
fitViewOnInit: writable<boolean>(false),
fitViewOnInitDone: writable<boolean>(false),
fitViewOptions: writable<FitViewOptions>(undefined),
panZoom: writable<PanZoomInstance | null>(null),
snapGrid: writable<SnapGrid | null>(null),
dragging: writable<boolean>(false),
selectionRect: writable<SelectionRect | null>(null),
selectionKeyPressed: writable<boolean>(false),
multiselectionKeyPressed: writable<boolean>(false),
deleteKeyPressed: writable<boolean>(false),
panActivationKeyPressed: writable<boolean>(false),
zoomActivationKeyPressed: writable<boolean>(false),
selectionRectMode: writable<string | null>(null),
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>(initialNodeTypes),
edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
viewport: writable<Viewport>(viewport),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connection: readable<ConnectionState>(initialConnection),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
connectionRadius: writable<number>(20),
isValidConnection: writable<IsValidConnection>(() => true),
nodesDraggable: writable<boolean>(true),
nodesConnectable: writable<boolean>(true),
elementsSelectable: writable<boolean>(true),
selectNodesOnDrag: writable<boolean>(true),
markers: readable<MarkerProps[]>([]),
defaultMarkerColor: writable<string>('#b1b1b7'),
lib: readable<string>('svelte'),
onlyRenderVisibleElements: writable<boolean>(false),
onerror: writable<OnError>(devWarn),
ondelete: writable<OnDelete>(undefined),
onedgecreate: writable<OnEdgeCreate>(undefined),
onconnect: writable<OnConnect>(undefined),
onconnectstart: writable<OnConnectStart>(undefined),
onconnectend: writable<OnConnectEnd>(undefined),
onbeforedelete: writable<OnBeforeDelete>(undefined),
nodesInitialized: writable<boolean>(false),
edgesInitialized: writable<boolean>(false),
viewportInitialized: writable<boolean>(false),
initialized: readable<boolean>(false)
};
};
+1 -6
View File
@@ -1,4 +1,3 @@
import type { Writable } from 'svelte/store';
import type { import type {
InternalNodeUpdate, InternalNodeUpdate,
XYPosition, XYPosition,
@@ -14,9 +13,6 @@ import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/type
import type { SvelteFlowProps } from '$lib/container/SvelteFlow'; import type { SvelteFlowProps } from '$lib/container/SvelteFlow';
export type SvelteFlowStoreActions = { export type SvelteFlowStoreActions = {
// syncNodeStores: (nodesStore: Writable<Node[]>) => void;
// syncEdgeStores: (edgeStore: Writable<Edge[]>) => void;
// syncViewport: (viewportStore?: Writable<Viewport>) => void;
setNodeTypes: (nodeTypes: NodeTypes) => void; setNodeTypes: (nodeTypes: NodeTypes) => void;
setEdgeTypes: (edgeTypes: EdgeTypes) => void; setEdgeTypes: (edgeTypes: EdgeTypes) => void;
addEdge: (edge: Edge | Connection) => void; addEdge: (edge: Edge | Connection) => void;
@@ -39,9 +35,8 @@ export type SvelteFlowStoreActions = {
reset(): void; reset(): void;
}; };
export type ContainerSignals = { domNode?: HTMLDivElement | null; width?: number; height?: number };
export type StoreSignals = { export type StoreSignals = {
props: Partial<SvelteFlowProps> & ContainerSignals; props: Partial<SvelteFlowProps>;
domNode?: HTMLDivElement; domNode?: HTMLDivElement;
width?: number; width?: number;
height?: number; height?: number;
-222
View File
@@ -1,222 +0,0 @@
import {
writable,
get,
type Unsubscriber,
type Subscriber,
type Updater,
type Writable
} from 'svelte/store';
import {
adoptUserNodes,
updateConnectionLookup,
type Viewport,
type PanZoomInstance,
type ConnectionLookup,
type EdgeLookup,
type NodeLookup,
type ParentLookup,
type NodeOrigin,
infiniteExtent,
type CoordinateExtent
} from '@xyflow/system';
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, InternalNode, Node } from '$lib/types';
// we need to sync the user nodes and the internal nodes so that the user can receive the updates
// made by Svelte Flow (like dragging or selecting a node).
export function syncNodeStores(
nodesStore: ReturnType<typeof createNodesStore>,
userNodesStore: Writable<Node[]>
) {
const nodesStoreSetter = nodesStore.set;
const userNodesStoreSetter = userNodesStore.set;
const currentNodesStore = get(nodesStore);
const currentUserNodesStore = get(userNodesStore);
// depending how the user initializes the nodes, we need to decide if we want to use
// the user nodes or the internal nodes for initialization. A user can use a SvelteFlowProvider
// without providing any nodes, in that case we want to use the nodes passed by the user.
// By default we are using the store nodes, because they already have the absolute positions.
const initWithUserNodes = currentNodesStore.length === 0 && currentUserNodesStore.length > 0;
let val = initWithUserNodes ? currentUserNodesStore : currentNodesStore;
nodesStore.set(val);
const _set = (nds: Node[]) => {
const updatedNodes = nodesStoreSetter(nds);
val = updatedNodes;
userNodesStoreSetter(val);
return updatedNodes;
};
nodesStore.set = userNodesStore.set = _set;
nodesStore.update = userNodesStore.update = (fn: (nds: Node[]) => Node[]) => _set(fn(val));
}
// same for edges
export function syncEdgeStores(
edgesStore: ReturnType<typeof createEdgesStore>,
userEdgesStore: Writable<Edge[]>
) {
const nodesStoreSetter = edgesStore.set;
const userEdgesStoreSetter = userEdgesStore.set;
let val = get(userEdgesStore);
edgesStore.set(val);
const _set = (eds: Edge[]) => {
nodesStoreSetter(eds);
userEdgesStoreSetter(eds);
val = eds;
};
edgesStore.set = userEdgesStore.set = _set;
edgesStore.update = userEdgesStore.update = (fn: (nds: Edge[]) => Edge[]) => _set(fn(val));
}
// it is possible to pass a viewport store to SvelteFlow for having more control
// if that's the case we need to sync the internal viewport with the user viewport
export const syncViewportStores = (
panZoomStore: PanZoomInstance | null,
viewportStore: Writable<Viewport>,
userViewportStore?: Writable<Viewport>
) => {
if (!userViewportStore) {
return;
}
const panZoom = panZoomStore;
const viewportStoreSetter = viewportStore.set;
const userViewportStoreSetter = userViewportStore.set;
let val = userViewportStore ? get(userViewportStore) : { x: 0, y: 0, zoom: 1 };
viewportStore.set(val);
viewportStore.set = (vp: Viewport) => {
viewportStoreSetter(vp);
userViewportStoreSetter(vp);
val = vp;
return vp;
};
userViewportStore.set = (vp: Viewport) => {
panZoom?.syncViewport(vp);
viewportStoreSetter(vp);
userViewportStoreSetter(vp);
val = vp;
return vp;
};
viewportStore.update = (fn: (vp: Viewport) => Viewport) => {
viewportStore.set(fn(val));
};
userViewportStore.update = (fn: (vp: Viewport) => Viewport) => {
userViewportStore.set(fn(val));
};
};
export type NodeStoreOptions = {
elevateNodesOnSelect?: boolean;
};
type Update<T> = Writable<T>['update'];
// we are creating a custom store for the internals nodes in order to update the zIndex and positionAbsolute.
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
export const createNodesStore = (
nodes: Node[],
nodeLookup: NodeLookup<InternalNode>,
parentLookup: ParentLookup<InternalNode>,
nodeOrigin: NodeOrigin = [0, 0],
nodeExtent: CoordinateExtent = infiniteExtent
): {
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
update: (this: void, updater: Updater<Node[]>) => void;
set: (this: void, value: Node[]) => Node[];
setDefaultOptions: (opts: DefaultNodeOptions) => void;
setOptions: (opts: NodeStoreOptions) => void;
} => {
const { subscribe, set } = writable<Node[]>([]);
let value = nodes;
let defaults = {};
let elevateNodesOnSelect = true;
const _set = (nds: Node[]): Node[] => {
adoptUserNodes(nds, nodeLookup, parentLookup, {
elevateNodesOnSelect,
nodeOrigin,
nodeExtent,
defaults,
checkEquality: false
});
value = nds;
set(value);
return value;
};
const _update: Update<Node[]> = (fn: (nds: Node[]) => Node[]) => _set(fn(value));
const setDefaultOptions = (options: DefaultNodeOptions) => {
defaults = options;
};
const setOptions = (options: NodeStoreOptions) => {
elevateNodesOnSelect = options.elevateNodesOnSelect ?? elevateNodesOnSelect;
};
_set(value);
return {
subscribe,
set: _set,
update: _update,
setDefaultOptions,
setOptions
};
};
export const createEdgesStore = (
edges: Edge[],
connectionLookup: ConnectionLookup,
edgeLookup: EdgeLookup<Edge>,
defaultOptions?: DefaultEdgeOptions
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
const { subscribe, set } = writable<Edge[]>([]);
let value = edges;
let defaults = defaultOptions || {};
const _set: typeof set = (eds: Edge[]) => {
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
updateConnectionLookup(connectionLookup, edgeLookup, nextEdges);
value = nextEdges;
set(value);
};
const _update: Update<Edge[]> = (fn: (eds: Edge[]) => Edge[]) => _set(fn(value));
const setDefaultOptions = (options: DefaultEdgeOptions) => {
defaults = options;
};
_set(value);
return {
subscribe,
set: _set,
update: _update,
setDefaultOptions
};
};
+1 -2
View File
@@ -8,8 +8,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true
"noErrorTruncation": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias