WIP: migrated store and useSvelteFlow hook to signals & solved state propagation for provider
This commit is contained in:
Vendored
+1
@@ -1 +1,2 @@
|
|||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|||||||
@@ -13,24 +13,24 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/kit": "^2.8.0",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
||||||
"@typescript-eslint/parser": "^8.14.0",
|
"@typescript-eslint/parser": "^8.17.0",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.0",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.2.8",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"svelte": "^5.1.16",
|
"svelte": "^5.7.1",
|
||||||
"svelte-check": "^4.0.7",
|
"svelte-check": "^4.1.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^5.4.11"
|
"vite": "^6.0.3"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.1",
|
||||||
"@xyflow/svelte": "workspace:^"
|
"@xyflow/svelte": "workspace:^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,23 +129,7 @@
|
|||||||
target: '6',
|
target: '6',
|
||||||
label: 'hi',
|
label: 'hi',
|
||||||
labelStyle: 'background: red; font-weight: 700; padding: 5px;',
|
labelStyle: 'background: red; font-weight: 700; padding: 5px;',
|
||||||
style: 'stroke: #ffcc0',
|
style: 'stroke: #ffcc0'
|
||||||
markerEnd: {
|
|
||||||
type: MarkerType.Arrow,
|
|
||||||
color: '#FFCC00',
|
|
||||||
markerUnits: 'userSpaceOnUse',
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
strokeWidth: 2
|
|
||||||
},
|
|
||||||
markerStart: {
|
|
||||||
type: MarkerType.ArrowClosed,
|
|
||||||
color: '#FFCC00',
|
|
||||||
orient: 'auto-start-reverse',
|
|
||||||
markerUnits: 'userSpaceOnUse',
|
|
||||||
width: 20,
|
|
||||||
height: 20
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getBezierPath, BaseEdge, type EdgeProps, EdgeLabelRenderer } from '@xyflow/svelte';
|
import {
|
||||||
|
getBezierPath,
|
||||||
|
BaseEdge,
|
||||||
|
type EdgeProps,
|
||||||
|
EdgeLabelRenderer,
|
||||||
|
useSvelteFlow,
|
||||||
|
MarkerType
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
|
||||||
type $$Props = EdgeProps;
|
type $$Props = EdgeProps;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: $$Props['id'];
|
id?: $$Props['id'];
|
||||||
style?: $$Props['style'];
|
style?: $$Props['style'];
|
||||||
@@ -15,7 +20,7 @@
|
|||||||
targetX: $$Props['targetX'];
|
targetX: $$Props['targetX'];
|
||||||
targetY: $$Props['targetY'];
|
targetY: $$Props['targetY'];
|
||||||
targetPosition: $$Props['targetPosition'];
|
targetPosition: $$Props['targetPosition'];
|
||||||
[key: string]: any
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -33,14 +38,18 @@
|
|||||||
|
|
||||||
rest;
|
rest;
|
||||||
|
|
||||||
let [edgePath, labelX, labelY] = $derived(getBezierPath({
|
let [edgePath, labelX, labelY] = $derived(
|
||||||
sourceX,
|
getBezierPath({
|
||||||
sourceY,
|
sourceX,
|
||||||
sourcePosition,
|
sourceY,
|
||||||
targetX,
|
sourcePosition,
|
||||||
targetY,
|
targetX,
|
||||||
targetPosition
|
targetY,
|
||||||
}));
|
targetPosition
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { updateEdge } = useSvelteFlow();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BaseEdge path={edgePath} {markerEnd} {style} />
|
<BaseEdge path={edgePath} {markerEnd} {style} />
|
||||||
@@ -53,7 +62,24 @@
|
|||||||
class="edgeButton"
|
class="edgeButton"
|
||||||
onclick={(event) => {
|
onclick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
alert(`remove ${id}`);
|
updateEdge('e5-6', {
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.Arrow,
|
||||||
|
color: '#FFCC00',
|
||||||
|
markerUnits: 'userSpaceOnUse',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
},
|
||||||
|
markerStart: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: '#FFCC00',
|
||||||
|
orient: 'auto-start-reverse',
|
||||||
|
markerUnits: 'userSpaceOnUse',
|
||||||
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
const nodes = writable<Node[]>(initialNodes);
|
const nodes = writable<Node[]>(initialNodes);
|
||||||
const edges = writable<Edge[]>(initialEdges);
|
const edges = writable<Edge[]>(initialEdges);
|
||||||
|
|
||||||
const { getIntersectingNodes } = useSvelteFlow();
|
const { getIntersectingNodes } = $derived(useSvelteFlow());
|
||||||
|
|
||||||
function onNodeDrag({ detail: { targetNode } }) {
|
function onNodeDrag({ targetNode }) {
|
||||||
if (targetNode) {
|
if (targetNode) {
|
||||||
const intersections = getIntersectingNodes(targetNode).map((n) => n.id);
|
const intersections = getIntersectingNodes(targetNode).map((n) => n.id);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div style="height:100vh;">
|
<div style="height:100vh;">
|
||||||
<SvelteFlow {nodes} {edges} fitView class="intersection-flow" on:nodedrag={onNodeDrag}>
|
<SvelteFlow {nodes} {edges} fitView class="intersection-flow" onnodedrag={onNodeDrag}>
|
||||||
<Background />
|
<Background />
|
||||||
<Controls />
|
<Controls />
|
||||||
</SvelteFlow>
|
</SvelteFlow>
|
||||||
|
|||||||
@@ -56,26 +56,26 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/kit": "^2.8.0",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
"@sveltejs/package": "^2.3.7",
|
"@sveltejs/package": "^2.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
||||||
"@typescript-eslint/parser": "^8.14.0",
|
"@typescript-eslint/parser": "^8.17.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.0",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"postcss-cli": "^11.0.0",
|
"postcss-cli": "^11.0.0",
|
||||||
"postcss-combine-duplicated-selectors": "^10.0.3",
|
"postcss-combine-duplicated-selectors": "^10.0.3",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-nested": "^7.0.2",
|
"postcss-nested": "^7.0.2",
|
||||||
"postcss-rename": "^0.6.1",
|
"postcss-rename": "^0.6.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.2.8",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"svelte": "^5.1.16",
|
"svelte": "^5.7.1",
|
||||||
"svelte-check": "^4.0.7",
|
"svelte-check": "^4.1.1",
|
||||||
"svelte-eslint-parser": "^0.43.0",
|
"svelte-eslint-parser": "^0.43.0",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
|
|||||||
@@ -25,24 +25,24 @@ export default function drag(domNode: Element, params: UseDragParams) {
|
|||||||
onDragStop,
|
onDragStop,
|
||||||
onNodeMouseDown,
|
onNodeMouseDown,
|
||||||
getStoreItems: () => {
|
getStoreItems: () => {
|
||||||
const snapGrid = get(store.snapGrid);
|
const snapGrid = store.snapGrid;
|
||||||
const vp = get(store.viewport);
|
const vp = get(store.viewport);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: get(store.nodes),
|
nodes: get(store.nodes),
|
||||||
nodeLookup: get(store.nodeLookup),
|
nodeLookup: store.nodeLookup,
|
||||||
edges: get(store.edges),
|
edges: get(store.edges),
|
||||||
nodeExtent: get(store.nodeExtent),
|
nodeExtent: store.nodeExtent,
|
||||||
snapGrid: snapGrid ? snapGrid : [0, 0],
|
snapGrid: snapGrid ? snapGrid : [0, 0],
|
||||||
snapToGrid: !!snapGrid,
|
snapToGrid: !!snapGrid,
|
||||||
nodeOrigin: get(store.nodeOrigin),
|
nodeOrigin: store.nodeOrigin,
|
||||||
multiSelectionActive: get(store.multiselectionKeyPressed),
|
multiSelectionActive: store.multiselectionKeyPressed,
|
||||||
domNode: get(store.domNode),
|
domNode: store.domNode,
|
||||||
transform: [vp.x, vp.y, vp.zoom],
|
transform: [vp.x, vp.y, vp.zoom],
|
||||||
autoPanOnNodeDrag: get(store.autoPanOnNodeDrag),
|
autoPanOnNodeDrag: store.autoPanOnNodeDrag,
|
||||||
nodesDraggable: get(store.nodesDraggable),
|
nodesDraggable: store.nodesDraggable,
|
||||||
selectNodesOnDrag: get(store.selectNodesOnDrag),
|
selectNodesOnDrag: store.selectNodesOnDrag,
|
||||||
nodeDragThreshold: get(store.nodeDragThreshold),
|
nodeDragThreshold: store.nodeDragThreshold,
|
||||||
unselectNodesAndEdges: store.unselectNodesAndEdges,
|
unselectNodesAndEdges: store.unselectNodesAndEdges,
|
||||||
updateNodePositions: store.updateNodePositions,
|
updateNodePositions: store.updateNodePositions,
|
||||||
panBy: store.panBy
|
panBy: store.panBy
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ type ZoomParams = {
|
|||||||
initialViewport: Viewport;
|
initialViewport: Viewport;
|
||||||
minZoom: number;
|
minZoom: number;
|
||||||
maxZoom: number;
|
maxZoom: number;
|
||||||
dragging: Writable<boolean>;
|
setPanZoomInstance: (panZoomInstance: PanZoomInstance) => void;
|
||||||
onPanZoomStart?: OnPanZoom;
|
onPanZoomStart?: OnPanZoom;
|
||||||
onPanZoom?: OnPanZoom;
|
onPanZoom?: OnPanZoom;
|
||||||
onPanZoomEnd?: OnPanZoom;
|
onPanZoomEnd?: OnPanZoom;
|
||||||
onPaneContextMenu?: (event: MouseEvent) => void;
|
onPaneContextMenu?: (event: MouseEvent) => void;
|
||||||
translateExtent: CoordinateExtent;
|
translateExtent: CoordinateExtent;
|
||||||
panZoom: Writable<PanZoomInstance | null>;
|
|
||||||
zoomOnScroll: boolean;
|
zoomOnScroll: boolean;
|
||||||
zoomOnPinch: boolean;
|
zoomOnPinch: boolean;
|
||||||
zoomOnDoubleClick: boolean;
|
zoomOnDoubleClick: boolean;
|
||||||
@@ -38,18 +37,19 @@ type ZoomParams = {
|
|||||||
lib: string;
|
lib: string;
|
||||||
paneClickDistance: number;
|
paneClickDistance: number;
|
||||||
onTransformChange: (transform: Transform) => void;
|
onTransformChange: (transform: Transform) => void;
|
||||||
|
onDraggingChange: (dragging: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function zoom(domNode: Element, params: ZoomParams) {
|
export default function zoom(domNode: Element, params: ZoomParams) {
|
||||||
const {
|
const {
|
||||||
panZoom,
|
|
||||||
minZoom,
|
minZoom,
|
||||||
maxZoom,
|
maxZoom,
|
||||||
initialViewport,
|
initialViewport,
|
||||||
viewport,
|
viewport,
|
||||||
dragging,
|
|
||||||
translateExtent,
|
translateExtent,
|
||||||
paneClickDistance
|
paneClickDistance,
|
||||||
|
setPanZoomInstance,
|
||||||
|
onDraggingChange
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const panZoomInstance = XYPanZoom({
|
const panZoomInstance = XYPanZoom({
|
||||||
@@ -59,11 +59,11 @@ export default function zoom(domNode: Element, params: ZoomParams) {
|
|||||||
translateExtent,
|
translateExtent,
|
||||||
viewport: initialViewport,
|
viewport: initialViewport,
|
||||||
paneClickDistance,
|
paneClickDistance,
|
||||||
onDraggingChange: dragging.set
|
onDraggingChange
|
||||||
});
|
});
|
||||||
const currentViewport = panZoomInstance.getViewport();
|
const currentViewport = panZoomInstance.getViewport();
|
||||||
viewport.set(currentViewport);
|
viewport.set(currentViewport);
|
||||||
panZoom.set(panZoomInstance);
|
setPanZoomInstance(panZoomInstance);
|
||||||
|
|
||||||
panZoomInstance.update(params);
|
panZoomInstance.update(params);
|
||||||
|
|
||||||
|
|||||||
@@ -9,35 +9,37 @@
|
|||||||
getStraightPath
|
getStraightPath
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
containerStyle = '',
|
containerStyle = '',
|
||||||
style = '',
|
style = '',
|
||||||
connectionLine
|
connectionLine
|
||||||
}: {
|
}: {
|
||||||
|
store: SvelteFlowStore;
|
||||||
containerStyle: string;
|
containerStyle: string;
|
||||||
style: string;
|
style: string;
|
||||||
connectionLine?: Snippet;
|
connectionLine?: Snippet;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const { width, height, connection, connectionLineType } = useStore();
|
// $inspect(store.connection);
|
||||||
|
|
||||||
let path = $derived.by(() => {
|
let path = $derived.by(() => {
|
||||||
if (!$connection.inProgress) {
|
if (!store.connection.inProgress) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathParams = {
|
const pathParams = {
|
||||||
sourceX: $connection.from.x,
|
sourceX: store.connection.from.x,
|
||||||
sourceY: $connection.from.y,
|
sourceY: store.connection.from.y,
|
||||||
sourcePosition: $connection.fromPosition,
|
sourcePosition: store.connection.fromPosition,
|
||||||
targetX: $connection.to.x,
|
targetX: store.connection.to.x,
|
||||||
targetY: $connection.to.y,
|
targetY: store.connection.to.y,
|
||||||
targetPosition: $connection.toPosition
|
targetPosition: store.connection.toPosition
|
||||||
};
|
};
|
||||||
|
|
||||||
switch ($connectionLineType) {
|
switch (store.connectionLineType) {
|
||||||
case ConnectionLineType.Bezier: {
|
case ConnectionLineType.Bezier: {
|
||||||
const [path] = getBezierPath(pathParams);
|
const [path] = getBezierPath(pathParams);
|
||||||
return path;
|
return path;
|
||||||
@@ -50,7 +52,7 @@
|
|||||||
case ConnectionLineType.SmoothStep: {
|
case ConnectionLineType.SmoothStep: {
|
||||||
const [path] = getSmoothStepPath({
|
const [path] = getSmoothStepPath({
|
||||||
...pathParams,
|
...pathParams,
|
||||||
borderRadius: $connectionLineType === ConnectionLineType.Step ? 0 : undefined
|
borderRadius: store.connectionLineType === ConnectionLineType.Step ? 0 : undefined
|
||||||
});
|
});
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -58,9 +60,14 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $connection.inProgress}
|
{#if store.connection.inProgress}
|
||||||
<svg width={$width} height={$height} class="svelte-flow__connectionline" style={containerStyle}>
|
<svg
|
||||||
<g class={cc(['svelte-flow__connection', getConnectionStatus($connection.isValid)])}>
|
width={store.width}
|
||||||
|
height={store.height}
|
||||||
|
class="svelte-flow__connectionline"
|
||||||
|
style={containerStyle}
|
||||||
|
>
|
||||||
|
<g class={cc(['svelte-flow__connection', getConnectionStatus(store.connection.isValid)])}>
|
||||||
{#if connectionLine}
|
{#if connectionLine}
|
||||||
{@render connectionLine()}
|
{@render connectionLine()}
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
const { domNode } = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
let { children }: { children?: Snippet } = $props();
|
let { children }: { children?: Snippet } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:portal={{ target: '.svelte-flow__edgelabel-renderer', domNode: $domNode }}>
|
<div use:portal={{ target: '.svelte-flow__edgelabel-renderer', domNode: store.domNode }}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
import { useHandleEdgeSelect } from '$lib/hooks/useHandleEdgeSelect';
|
import { useHandleEdgeSelect } from '$lib/hooks/useHandleEdgeSelect';
|
||||||
|
|
||||||
import type { EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
|
import type { EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
|
||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
type = 'default',
|
type = 'default',
|
||||||
|
store,
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
data = {},
|
data = {},
|
||||||
@@ -42,24 +44,24 @@
|
|||||||
onedgecontextmenu,
|
onedgecontextmenu,
|
||||||
onedgemouseenter,
|
onedgemouseenter,
|
||||||
onedgemouseleave
|
onedgemouseleave
|
||||||
}: EdgeLayouted & EdgeEvents = $props();
|
}: { store: SvelteFlowStore } & EdgeLayouted & EdgeEvents = $props();
|
||||||
|
|
||||||
setContext('svelteflow__edge_id', id);
|
setContext('svelteflow__edge_id', id);
|
||||||
|
|
||||||
const { edgeLookup, edgeTypes, flowId, elementsSelectable } = useStore();
|
|
||||||
|
|
||||||
let edgeType = $derived(type ?? 'default');
|
let edgeType = $derived(type ?? 'default');
|
||||||
let EdgeComponent = $derived($edgeTypes[edgeType] ?? BezierEdgeInternal);
|
let EdgeComponent = $derived(store.edgeTypes[edgeType] ?? BezierEdgeInternal);
|
||||||
let markerStartUrl = $derived(
|
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 markerEndUrl = $derived(
|
||||||
let isSelectable = $derived(selectable ?? $elementsSelectable);
|
markerEnd ? `url('#${getMarkerId(markerEnd, store.flowId)}')` : undefined
|
||||||
|
);
|
||||||
|
let isSelectable = $derived(selectable ?? store.elementsSelectable);
|
||||||
|
|
||||||
const handleEdgeSelect = useHandleEdgeSelect();
|
const handleEdgeSelect = useHandleEdgeSelect();
|
||||||
|
|
||||||
function onclick(event: MouseEvent | TouchEvent) {
|
function onclick(event: MouseEvent | TouchEvent) {
|
||||||
const edge = $edgeLookup.get(id);
|
const edge = store.edgeLookup.get(id);
|
||||||
|
|
||||||
if (edge) {
|
if (edge) {
|
||||||
handleEdgeSelect(id);
|
handleEdgeSelect(id);
|
||||||
@@ -71,7 +73,7 @@
|
|||||||
event: MouseEvent,
|
event: MouseEvent,
|
||||||
callback: ({ edge, event }: { edge: Edge; event: MouseEvent }) => void
|
callback: ({ edge, event }: { edge: Edge; event: MouseEvent }) => void
|
||||||
) {
|
) {
|
||||||
const edge = $edgeLookup.get(id);
|
const edge = store.edgeLookup.get(id);
|
||||||
|
|
||||||
if (edge) {
|
if (edge) {
|
||||||
callback({ event, edge });
|
callback({ event, edge });
|
||||||
|
|||||||
@@ -39,28 +39,8 @@
|
|||||||
isConnectableProp !== undefined ? isConnectableProp : isConnectableContext.value
|
isConnectableProp !== undefined ? isConnectableProp : isConnectableContext.value
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
let store = useStore();
|
||||||
connectionMode,
|
let { viewport, edges } = store;
|
||||||
domNode,
|
|
||||||
nodeLookup,
|
|
||||||
connectionRadius,
|
|
||||||
viewport,
|
|
||||||
isValidConnection: isValidConnectionStore,
|
|
||||||
lib,
|
|
||||||
addEdge,
|
|
||||||
onedgecreate,
|
|
||||||
panBy,
|
|
||||||
cancelConnection,
|
|
||||||
updateConnection,
|
|
||||||
autoPanOnConnect,
|
|
||||||
edges,
|
|
||||||
connectionLookup,
|
|
||||||
onconnect: onConnectAction,
|
|
||||||
onconnectstart: onConnectStartAction,
|
|
||||||
onconnectend: onConnectEndAction,
|
|
||||||
flowId,
|
|
||||||
connection
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
function onPointerDown(event: MouseEvent | TouchEvent) {
|
function onPointerDown(event: MouseEvent | TouchEvent) {
|
||||||
const isMouseTriggered = isMouseEvent(event);
|
const isMouseTriggered = isMouseEvent(event);
|
||||||
@@ -70,39 +50,39 @@
|
|||||||
handleId,
|
handleId,
|
||||||
nodeId,
|
nodeId,
|
||||||
isTarget,
|
isTarget,
|
||||||
connectionRadius: $connectionRadius,
|
connectionRadius: store.connectionRadius,
|
||||||
domNode: $domNode,
|
domNode: store.domNode,
|
||||||
nodeLookup: $nodeLookup,
|
nodeLookup: store.nodeLookup,
|
||||||
connectionMode: $connectionMode,
|
connectionMode: store.connectionMode,
|
||||||
lib: $lib,
|
lib: 'svelte',
|
||||||
autoPanOnConnect: $autoPanOnConnect,
|
autoPanOnConnect: store.autoPanOnConnect,
|
||||||
flowId: $flowId,
|
flowId: store.flowId,
|
||||||
isValidConnection: isValidConnectionProp ?? $isValidConnectionStore,
|
isValidConnection: isValidConnectionProp ?? store.isValidConnection,
|
||||||
updateConnection,
|
updateConnection: store.updateConnection,
|
||||||
cancelConnection,
|
cancelConnection: store.cancelConnection,
|
||||||
panBy,
|
panBy: store.panBy,
|
||||||
onConnect: (connection) => {
|
onConnect: (connection) => {
|
||||||
const edge = $onedgecreate ? $onedgecreate(connection) : connection;
|
const edge = store.onedgecreate ? store.onedgecreate(connection) : connection;
|
||||||
|
|
||||||
if (!edge) {
|
if (!edge) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEdge(edge);
|
store.addEdge(edge);
|
||||||
$onConnectAction?.(connection);
|
store.onconnect?.(connection);
|
||||||
},
|
},
|
||||||
onConnectStart: (event, startParams) => {
|
onConnectStart: (event, startParams) => {
|
||||||
$onConnectStartAction?.(event, {
|
store.onconnectstart?.(event, {
|
||||||
nodeId: startParams.nodeId,
|
nodeId: startParams.nodeId,
|
||||||
handleId: startParams.handleId,
|
handleId: startParams.handleId,
|
||||||
handleType: startParams.handleType
|
handleType: startParams.handleType
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onConnectEnd: (event, connectionState) => {
|
onConnectEnd: (event, connectionState) => {
|
||||||
$onConnectEndAction?.(event, connectionState);
|
store.onconnectend?.(event, connectionState);
|
||||||
},
|
},
|
||||||
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
|
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
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
$edges;
|
$edges;
|
||||||
if (onconnect || ondisconnect) {
|
if (onconnect || ondisconnect) {
|
||||||
let connections = $connectionLookup.get(`${nodeId}-${type}-${handleId}`);
|
let connections = store.connectionLookup.get(`${nodeId}-${type}-${handleId}`);
|
||||||
|
|
||||||
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
|
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
|
||||||
const _connections = connections ?? new Map();
|
const _connections = connections ?? new Map();
|
||||||
@@ -128,7 +108,7 @@
|
|||||||
|
|
||||||
let [connectionInProcess, connectingFrom, connectingTo, isPossibleEndHandle, valid] = $derived.by(
|
let [connectionInProcess, connectingFrom, connectingTo, isPossibleEndHandle, valid] = $derived.by(
|
||||||
() => {
|
() => {
|
||||||
const { fromHandle, toHandle, isValid } = $connection;
|
const { fromHandle, toHandle, isValid } = store.connection;
|
||||||
|
|
||||||
const connectionInProcess = !!fromHandle;
|
const connectionInProcess = !!fromHandle;
|
||||||
|
|
||||||
@@ -139,7 +119,7 @@
|
|||||||
toHandle?.nodeId === nodeId && toHandle?.type === type && toHandle?.id === handleId;
|
toHandle?.nodeId === nodeId && toHandle?.type === type && toHandle?.id === handleId;
|
||||||
|
|
||||||
const isPossibleEndHandle =
|
const isPossibleEndHandle =
|
||||||
$connectionMode === ConnectionMode.Strict
|
store.connectionMode === ConnectionMode.Strict
|
||||||
? fromHandle?.type !== type
|
? fromHandle?.type !== type
|
||||||
: nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id;
|
: 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-handleid={handleId}
|
||||||
data-nodeid={nodeId}
|
data-nodeid={nodeId}
|
||||||
data-handlepos={position}
|
data-handlepos={position}
|
||||||
data-id="{$flowId}-{nodeId}-{handleId}-{type}"
|
data-id="{store.flowId}-{nodeId}-{handleId}-{type}"
|
||||||
class={cc([
|
class={cc([
|
||||||
'svelte-flow__handle',
|
'svelte-flow__handle',
|
||||||
`svelte-flow__handle-${position}`,
|
`svelte-flow__handle-${position}`,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
|
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
selectionKey = 'Shift',
|
selectionKey = 'Shift',
|
||||||
multiSelectionKey = isMacOs() ? 'Meta' : 'Control',
|
multiSelectionKey = isMacOs() ? 'Meta' : 'Control',
|
||||||
deleteKey = 'Backspace',
|
deleteKey = 'Backspace',
|
||||||
@@ -19,18 +20,7 @@
|
|||||||
zoomActivationKey = isMacOs() ? 'Meta' : 'Control'
|
zoomActivationKey = isMacOs() ? 'Meta' : 'Control'
|
||||||
}: KeyHandlerProps = $props();
|
}: KeyHandlerProps = $props();
|
||||||
|
|
||||||
const {
|
const { nodes: _nodes, edges: _edges } = store;
|
||||||
selectionKeyPressed,
|
|
||||||
multiselectionKeyPressed,
|
|
||||||
deleteKeyPressed,
|
|
||||||
panActivationKeyPressed,
|
|
||||||
zoomActivationKeyPressed,
|
|
||||||
selectionRect,
|
|
||||||
onbeforedelete,
|
|
||||||
ondelete,
|
|
||||||
nodes: _nodes,
|
|
||||||
edges: _edges
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
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';
|
||||||
@@ -67,12 +57,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetKeysAndSelection() {
|
function resetKeysAndSelection() {
|
||||||
selectionRect.set(null);
|
store.selectionRect = null;
|
||||||
selectionKeyPressed.set(false);
|
store.selectionKeyPressed = false;
|
||||||
multiselectionKeyPressed.set(false);
|
store.multiselectionKeyPressed = false;
|
||||||
deleteKeyPressed.set(false);
|
store.deleteKeyPressed = false;
|
||||||
panActivationKeyPressed.set(false);
|
store.panActivationKeyPressed = false;
|
||||||
zoomActivationKeyPressed.set(false);
|
store.zoomActivationKeyPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
@@ -86,14 +76,14 @@
|
|||||||
edgesToRemove: selectedEdges,
|
edgesToRemove: selectedEdges,
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
onBeforeDelete: get(onbeforedelete)
|
onBeforeDelete: store.onbeforedelete
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matchingNodes.length || matchingEdges.length) {
|
if (matchingNodes.length || matchingEdges.length) {
|
||||||
_nodes.update((nds) => nds.filter((node) => !matchingNodes.some((mN) => mN.id === node.id)));
|
_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)));
|
_edges.update((eds) => eds.filter((edge) => !matchingEdges.some((mE) => mE.id === edge.id)));
|
||||||
|
|
||||||
get(ondelete)?.({
|
store.ondelete?.({
|
||||||
nodes: matchingNodes,
|
nodes: matchingNodes,
|
||||||
edges: matchingEdges
|
edges: matchingEdges
|
||||||
});
|
});
|
||||||
@@ -105,19 +95,19 @@
|
|||||||
on:blur={resetKeysAndSelection}
|
on:blur={resetKeysAndSelection}
|
||||||
on:contextmenu={resetKeysAndSelection}
|
on:contextmenu={resetKeysAndSelection}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(true)),
|
trigger: getShortcutTrigger(selectionKey, () => (store.selectionKeyPressed = true)),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(false)),
|
trigger: getShortcutTrigger(selectionKey, () => (store.selectionKeyPressed = false)),
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(true)),
|
trigger: getShortcutTrigger(multiSelectionKey, () => (store.multiselectionKeyPressed = true)),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(false)),
|
trigger: getShortcutTrigger(multiSelectionKey, () => (store.multiselectionKeyPressed = false)),
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
@@ -127,30 +117,30 @@
|
|||||||
detail.originalEvent.metaKey ||
|
detail.originalEvent.metaKey ||
|
||||||
detail.originalEvent.shiftKey;
|
detail.originalEvent.shiftKey;
|
||||||
if (!isModifierKey && !isInputDOMNode(detail.originalEvent)) {
|
if (!isModifierKey && !isInputDOMNode(detail.originalEvent)) {
|
||||||
deleteKeyPressed.set(true);
|
store.deleteKeyPressed = true;
|
||||||
handleDelete();
|
handleDelete();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(deleteKey, () => deleteKeyPressed.set(false)),
|
trigger: getShortcutTrigger(deleteKey, () => (store.deleteKeyPressed = false)),
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(true)),
|
trigger: getShortcutTrigger(panActivationKey, () => (store.panActivationKeyPressed = true)),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(false)),
|
trigger: getShortcutTrigger(panActivationKey, () => (store.panActivationKeyPressed = false)),
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(true)),
|
trigger: getShortcutTrigger(zoomActivationKey, () => (store.zoomActivationKeyPressed = true)),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(false)),
|
trigger: getShortcutTrigger(zoomActivationKey, () => (store.zoomActivationKeyPressed = false)),
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import type { KeyDefinition } from '$lib/types';
|
import type { KeyDefinition } from '$lib/types';
|
||||||
|
|
||||||
export type KeyHandlerProps = {
|
export type KeyHandlerProps = {
|
||||||
|
store: SvelteFlowStore;
|
||||||
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,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system';
|
import { getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
|
||||||
import { Selection } from '$lib/components/Selection';
|
import { Selection } from '$lib/components/Selection';
|
||||||
import drag from '$lib/actions/drag';
|
import drag from '$lib/actions/drag';
|
||||||
|
|
||||||
import type { NodeSelectionProps } from './types';
|
import type { NodeSelectionProps } from './types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
onnodedrag,
|
onnodedrag,
|
||||||
onnodedragstart,
|
onnodedragstart,
|
||||||
onnodedragstop,
|
onnodedragstop,
|
||||||
@@ -15,14 +15,13 @@
|
|||||||
onselectioncontextmenu
|
onselectioncontextmenu
|
||||||
}: NodeSelectionProps = $props();
|
}: NodeSelectionProps = $props();
|
||||||
|
|
||||||
const store = useStore();
|
const { nodes } = store;
|
||||||
const { selectionRectMode, nodes, nodeLookup } = store;
|
|
||||||
|
|
||||||
let bounds: Rect | null = $derived.by(() => {
|
let bounds: Rect | null = $derived.by(() => {
|
||||||
if ($selectionRectMode === 'nodes') {
|
if (store.selectionRectMode === 'nodes') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
$nodes;
|
$nodes;
|
||||||
return getInternalNodesBounds($nodeLookup, { filter: (node) => !!node.selected });
|
return getInternalNodesBounds(store.nodeLookup, { filter: (node) => !!node.selected });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
|
{#if store.selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
|
||||||
<div
|
<div
|
||||||
class="selection-wrapper nopan"
|
class="selection-wrapper nopan"
|
||||||
style="width: {bounds.width}px; height: {bounds.height}px; transform: translate({bounds.x}px, {bounds.y}px)"
|
style="width: {bounds.width}px; height: {bounds.height}px; transform: translate({bounds.x}px, {bounds.y}px)"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import type { NodeEvents, NodeSelectionEvents } from '$lib/types';
|
import type { NodeEvents, NodeSelectionEvents } from '$lib/types';
|
||||||
|
|
||||||
export type NodeSelectionProps = NodeSelectionEvents &
|
export type NodeSelectionProps = { store: SvelteFlowStore } & NodeSelectionEvents &
|
||||||
Pick<NodeEvents, 'onnodedrag' | 'onnodedragstart' | 'onnodedragstop'>;
|
Pick<NodeEvents, 'onnodedrag' | 'onnodedragstart' | 'onnodedragstop'>;
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, onDestroy } from 'svelte';
|
import { setContext, onDestroy } from 'svelte';
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { errorMessages, Position } from '@xyflow/system';
|
import { errorMessages, Position } from '@xyflow/system';
|
||||||
|
|
||||||
import drag from '$lib/actions/drag';
|
import drag from '$lib/actions/drag';
|
||||||
import { useStore } from '$lib/store';
|
|
||||||
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 { getNodeInlineStyleDimensions } from './utils';
|
import { getNodeInlineStyleDimensions } from './utils';
|
||||||
import type { NodeEvents } from '$lib/types';
|
import type { NodeEvents } from '$lib/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
node,
|
node,
|
||||||
id,
|
id,
|
||||||
data = {},
|
data = {},
|
||||||
@@ -52,15 +51,6 @@
|
|||||||
onnodecontextmenu
|
onnodecontextmenu
|
||||||
}: NodeWrapperProps & NodeEvents = $props();
|
}: NodeWrapperProps & NodeEvents = $props();
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
const {
|
|
||||||
nodeTypes,
|
|
||||||
nodeDragThreshold,
|
|
||||||
selectNodesOnDrag,
|
|
||||||
handleNodeSelection,
|
|
||||||
updateNodeInternals
|
|
||||||
} = store;
|
|
||||||
|
|
||||||
let nodeRef: HTMLDivElement | null = $state(null);
|
let nodeRef: HTMLDivElement | null = $state(null);
|
||||||
let prevNodeRef: HTMLDivElement | null = null;
|
let prevNodeRef: HTMLDivElement | null = null;
|
||||||
|
|
||||||
@@ -68,7 +58,7 @@
|
|||||||
let prevSourcePosition: Position | undefined;
|
let prevSourcePosition: Position | undefined;
|
||||||
let prevTargetPosition: Position | undefined;
|
let prevTargetPosition: Position | undefined;
|
||||||
|
|
||||||
let NodeComponent = $derived($nodeTypes[type] ?? DefaultNode);
|
let NodeComponent = $derived(store.nodeTypes[type] ?? DefaultNode);
|
||||||
|
|
||||||
let connectableContext: ConnectableContext = {
|
let connectableContext: ConnectableContext = {
|
||||||
get value() {
|
get value() {
|
||||||
@@ -80,7 +70,7 @@
|
|||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const valid = !!$nodeTypes[type];
|
const valid = !!store.nodeTypes[type];
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
console.warn('003', errorMessages['error003'](type!));
|
console.warn('003', errorMessages['error003'](type!));
|
||||||
}
|
}
|
||||||
@@ -109,7 +99,7 @@
|
|||||||
if (doUpdate && nodeRef !== null) {
|
if (doUpdate && nodeRef !== null) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (nodeRef !== null) {
|
if (nodeRef !== null) {
|
||||||
updateNodeInternals(
|
store.updateNodeInternals(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
id,
|
id,
|
||||||
@@ -148,10 +138,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
||||||
if (selectable && (!get(selectNodesOnDrag) || !draggable || get(nodeDragThreshold) > 0)) {
|
if (selectable && (!store.selectNodesOnDrag || !draggable || store.nodeDragThreshold > 0)) {
|
||||||
// this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
// this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
||||||
// here we only need to call it when selectNodesOnDrag=false
|
// here we only need to call it when selectNodesOnDrag=false
|
||||||
handleNodeSelection(id);
|
store.handleNodeSelection(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
onnodeclick?.({ node: node.internals.userNode, event });
|
onnodeclick?.({ node: node.internals.userNode, event });
|
||||||
@@ -169,7 +159,7 @@
|
|||||||
handleSelector: dragHandle,
|
handleSelector: dragHandle,
|
||||||
noDragClass: 'nodrag',
|
noDragClass: 'nodrag',
|
||||||
nodeClickDistance,
|
nodeClickDistance,
|
||||||
onNodeMouseDown: handleNodeSelection,
|
onNodeMouseDown: store.handleNodeSelection,
|
||||||
onDrag: (event, _, targetNode, nodes) => {
|
onDrag: (event, _, targetNode, nodes) => {
|
||||||
onnodedrag?.({ event, targetNode, nodes });
|
onnodedrag?.({ event, targetNode, nodes });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import type { InternalNode, Node } from '$lib/types';
|
import type { InternalNode, Node } from '$lib/types';
|
||||||
|
|
||||||
export type ConnectableContext = {
|
export type ConnectableContext = {
|
||||||
@@ -39,4 +40,5 @@ export type NodeWrapperProps = Pick<
|
|||||||
node: InternalNode;
|
node: InternalNode;
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
nodeClickDistance?: number;
|
nodeClickDistance?: number;
|
||||||
|
store: SvelteFlowStore;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,29 +3,21 @@
|
|||||||
|
|
||||||
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';
|
||||||
|
|
||||||
let {
|
let { children }: SvelteFlowProviderProps = $props();
|
||||||
initialNodes,
|
|
||||||
initialEdges,
|
|
||||||
initialWidth,
|
|
||||||
initialHeight,
|
|
||||||
nodeOrigin,
|
|
||||||
fitView,
|
|
||||||
children
|
|
||||||
}: SvelteFlowProviderProps = $props();
|
|
||||||
|
|
||||||
const store = createStore({
|
let store = $state.raw(createStore({ props: {} }));
|
||||||
nodes: initialNodes,
|
|
||||||
edges: initialEdges,
|
|
||||||
width: initialWidth,
|
|
||||||
height: initialHeight,
|
|
||||||
nodeOrigin,
|
|
||||||
fitView
|
|
||||||
});
|
|
||||||
|
|
||||||
setContext(key, {
|
setContext(key, {
|
||||||
getStore: () => store
|
provider: true,
|
||||||
});
|
getStore() {
|
||||||
|
return store;
|
||||||
|
},
|
||||||
|
setStore: (newStore: SvelteFlowStore) => {
|
||||||
|
store = newStore;
|
||||||
|
}
|
||||||
|
} satisfies ProviderContext);
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
store.reset();
|
store.reset();
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { Selection } from '$lib/components/Selection';
|
import { Selection } from '$lib/components/Selection';
|
||||||
|
|
||||||
const { selectionRect, selectionRectMode } = useStore();
|
const store = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Selection
|
<Selection
|
||||||
isVisible={!!($selectionRect && $selectionRectMode === 'user')}
|
isVisible={!!(store.selectionRect && store.selectionRectMode === 'user')}
|
||||||
width={$selectionRect?.width}
|
width={store.selectionRect?.width}
|
||||||
height={$selectionRect?.height}
|
height={store.selectionRect?.height}
|
||||||
x={$selectionRect?.x}
|
x={store.selectionRect?.x}
|
||||||
y={$selectionRect?.y}
|
y={store.selectionRect?.y}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
let { children }: { children?: Snippet } = $props();
|
let { children }: { children?: Snippet } = $props();
|
||||||
|
|
||||||
const { domNode } = useStore();
|
const store = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:portal={{ target: '.svelte-flow__viewport-portal', domNode: $domNode }}>
|
<div use:portal={{ target: '.svelte-flow__viewport-portal', domNode: store.domNode }}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,29 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { EdgeWrapper } from '$lib/components/EdgeWrapper';
|
import { EdgeWrapper } from '$lib/components/EdgeWrapper';
|
||||||
import { CallOnMount } from '$lib/components/CallOnMount';
|
import { CallOnMount } from '$lib/components/CallOnMount';
|
||||||
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
||||||
import { useStore } from '$lib/store';
|
import type { EdgeEvents, EdgeLayouted } from '$lib/types';
|
||||||
import type { DefaultEdgeOptions, EdgeEvents } from '$lib/types';
|
import { getEdgePosition, getElevatedEdgeZIndex } from '@xyflow/system';
|
||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
defaultEdgeOptions,
|
store,
|
||||||
onedgeclick,
|
onedgeclick,
|
||||||
onedgecontextmenu,
|
onedgecontextmenu,
|
||||||
onedgemouseenter,
|
onedgemouseenter,
|
||||||
onedgemouseleave
|
onedgemouseleave
|
||||||
}: { defaultEdgeOptions?: DefaultEdgeOptions } & EdgeEvents = $props();
|
}: { store: SvelteFlowStore } & EdgeEvents = $props();
|
||||||
|
|
||||||
const {
|
const { edges, nodes } = store;
|
||||||
visibleEdges,
|
|
||||||
edgesInitialized,
|
|
||||||
edges: { setDefaultOptions },
|
|
||||||
elementsSelectable
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
onMount(() => {
|
let layoutedEdges = $derived.by(() => {
|
||||||
// TODO: Remove this when we have a better way to set default options
|
$nodes;
|
||||||
if (defaultEdgeOptions) setDefaultOptions(defaultEdgeOptions);
|
const layoutedEdges = $edges.reduce<EdgeLayouted[]>((res, edge) => {
|
||||||
|
const sourceNode = store.nodeLookup.get(edge.source);
|
||||||
|
const targetNode = store.nodeLookup.get(edge.target);
|
||||||
|
|
||||||
|
if (!sourceNode || !targetNode) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const edgePosition = getEdgePosition({
|
||||||
|
id: edge.id,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
sourceHandle: edge.sourceHandle || null,
|
||||||
|
targetHandle: edge.targetHandle || null,
|
||||||
|
connectionMode: store.connectionMode,
|
||||||
|
onError: store.onerror
|
||||||
|
});
|
||||||
|
|
||||||
|
if (edgePosition) {
|
||||||
|
res.push({
|
||||||
|
...edge,
|
||||||
|
zIndex: getElevatedEdgeZIndex({
|
||||||
|
selected: edge.selected,
|
||||||
|
zIndex: edge.zIndex,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
elevateOnSelect: false
|
||||||
|
}),
|
||||||
|
...edgePosition
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return layoutedEdges;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -32,8 +62,9 @@
|
|||||||
<MarkerDefinition />
|
<MarkerDefinition />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{#each $visibleEdges as edge (edge.id)}
|
{#each layoutedEdges as edge (edge.id)}
|
||||||
<EdgeWrapper
|
<EdgeWrapper
|
||||||
|
{store}
|
||||||
id={edge.id}
|
id={edge.id}
|
||||||
source={edge.source}
|
source={edge.source}
|
||||||
target={edge.target}
|
target={edge.target}
|
||||||
@@ -41,7 +72,7 @@
|
|||||||
style={edge.style}
|
style={edge.style}
|
||||||
animated={edge.animated}
|
animated={edge.animated}
|
||||||
selected={edge.selected}
|
selected={edge.selected}
|
||||||
selectable={edge.selectable ?? $elementsSelectable}
|
selectable={edge.selectable ?? store.elementsSelectable}
|
||||||
deletable={edge.deletable}
|
deletable={edge.deletable}
|
||||||
hidden={edge.hidden}
|
hidden={edge.hidden}
|
||||||
label={edge.label}
|
label={edge.label}
|
||||||
@@ -68,13 +99,13 @@
|
|||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if $visibleEdges.length > 0}
|
{#if layoutedEdges.length > 0}
|
||||||
<CallOnMount
|
<CallOnMount
|
||||||
onMount={() => {
|
onMount={() => {
|
||||||
$edgesInitialized = true;
|
store.edgesInitialized = true;
|
||||||
}}
|
}}
|
||||||
onDestroy={() => {
|
onDestroy={() => {
|
||||||
$edgesInitialized = false;
|
store.edgesInitialized = false;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
+2
-2
@@ -2,11 +2,11 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { Marker } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
import { Marker } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
||||||
|
|
||||||
const { markers } = useStore();
|
const store = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
{#each $markers as marker (marker.id)}
|
{#each store.markers as marker (marker.id)}
|
||||||
<Marker {...marker} />
|
<Marker {...marker} />
|
||||||
{/each}
|
{/each}
|
||||||
</defs>
|
</defs>
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
|
|
||||||
import type { NodeEvents } from '$lib/types';
|
import type { NodeEvents } from '$lib/types';
|
||||||
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
||||||
import { useStore } from '$lib/store';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
nodeClickDistance,
|
nodeClickDistance,
|
||||||
onnodeclick,
|
onnodeclick,
|
||||||
onnodecontextmenu,
|
onnodecontextmenu,
|
||||||
@@ -16,16 +17,9 @@
|
|||||||
onnodedrag,
|
onnodedrag,
|
||||||
onnodedragstart,
|
onnodedragstart,
|
||||||
onnodedragstop
|
onnodedragstop
|
||||||
}: { nodeClickDistance?: number } & NodeEvents = $props();
|
}: { store: SvelteFlowStore; nodeClickDistance?: number } & NodeEvents = $props();
|
||||||
|
|
||||||
const {
|
const { nodes } = store;
|
||||||
visibleNodes,
|
|
||||||
nodesDraggable,
|
|
||||||
nodesConnectable,
|
|
||||||
elementsSelectable,
|
|
||||||
updateNodeInternals,
|
|
||||||
parentLookup
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
const resizeObserver: ResizeObserver | null =
|
const resizeObserver: ResizeObserver | null =
|
||||||
typeof ResizeObserver === 'undefined'
|
typeof ResizeObserver === 'undefined'
|
||||||
@@ -43,7 +37,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateNodeInternals(updates);
|
store.updateNodeInternals(updates);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -52,41 +46,46 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="svelte-flow__nodes">
|
<div class="svelte-flow__nodes">
|
||||||
{#each $visibleNodes as node (node.id)}
|
{#each $nodes as node (node.id)}
|
||||||
|
{@const internalNode = store.nodeLookup.get(node.id)}
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
{node}
|
{store}
|
||||||
|
node={internalNode!}
|
||||||
id={node.id}
|
id={node.id}
|
||||||
data={node.data}
|
data={node.data}
|
||||||
selected={!!node.selected}
|
selected={!!node.selected}
|
||||||
hidden={!!node.hidden}
|
hidden={!!node.hidden}
|
||||||
draggable={!!(node.draggable || ($nodesDraggable && typeof node.draggable === 'undefined'))}
|
draggable={!!(
|
||||||
|
node.draggable ||
|
||||||
|
(store.nodesDraggable && typeof node.draggable === 'undefined')
|
||||||
|
)}
|
||||||
selectable={!!(
|
selectable={!!(
|
||||||
node.selectable ||
|
node.selectable ||
|
||||||
($elementsSelectable && typeof node.selectable === 'undefined')
|
(store.elementsSelectable && typeof node.selectable === 'undefined')
|
||||||
)}
|
)}
|
||||||
connectable={!!(
|
connectable={!!(
|
||||||
node.connectable ||
|
node.connectable ||
|
||||||
($nodesConnectable && typeof node.connectable === 'undefined')
|
(store.nodesConnectable && typeof node.connectable === 'undefined')
|
||||||
)}
|
)}
|
||||||
deletable={node.deletable ?? true}
|
deletable={node.deletable ?? true}
|
||||||
positionX={node.internals.positionAbsolute.x}
|
positionX={internalNode!.internals.positionAbsolute.x}
|
||||||
positionY={node.internals.positionAbsolute.y}
|
positionY={internalNode!.internals.positionAbsolute.y}
|
||||||
isParent={$parentLookup.has(node.id)}
|
isParent={store.parentLookup.has(node.id)}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
class={node.class}
|
class={node.class}
|
||||||
type={node.type ?? 'default'}
|
type={node.type ?? 'default'}
|
||||||
sourcePosition={node.sourcePosition}
|
sourcePosition={node.sourcePosition}
|
||||||
targetPosition={node.targetPosition}
|
targetPosition={node.targetPosition}
|
||||||
dragging={node.dragging}
|
dragging={node.dragging}
|
||||||
zIndex={node.internals.z ?? 0}
|
zIndex={internalNode!.internals.z ?? 0}
|
||||||
dragHandle={node.dragHandle}
|
dragHandle={node.dragHandle}
|
||||||
initialized={nodeHasDimensions(node)}
|
initialized={nodeHasDimensions(node)}
|
||||||
width={node.width}
|
width={node.width}
|
||||||
height={node.height}
|
height={node.height}
|
||||||
initialWidth={node.initialWidth}
|
initialWidth={node.initialWidth}
|
||||||
initialHeight={node.initialHeight}
|
initialHeight={node.initialHeight}
|
||||||
measuredWidth={node.measured.width}
|
measuredWidth={node.measured?.width ?? 0}
|
||||||
measuredHeight={node.measured.height}
|
measuredHeight={node.measured?.height ?? 0}
|
||||||
parentId={node.parentId}
|
parentId={node.parentId}
|
||||||
{resizeObserver}
|
{resizeObserver}
|
||||||
{nodeClickDistance}
|
{nodeClickDistance}
|
||||||
|
|||||||
@@ -37,39 +37,29 @@
|
|||||||
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 {
|
||||||
|
store,
|
||||||
panOnDrag = true,
|
panOnDrag = true,
|
||||||
selectionOnDrag,
|
selectionOnDrag,
|
||||||
onpaneclick,
|
onpaneclick,
|
||||||
onpanecontextmenu,
|
onpanecontextmenu,
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
children
|
children
|
||||||
}: PaneProps = $props();
|
}: PaneProps = $props();
|
||||||
|
|
||||||
const {
|
const { viewport, nodes, edges } = store;
|
||||||
nodeLookup,
|
|
||||||
viewport,
|
|
||||||
dragging,
|
|
||||||
elementsSelectable,
|
|
||||||
selectionRect,
|
|
||||||
selectionRectMode,
|
|
||||||
selectionKeyPressed,
|
|
||||||
selectionMode,
|
|
||||||
panActivationKeyPressed,
|
|
||||||
unselectNodesAndEdges
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
// 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;
|
||||||
let selectedNodes: InternalNode[] = [];
|
let selectedNodes: InternalNode[] = [];
|
||||||
|
|
||||||
let panOnDragActive = $derived($panActivationKeyPressed || panOnDrag);
|
let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag);
|
||||||
let isSelecting = $derived(
|
let isSelecting = $derived(
|
||||||
$selectionKeyPressed || $selectionRect || (selectionOnDrag && panOnDragActive !== true)
|
store.selectionKeyPressed ||
|
||||||
|
store.selectionRect ||
|
||||||
|
(selectionOnDrag && panOnDragActive !== true)
|
||||||
);
|
);
|
||||||
let hasActiveSelection = $derived(
|
let hasActiveSelection = $derived(
|
||||||
$elementsSelectable && (isSelecting || $selectionRectMode === 'user')
|
store.elementsSelectable && (isSelecting || store.selectionRectMode === 'user')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Used to prevent click events when the user lets go of the selectionKey during a selection
|
// Used to prevent click events when the user lets go of the selectionKey during a selection
|
||||||
@@ -83,15 +73,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onpaneclick?.({ event });
|
onpaneclick?.({ event });
|
||||||
unselectNodesAndEdges();
|
store.unselectNodesAndEdges();
|
||||||
selectionRectMode.set(null);
|
store.selectionRectMode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerDown(event: PointerEvent) {
|
function onPointerDown(event: PointerEvent) {
|
||||||
containerBounds = container.getBoundingClientRect();
|
containerBounds = container.getBoundingClientRect();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!elementsSelectable ||
|
!store.elementsSelectable ||
|
||||||
!isSelecting ||
|
!isSelecting ||
|
||||||
event.button !== 0 ||
|
event.button !== 0 ||
|
||||||
event.target !== container ||
|
event.target !== container ||
|
||||||
@@ -104,32 +94,32 @@
|
|||||||
|
|
||||||
const { x, y } = getEventPosition(event, containerBounds);
|
const { x, y } = getEventPosition(event, containerBounds);
|
||||||
|
|
||||||
unselectNodesAndEdges();
|
store.unselectNodesAndEdges();
|
||||||
|
|
||||||
selectionRect.set({
|
store.selectionRect = {
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
startX: x,
|
startX: x,
|
||||||
startY: y,
|
startY: y,
|
||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
});
|
};
|
||||||
|
|
||||||
// onSelectionStart?.(event);
|
// onSelectionStart?.(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerMove(event: PointerEvent) {
|
function onPointerMove(event: PointerEvent) {
|
||||||
if (!isSelecting || !containerBounds || !$selectionRect) {
|
if (!isSelecting || !containerBounds || !store.selectionRect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionInProgress = true;
|
selectionInProgress = true;
|
||||||
|
|
||||||
const mousePos = getEventPosition(event, containerBounds);
|
const mousePos = getEventPosition(event, containerBounds);
|
||||||
const startX = $selectionRect.startX ?? 0;
|
const startX = store.selectionRect.startX ?? 0;
|
||||||
const startY = $selectionRect.startY ?? 0;
|
const startY = store.selectionRect.startY ?? 0;
|
||||||
const nextUserSelectRect = {
|
const nextUserSelectRect = {
|
||||||
...$selectionRect,
|
...store.selectionRect,
|
||||||
x: mousePos.x < startX ? mousePos.x : startX,
|
x: mousePos.x < startX ? mousePos.x : startX,
|
||||||
y: mousePos.y < startY ? mousePos.y : startY,
|
y: mousePos.y < startY ? mousePos.y : startY,
|
||||||
width: Math.abs(mousePos.x - startX),
|
width: Math.abs(mousePos.x - startX),
|
||||||
@@ -139,10 +129,10 @@
|
|||||||
const prevSelectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
const prevSelectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
||||||
|
|
||||||
selectedNodes = getNodesInside(
|
selectedNodes = getNodesInside(
|
||||||
$nodeLookup,
|
store.nodeLookup,
|
||||||
nextUserSelectRect,
|
nextUserSelectRect,
|
||||||
[$viewport.x, $viewport.y, $viewport.zoom],
|
[$viewport.x, $viewport.y, $viewport.zoom],
|
||||||
$selectionMode === SelectionMode.Partial,
|
store.selectionMode === SelectionMode.Partial,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
||||||
@@ -163,8 +153,8 @@
|
|||||||
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
|
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionRectMode.set('user');
|
store.selectionRectMode = 'user';
|
||||||
selectionRect.set(nextUserSelectRect);
|
store.selectionRect = nextUserSelectRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerUp(event: PointerEvent) {
|
function onPointerUp(event: PointerEvent) {
|
||||||
@@ -176,18 +166,18 @@
|
|||||||
|
|
||||||
// We only want to trigger click functions when in selection mode if
|
// We only want to trigger click functions when in selection mode if
|
||||||
// the user did not move the mouse.
|
// the user did not move the mouse.
|
||||||
if (!isSelecting && $selectionRectMode === 'user' && event.target === container) {
|
if (!isSelecting && store.selectionRectMode === 'user' && event.target === container) {
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
}
|
}
|
||||||
selectionRect.set(null);
|
store.selectionRect = null;
|
||||||
|
|
||||||
if (selectedNodes.length > 0) {
|
if (selectedNodes.length > 0) {
|
||||||
$selectionRectMode = 'nodes';
|
store.selectionRectMode = 'nodes';
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user kept holding the selectionKey during the selection,
|
// If the user kept holding the selectionKey during the selection,
|
||||||
// we need to reset the selectionInProgress, so the next click event is not prevented
|
// we need to reset the selectionInProgress, so the next click event is not prevented
|
||||||
if ($selectionKeyPressed) {
|
if (store.selectionKeyPressed) {
|
||||||
selectionInProgress = false;
|
selectionInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +200,7 @@
|
|||||||
bind:this={container}
|
bind:this={container}
|
||||||
class="svelte-flow__pane"
|
class="svelte-flow__pane"
|
||||||
class:draggable={panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0))}
|
class:draggable={panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0))}
|
||||||
class:dragging={$dragging}
|
class:dragging={store.dragging}
|
||||||
class:selection={isSelecting}
|
class:selection={isSelecting}
|
||||||
onclick={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
|
onclick={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
|
||||||
onpointerdown={hasActiveSelection ? onPointerDown : undefined}
|
onpointerdown={hasActiveSelection ? onPointerDown : undefined}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { PaneEvents } from '$lib/types';
|
||||||
import type { Edge, Node, PaneEvents } from '$lib/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
export type PaneProps = {
|
export type PaneProps = {
|
||||||
|
store: SvelteFlowStore;
|
||||||
panOnDrag?: boolean | number[];
|
panOnDrag?: boolean | number[];
|
||||||
selectionOnDrag?: boolean;
|
selectionOnDrag?: boolean;
|
||||||
nodes: Writable<Node[]>;
|
|
||||||
edges: Writable<Edge[]>;
|
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
} & PaneEvents;
|
} & PaneEvents;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
...restProps
|
...restProps
|
||||||
}: PanelProps = $props();
|
}: PanelProps = $props();
|
||||||
|
|
||||||
const { selectionRectMode } = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
let positionClasses = $derived(`${position}`.split('-'));
|
let positionClasses = $derived(`${position}`.split('-'));
|
||||||
</script>
|
</script>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div
|
<div
|
||||||
class={cc(['svelte-flow__panel', className, ...positionClasses])}
|
class={cc(['svelte-flow__panel', className, ...positionClasses])}
|
||||||
{style}
|
{style}
|
||||||
style:pointer-events={$selectionRectMode ? 'none' : ''}
|
style:pointer-events={store.selectionRectMode ? 'none' : ''}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, hasContext } from 'svelte';
|
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
import { onMount, getContext, setContext } from 'svelte';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { ConnectionMode, PanOnScrollMode } from '@xyflow/system';
|
import { PanOnScrollMode } from '@xyflow/system';
|
||||||
|
|
||||||
import { Zoom } from '$lib/container/Zoom';
|
import { Zoom } from '$lib/container/Zoom';
|
||||||
import { Pane } from '$lib/container/Pane';
|
import { Pane } from '$lib/container/Pane';
|
||||||
@@ -14,78 +14,28 @@
|
|||||||
import { KeyHandler } from '$lib/components/KeyHandler';
|
import { KeyHandler } from '$lib/components/KeyHandler';
|
||||||
import { ConnectionLine } from '$lib/components/ConnectionLine';
|
import { ConnectionLine } from '$lib/components/ConnectionLine';
|
||||||
import { Attribution } from '$lib/components/Attribution';
|
import { Attribution } from '$lib/components/Attribution';
|
||||||
import { key, useStore, createStoreContext } from '$lib/store';
|
import { key, createStore } from '$lib/store';
|
||||||
import type { SvelteFlowProps } from './types';
|
import type { SvelteFlowProps } from './types';
|
||||||
import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils';
|
|
||||||
import { useColorModeClass } from '$lib/hooks/useColorModeClass';
|
import { useColorModeClass } from '$lib/hooks/useColorModeClass';
|
||||||
|
import { type ProviderContext, type ContainerSignals, type StoreContext } from '$lib/store/types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
id = '1',
|
style,
|
||||||
nodes,
|
class: className,
|
||||||
edges,
|
width,
|
||||||
fitView,
|
height,
|
||||||
fitViewOptions,
|
proOptions,
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
initialViewport,
|
|
||||||
viewport,
|
|
||||||
nodeTypes,
|
|
||||||
edgeTypes,
|
|
||||||
selectionKey,
|
selectionKey,
|
||||||
selectionMode,
|
deleteKey,
|
||||||
panActivationKey,
|
panActivationKey,
|
||||||
multiSelectionKey,
|
multiSelectionKey,
|
||||||
zoomActivationKey,
|
zoomActivationKey,
|
||||||
nodesDraggable,
|
|
||||||
nodesConnectable,
|
|
||||||
nodeDragThreshold,
|
|
||||||
elementsSelectable,
|
|
||||||
snapGrid,
|
|
||||||
deleteKey,
|
|
||||||
connectionRadius,
|
|
||||||
connectionLineType,
|
|
||||||
connectionMode = ConnectionMode.Strict,
|
|
||||||
connectionLineStyle = '',
|
|
||||||
connectionLineContainerStyle = '',
|
|
||||||
onMoveStart,
|
|
||||||
onMove,
|
|
||||||
onMoveEnd,
|
|
||||||
isValidConnection,
|
|
||||||
translateExtent,
|
|
||||||
nodeExtent,
|
|
||||||
onlyRenderVisibleElements,
|
|
||||||
panOnScrollMode = PanOnScrollMode.Free,
|
|
||||||
preventScrolling = true,
|
|
||||||
zoomOnScroll = true,
|
|
||||||
zoomOnDoubleClick = true,
|
|
||||||
zoomOnPinch = true,
|
|
||||||
panOnScroll = false,
|
|
||||||
panOnDrag = true,
|
|
||||||
selectionOnDrag,
|
|
||||||
autoPanOnConnect = true,
|
|
||||||
autoPanOnNodeDrag = true,
|
|
||||||
onerror,
|
|
||||||
ondelete,
|
|
||||||
onedgecreate,
|
|
||||||
attributionPosition,
|
|
||||||
proOptions,
|
|
||||||
defaultEdgeOptions,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
colorMode = 'light',
|
|
||||||
onconnect,
|
|
||||||
onconnectstart,
|
|
||||||
onconnectend,
|
|
||||||
onbeforedelete,
|
|
||||||
oninit,
|
|
||||||
nodeOrigin,
|
|
||||||
paneClickDistance = 1,
|
paneClickDistance = 1,
|
||||||
nodeClickDistance = 1,
|
nodeClickDistance = 1,
|
||||||
defaultMarkerColor = '#b1b1b7',
|
onMoveStart,
|
||||||
style,
|
onMoveEnd,
|
||||||
class: className,
|
onMove,
|
||||||
connectionLine,
|
connectionLine,
|
||||||
children,
|
|
||||||
onnodeclick,
|
onnodeclick,
|
||||||
onnodecontextmenu,
|
onnodecontextmenu,
|
||||||
onnodedrag,
|
onnodedrag,
|
||||||
@@ -102,135 +52,74 @@
|
|||||||
onedgemouseleave,
|
onedgemouseleave,
|
||||||
onpaneclick,
|
onpaneclick,
|
||||||
onpanecontextmenu,
|
onpanecontextmenu,
|
||||||
...rest
|
panOnScrollMode = PanOnScrollMode.Free,
|
||||||
|
preventScrolling = true,
|
||||||
|
zoomOnScroll = true,
|
||||||
|
zoomOnDoubleClick = true,
|
||||||
|
zoomOnPinch = true,
|
||||||
|
panOnScroll = false,
|
||||||
|
panOnDrag = true,
|
||||||
|
selectionOnDrag = true,
|
||||||
|
defaultEdgeOptions,
|
||||||
|
connectionLineContainerStyle = '',
|
||||||
|
connectionLineStyle = '',
|
||||||
|
attributionPosition,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
}: SvelteFlowProps = $props();
|
}: SvelteFlowProps = $props();
|
||||||
|
|
||||||
let domNode = $state<HTMLDivElement>();
|
// svelte-ignore perf_avoid_inline_class
|
||||||
let clientWidth = $state<number>();
|
const container: ContainerSignals = new (class {
|
||||||
let clientHeight = $state<number>();
|
domNode = $state<HTMLDivElement>();
|
||||||
|
width = $state<number | undefined>(width);
|
||||||
|
height = $state<number | undefined>(height);
|
||||||
|
})();
|
||||||
|
|
||||||
const initViewport = $viewport || initialViewport;
|
const store = createStore({
|
||||||
|
props,
|
||||||
|
container
|
||||||
|
});
|
||||||
|
const { viewport } = store;
|
||||||
|
const initialViewport = props.initialViewport ?? get(viewport);
|
||||||
|
|
||||||
//TODO SVELTE5
|
store.syncNodeStores(props.nodes);
|
||||||
|
store.syncEdgeStores(props.edges);
|
||||||
|
store.syncViewport(props.viewport);
|
||||||
|
|
||||||
const store = hasContext(key)
|
const providerContext = getContext<ProviderContext>(key);
|
||||||
? useStore()
|
if (providerContext) {
|
||||||
: createStoreContext({
|
providerContext.setStore(store);
|
||||||
nodes: get(nodes),
|
}
|
||||||
edges: get(edges),
|
setContext(key, {
|
||||||
width,
|
provider: false,
|
||||||
height,
|
getStore() {
|
||||||
fitView,
|
return store;
|
||||||
nodeOrigin,
|
}
|
||||||
nodeExtent
|
} satisfies StoreContext);
|
||||||
});
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
store.width.set(clientWidth!);
|
|
||||||
store.height.set(clientHeight!);
|
|
||||||
store.domNode.set(domNode!);
|
|
||||||
|
|
||||||
store.syncNodeStores(nodes);
|
|
||||||
store.syncEdgeStores(edges);
|
|
||||||
store.syncViewport(viewport);
|
|
||||||
|
|
||||||
if (fitView !== undefined) {
|
|
||||||
store.fitViewOnInit.set(fitView);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fitViewOptions) {
|
|
||||||
store.fitViewOptions.set(fitViewOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStore(store, {
|
|
||||||
nodeTypes,
|
|
||||||
edgeTypes,
|
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
translateExtent,
|
|
||||||
paneClickDistance
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
store.reset();
|
store.reset();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update width & height on resize
|
let colorModeClass = $derived(useColorModeClass(store.colorMode));
|
||||||
$effect.pre(() => {
|
|
||||||
if (clientWidth !== undefined && clientHeight !== undefined) {
|
|
||||||
store.width.set(clientWidth);
|
|
||||||
store.height.set(clientHeight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Call oninit once when flow is intialized
|
|
||||||
const { initialized } = store;
|
|
||||||
let onInitCalled = $state(false);
|
|
||||||
$effect.pre(() => {
|
|
||||||
if (!onInitCalled && $initialized) {
|
|
||||||
oninit?.();
|
|
||||||
onInitCalled = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// this updates the store for simple changes
|
|
||||||
// where the prop names equals the store name
|
|
||||||
$effect(() => {
|
|
||||||
const updatableProps: UpdatableStoreProps = {
|
|
||||||
flowId: id,
|
|
||||||
connectionLineType,
|
|
||||||
connectionRadius,
|
|
||||||
selectionMode,
|
|
||||||
snapGrid,
|
|
||||||
defaultMarkerColor,
|
|
||||||
nodesDraggable,
|
|
||||||
nodesConnectable,
|
|
||||||
elementsSelectable,
|
|
||||||
onlyRenderVisibleElements,
|
|
||||||
isValidConnection,
|
|
||||||
autoPanOnConnect,
|
|
||||||
autoPanOnNodeDrag,
|
|
||||||
onerror,
|
|
||||||
ondelete,
|
|
||||||
onedgecreate,
|
|
||||||
connectionMode,
|
|
||||||
nodeDragThreshold,
|
|
||||||
onconnect,
|
|
||||||
onconnectstart,
|
|
||||||
onconnectend,
|
|
||||||
onbeforedelete,
|
|
||||||
nodeOrigin
|
|
||||||
};
|
|
||||||
|
|
||||||
updateStoreByKeys(store, updatableProps);
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
updateStore(store, {
|
|
||||||
nodeTypes,
|
|
||||||
edgeTypes,
|
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
translateExtent,
|
|
||||||
paneClickDistance
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let colorModeClass = $derived(useColorModeClass(colorMode));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={domNode}
|
bind:this={container.domNode}
|
||||||
bind:clientWidth
|
bind:clientWidth={container.width}
|
||||||
bind:clientHeight
|
bind:clientHeight={container.height}
|
||||||
|
style:width
|
||||||
|
style:height
|
||||||
{style}
|
{style}
|
||||||
class={cc(['svelte-flow', className, $colorModeClass])}
|
class={cc(['svelte-flow', className, $colorModeClass])}
|
||||||
data-testid="svelte-flow__wrapper"
|
data-testid="svelte-flow__wrapper"
|
||||||
{...rest}
|
{...props}
|
||||||
role="application"
|
role="application"
|
||||||
>
|
>
|
||||||
<KeyHandler
|
<KeyHandler
|
||||||
|
{store}
|
||||||
{selectionKey}
|
{selectionKey}
|
||||||
{deleteKey}
|
{deleteKey}
|
||||||
{panActivationKey}
|
{panActivationKey}
|
||||||
@@ -238,7 +127,8 @@
|
|||||||
{zoomActivationKey}
|
{zoomActivationKey}
|
||||||
/>
|
/>
|
||||||
<Zoom
|
<Zoom
|
||||||
initialViewport={initViewport}
|
{store}
|
||||||
|
{initialViewport}
|
||||||
{onMoveStart}
|
{onMoveStart}
|
||||||
{onMove}
|
{onMove}
|
||||||
{onMoveEnd}
|
{onMoveEnd}
|
||||||
@@ -251,16 +141,17 @@
|
|||||||
{panOnDrag}
|
{panOnDrag}
|
||||||
{paneClickDistance}
|
{paneClickDistance}
|
||||||
>
|
>
|
||||||
<Pane {nodes} {edges} {onpaneclick} {onpanecontextmenu} {panOnDrag} {selectionOnDrag}>
|
<Pane {store} {onpaneclick} {onpanecontextmenu} {panOnDrag} {selectionOnDrag}>
|
||||||
<ViewportComponent>
|
<ViewportComponent viewport={$viewport}>
|
||||||
<EdgeRenderer
|
<EdgeRenderer
|
||||||
|
{store}
|
||||||
{onedgeclick}
|
{onedgeclick}
|
||||||
{onedgecontextmenu}
|
{onedgecontextmenu}
|
||||||
{onedgemouseenter}
|
{onedgemouseenter}
|
||||||
{onedgemouseleave}
|
{onedgemouseleave}
|
||||||
{defaultEdgeOptions}
|
|
||||||
/>
|
/>
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
|
{store}
|
||||||
containerStyle={connectionLineContainerStyle}
|
containerStyle={connectionLineContainerStyle}
|
||||||
style={connectionLineStyle}
|
style={connectionLineStyle}
|
||||||
{connectionLine}
|
{connectionLine}
|
||||||
@@ -268,6 +159,7 @@
|
|||||||
<div class="svelte-flow__edgelabel-renderer"></div>
|
<div class="svelte-flow__edgelabel-renderer"></div>
|
||||||
<div class="svelte-flow__viewport-portal"></div>
|
<div class="svelte-flow__viewport-portal"></div>
|
||||||
<NodeRenderer
|
<NodeRenderer
|
||||||
|
{store}
|
||||||
{nodeClickDistance}
|
{nodeClickDistance}
|
||||||
{onnodeclick}
|
{onnodeclick}
|
||||||
{onnodecontextmenu}
|
{onnodecontextmenu}
|
||||||
@@ -279,6 +171,7 @@
|
|||||||
{onnodedragstop}
|
{onnodedragstop}
|
||||||
/>
|
/>
|
||||||
<NodeSelection
|
<NodeSelection
|
||||||
|
{store}
|
||||||
{onselectionclick}
|
{onselectionclick}
|
||||||
{onselectioncontextmenu}
|
{onselectioncontextmenu}
|
||||||
{onnodedrag}
|
{onnodedrag}
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ export type SvelteFlowProps = NodeEvents &
|
|||||||
* @default 'full'
|
* @default 'full'
|
||||||
*/
|
*/
|
||||||
selectionMode?: SelectionMode;
|
selectionMode?: SelectionMode;
|
||||||
|
/**
|
||||||
|
* Controls if nodes should be automatically selected when being dragged
|
||||||
|
*/
|
||||||
|
selectNodesOnDrag: boolean;
|
||||||
/** Grid all nodes will snap to
|
/** Grid all nodes will snap to
|
||||||
* @example [20, 20]
|
* @example [20, 20]
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import type { Writable } from 'svelte/store';
|
|
||||||
import type { CoordinateExtent } from '@xyflow/system';
|
|
||||||
|
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
|
||||||
import type { EdgeTypes, NodeTypes } from '$lib/types';
|
|
||||||
|
|
||||||
// this is helper function for updating the store
|
|
||||||
// for props where we need to call a specific store action
|
|
||||||
export function updateStore(
|
|
||||||
store: SvelteFlowStore,
|
|
||||||
{
|
|
||||||
nodeTypes,
|
|
||||||
edgeTypes,
|
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
translateExtent,
|
|
||||||
paneClickDistance
|
|
||||||
}: {
|
|
||||||
nodeTypes?: NodeTypes;
|
|
||||||
edgeTypes?: EdgeTypes;
|
|
||||||
minZoom?: number;
|
|
||||||
maxZoom?: number;
|
|
||||||
translateExtent?: CoordinateExtent;
|
|
||||||
paneClickDistance?: number;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (nodeTypes !== undefined) {
|
|
||||||
store.setNodeTypes(nodeTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edgeTypes !== undefined) {
|
|
||||||
store.setEdgeTypes(edgeTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minZoom !== undefined) {
|
|
||||||
store.setMinZoom(minZoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxZoom !== undefined) {
|
|
||||||
store.setMaxZoom(maxZoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translateExtent !== undefined) {
|
|
||||||
store.setTranslateExtent(translateExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paneClickDistance !== undefined) {
|
|
||||||
store.setPaneClickDistance(paneClickDistance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getKeys = <T extends object>(obj: T) => Object.keys(obj) as Array<keyof T>;
|
|
||||||
|
|
||||||
type UnwrapWritable<T> = T extends Writable<infer U> ? U : T;
|
|
||||||
|
|
||||||
// @todo there must be a better way to define the types here..
|
|
||||||
export type UpdatableStoreProps = {
|
|
||||||
flowId?: UnwrapWritable<SvelteFlowStore['flowId']>;
|
|
||||||
connectionLineType?: UnwrapWritable<SvelteFlowStore['connectionLineType']>;
|
|
||||||
connectionRadius?: UnwrapWritable<SvelteFlowStore['connectionRadius']>;
|
|
||||||
selectionMode?: UnwrapWritable<SvelteFlowStore['selectionMode']>;
|
|
||||||
snapGrid?: UnwrapWritable<SvelteFlowStore['snapGrid']>;
|
|
||||||
defaultMarkerColor?: UnwrapWritable<SvelteFlowStore['defaultMarkerColor']>;
|
|
||||||
nodesDraggable?: UnwrapWritable<SvelteFlowStore['nodesDraggable']>;
|
|
||||||
nodesConnectable?: UnwrapWritable<SvelteFlowStore['nodesConnectable']>;
|
|
||||||
elementsSelectable?: UnwrapWritable<SvelteFlowStore['elementsSelectable']>;
|
|
||||||
onlyRenderVisibleElements?: UnwrapWritable<SvelteFlowStore['onlyRenderVisibleElements']>;
|
|
||||||
isValidConnection?: UnwrapWritable<SvelteFlowStore['isValidConnection']>;
|
|
||||||
autoPanOnConnect?: UnwrapWritable<SvelteFlowStore['autoPanOnConnect']>;
|
|
||||||
autoPanOnNodeDrag?: UnwrapWritable<SvelteFlowStore['autoPanOnNodeDrag']>;
|
|
||||||
connectionMode?: UnwrapWritable<SvelteFlowStore['connectionMode']>;
|
|
||||||
onerror?: UnwrapWritable<SvelteFlowStore['onerror']>;
|
|
||||||
ondelete?: UnwrapWritable<SvelteFlowStore['ondelete']>;
|
|
||||||
onedgecreate?: UnwrapWritable<SvelteFlowStore['onedgecreate']>;
|
|
||||||
nodeDragThreshold?: UnwrapWritable<SvelteFlowStore['nodeDragThreshold']>;
|
|
||||||
onconnect?: UnwrapWritable<SvelteFlowStore['onconnect']>;
|
|
||||||
onconnectstart?: UnwrapWritable<SvelteFlowStore['onconnectstart']>;
|
|
||||||
onconnectend?: UnwrapWritable<SvelteFlowStore['onconnectend']>;
|
|
||||||
onbeforedelete?: UnwrapWritable<SvelteFlowStore['onbeforedelete']>;
|
|
||||||
nodeOrigin?: UnwrapWritable<SvelteFlowStore['nodeOrigin']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from '$lib/store';
|
import type { Viewport } from '@xyflow/system';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
const { viewport } = useStore();
|
let { viewport, children }: { viewport: Viewport; children: Snippet } = $props();
|
||||||
|
|
||||||
let { children }: { children: Snippet } = $props();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="svelte-flow__viewport xyflow__viewport"
|
class="svelte-flow__viewport xyflow__viewport"
|
||||||
style="transform: translate({$viewport.x}px, {$viewport.y}px) scale({$viewport.zoom})"
|
style="transform: translate({viewport.x}px, {viewport.y}px) scale({viewport.zoom})"
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { PanOnScrollMode, type Transform } from '@xyflow/system';
|
import { PanOnScrollMode, type PanZoomInstance, type Transform } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import zoom from '$lib/actions/zoom';
|
import zoom from '$lib/actions/zoom';
|
||||||
import type { ZoomProps } from './types';
|
import type { ZoomProps } from './types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
store,
|
||||||
initialViewport = { x: 0, y: 0, zoom: 1 },
|
initialViewport = { x: 0, y: 0, zoom: 1 },
|
||||||
onMoveStart,
|
onMoveStart,
|
||||||
onMove,
|
onMove,
|
||||||
@@ -22,40 +23,30 @@
|
|||||||
children
|
children
|
||||||
}: ZoomProps = $props();
|
}: ZoomProps = $props();
|
||||||
|
|
||||||
const {
|
let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag);
|
||||||
viewport,
|
let panOnScrollActive = $derived(store.panActivationKeyPressed || panOnScroll);
|
||||||
panZoom,
|
|
||||||
selectionRect,
|
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
dragging,
|
|
||||||
translateExtent,
|
|
||||||
lib,
|
|
||||||
panActivationKeyPressed,
|
|
||||||
zoomActivationKeyPressed,
|
|
||||||
viewportInitialized
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
let panOnDragActive = $derived($panActivationKeyPressed || panOnDrag);
|
|
||||||
let panOnScrollActive = $derived($panActivationKeyPressed || panOnScroll);
|
|
||||||
|
|
||||||
const onTransformChange = (transform: Transform) =>
|
const onTransformChange = (transform: Transform) =>
|
||||||
viewport.set({ x: transform[0], y: transform[1], zoom: transform[2] });
|
store.viewport.set({ x: transform[0], y: transform[1], zoom: transform[2] });
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
$viewportInitialized = true;
|
store.viewportInitialized = true;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="svelte-flow__zoom"
|
class="svelte-flow__zoom"
|
||||||
use:zoom={{
|
use:zoom={{
|
||||||
viewport,
|
viewport: store.viewport,
|
||||||
minZoom: $minZoom,
|
minZoom: store.minZoom,
|
||||||
maxZoom: $maxZoom,
|
maxZoom: store.maxZoom,
|
||||||
initialViewport,
|
initialViewport,
|
||||||
dragging,
|
onDraggingChange: (dragging: boolean) => {
|
||||||
panZoom,
|
store.dragging = dragging;
|
||||||
|
},
|
||||||
|
setPanZoomInstance: (instance: PanZoomInstance) => {
|
||||||
|
store.panZoom = instance;
|
||||||
|
},
|
||||||
onPanZoomStart: onMoveStart,
|
onPanZoomStart: onMoveStart,
|
||||||
onPanZoom: onMove,
|
onPanZoom: onMove,
|
||||||
onPanZoomEnd: onMoveEnd,
|
onPanZoomEnd: onMoveEnd,
|
||||||
@@ -66,13 +57,13 @@
|
|||||||
panOnDrag: panOnDragActive,
|
panOnDrag: panOnDragActive,
|
||||||
panOnScrollSpeed: 0.5,
|
panOnScrollSpeed: 0.5,
|
||||||
panOnScrollMode: panOnScrollMode || PanOnScrollMode.Free,
|
panOnScrollMode: panOnScrollMode || PanOnScrollMode.Free,
|
||||||
zoomActivationKeyPressed: $zoomActivationKeyPressed,
|
zoomActivationKeyPressed: store.zoomActivationKeyPressed,
|
||||||
preventScrolling: typeof preventScrolling === 'boolean' ? preventScrolling : true,
|
preventScrolling: typeof preventScrolling === 'boolean' ? preventScrolling : true,
|
||||||
noPanClassName: 'nopan',
|
noPanClassName: 'nopan',
|
||||||
noWheelClassName: 'nowheel',
|
noWheelClassName: 'nowheel',
|
||||||
userSelectionActive: !!$selectionRect,
|
userSelectionActive: !!store.selectionRect,
|
||||||
translateExtent: $translateExtent,
|
translateExtent: store.translateExtent,
|
||||||
lib: $lib,
|
lib: 'svelte',
|
||||||
paneClickDistance,
|
paneClickDistance,
|
||||||
onTransformChange
|
onTransformChange
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
import type { OnMoveStart, OnMove, OnMoveEnd, PanOnScrollMode, Viewport } from '@xyflow/system';
|
import type { OnMoveStart, OnMove, OnMoveEnd, PanOnScrollMode, Viewport } from '@xyflow/system';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
export type ZoomProps = {
|
export type ZoomProps = {
|
||||||
|
store: SvelteFlowStore;
|
||||||
initialViewport?: Viewport;
|
initialViewport?: Viewport;
|
||||||
panOnScrollMode: PanOnScrollMode;
|
panOnScrollMode: PanOnScrollMode;
|
||||||
onMove?: OnMove;
|
onMove?: OnMove;
|
||||||
|
|||||||
@@ -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<StoreContext>(key);
|
||||||
|
|
||||||
|
if (!storeContext) {
|
||||||
|
throw new Error(
|
||||||
|
`In order to use ${functionName}() you need to wrap your component in a <SvelteFlowProvider />`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeContext.provider && !$effect.tracking()) {
|
||||||
|
console.warn(
|
||||||
|
`Use $derived(${functionName}()), when not calling inside a child of the <SvelteFlow /> component.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './useSvelteFlow.svelte';
|
||||||
@@ -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<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') {
|
||||||
|
derivedWarning('useStore');
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeContext.getStore();
|
||||||
|
}
|
||||||
+90
-73
@@ -22,7 +22,8 @@ import {
|
|||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { Edge, FitViewOptions, InternalNode, Node } from '$lib/types';
|
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.
|
* Hook for accessing the ReactFlow instance.
|
||||||
@@ -232,6 +233,21 @@ export function useSvelteFlow(): {
|
|||||||
*
|
*
|
||||||
* @returns the nodes, edges and the viewport as a JSON object
|
* @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: Edge) => Partial<Edge>),
|
||||||
|
options?: { replace: boolean }
|
||||||
|
) => void;
|
||||||
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
||||||
/**
|
/**
|
||||||
* Returns the bounds of the given nodes or node ids.
|
* Returns the bounds of the given nodes or node ids.
|
||||||
@@ -258,37 +274,22 @@ export function useSvelteFlow(): {
|
|||||||
id?: string | null;
|
id?: string | null;
|
||||||
}) => HandleConnection[];
|
}) => HandleConnection[];
|
||||||
} {
|
} {
|
||||||
const {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
zoomIn,
|
derivedWarning('useSvelteFlow');
|
||||||
zoomOut,
|
}
|
||||||
fitView,
|
|
||||||
onbeforedelete,
|
const store = useStore();
|
||||||
snapGrid,
|
const { viewport, nodes, edges } = store;
|
||||||
viewport,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
panZoom,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
domNode,
|
|
||||||
nodeLookup,
|
|
||||||
nodeOrigin,
|
|
||||||
edgeLookup,
|
|
||||||
connectionLookup
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => {
|
const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => {
|
||||||
const $nodeLookup = get(nodeLookup);
|
const nodeToUse = isNode(node) ? node : store.nodeLookup.get(node.id)!;
|
||||||
const nodeToUse = isNode(node) ? node : $nodeLookup.get(node.id)!;
|
|
||||||
const position = nodeToUse.parentId
|
const position = nodeToUse.parentId
|
||||||
? evaluateAbsolutePosition(
|
? evaluateAbsolutePosition(
|
||||||
nodeToUse.position,
|
nodeToUse.position,
|
||||||
nodeToUse.measured,
|
nodeToUse.measured,
|
||||||
nodeToUse.parentId,
|
nodeToUse.parentId,
|
||||||
$nodeLookup,
|
store.nodeLookup,
|
||||||
get(nodeOrigin)
|
store.nodeOrigin
|
||||||
)
|
)
|
||||||
: nodeToUse.position;
|
: nodeToUse.position;
|
||||||
|
|
||||||
@@ -308,7 +309,7 @@ export function useSvelteFlow(): {
|
|||||||
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
||||||
options: { replace: boolean } = { replace: false }
|
options: { replace: boolean } = { replace: false }
|
||||||
) => {
|
) => {
|
||||||
const node = get(nodeLookup).get(id)?.internals.userNode;
|
const node = store.nodeLookup.get(id)?.internals.userNode;
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
@@ -328,36 +329,64 @@ export function useSvelteFlow(): {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Object.assign(node, nextNode);
|
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: Edge) => Partial<Edge>),
|
||||||
|
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 {
|
return {
|
||||||
zoomIn,
|
zoomIn: store.zoomIn,
|
||||||
zoomOut,
|
zoomOut: store.zoomOut,
|
||||||
getInternalNode,
|
getInternalNode,
|
||||||
getNode: (id) => getInternalNode(id)?.internals.userNode,
|
getNode: (id) => getInternalNode(id)?.internals.userNode,
|
||||||
getNodes: (ids) => (ids === undefined ? get(nodes) : getElements(get(nodeLookup), ids)),
|
getNodes: (ids) => (ids === undefined ? get(nodes) : getElements(store.nodeLookup, ids)),
|
||||||
getEdge: (id) => get(edgeLookup).get(id),
|
getEdge: (id) => store.edgeLookup.get(id),
|
||||||
getEdges: (ids) => (ids === undefined ? get(edges) : getElements(get(edgeLookup), ids)),
|
getEdges: (ids) => (ids === undefined ? get(edges) : getElements(store.edgeLookup, ids)),
|
||||||
setZoom: (zoomLevel, options) => {
|
setZoom: (zoomLevel, options) => {
|
||||||
const currentPanZoom = get(panZoom);
|
const panZoom = store.panZoom;
|
||||||
return currentPanZoom
|
return panZoom
|
||||||
? currentPanZoom.scaleTo(zoomLevel, { duration: options?.duration })
|
? panZoom.scaleTo(zoomLevel, { duration: options?.duration })
|
||||||
: Promise.resolve(false);
|
: Promise.resolve(false);
|
||||||
},
|
},
|
||||||
getZoom: () => get(viewport).zoom,
|
getZoom: () => get(viewport).zoom,
|
||||||
setViewport: async (nextViewport, options) => {
|
setViewport: async (nextViewport, options) => {
|
||||||
const currentViewport = get(viewport);
|
const currentViewport = get(viewport);
|
||||||
const currentPanZoom = get(panZoom);
|
|
||||||
|
|
||||||
if (!currentPanZoom) {
|
if (!store.panZoom) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await currentPanZoom.setViewport(
|
await store.panZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: nextViewport.x ?? currentViewport.x,
|
x: nextViewport.x ?? currentViewport.x,
|
||||||
y: nextViewport.y ?? currentViewport.y,
|
y: nextViewport.y ?? currentViewport.y,
|
||||||
@@ -370,8 +399,8 @@ export function useSvelteFlow(): {
|
|||||||
},
|
},
|
||||||
getViewport: () => get(viewport),
|
getViewport: () => get(viewport),
|
||||||
setCenter: async (x, y, options) => {
|
setCenter: async (x, y, options) => {
|
||||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom;
|
||||||
const currentPanZoom = get(panZoom);
|
const currentPanZoom = store.panZoom;
|
||||||
|
|
||||||
if (!currentPanZoom) {
|
if (!currentPanZoom) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
@@ -379,8 +408,8 @@ export function useSvelteFlow(): {
|
|||||||
|
|
||||||
await currentPanZoom.setViewport(
|
await currentPanZoom.setViewport(
|
||||||
{
|
{
|
||||||
x: get(width) / 2 - x * nextZoom,
|
x: store.width / 2 - x * nextZoom,
|
||||||
y: get(height) / 2 - y * nextZoom,
|
y: store.height / 2 - y * nextZoom,
|
||||||
zoom: nextZoom
|
zoom: nextZoom
|
||||||
},
|
},
|
||||||
{ duration: options?.duration }
|
{ duration: options?.duration }
|
||||||
@@ -388,24 +417,22 @@ export function useSvelteFlow(): {
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
fitView,
|
fitView: store.fitView,
|
||||||
fitBounds: async (bounds: Rect, options?: FitBoundsOptions) => {
|
fitBounds: async (bounds: Rect, options?: FitBoundsOptions) => {
|
||||||
const currentPanZoom = get(panZoom);
|
if (!store.panZoom) {
|
||||||
|
|
||||||
if (!currentPanZoom) {
|
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewport = getViewportForBounds(
|
const viewport = getViewportForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
get(width),
|
store.width,
|
||||||
get(height),
|
store.height,
|
||||||
get(minZoom),
|
store.minZoom,
|
||||||
get(maxZoom),
|
store.maxZoom,
|
||||||
options?.padding ?? 0.1
|
options?.padding ?? 0.1
|
||||||
);
|
);
|
||||||
|
|
||||||
await currentPanZoom.setViewport(viewport, { duration: options?.duration });
|
await store.panZoom.setViewport(viewport, { duration: options?.duration });
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
@@ -422,7 +449,7 @@ export function useSvelteFlow(): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (nodesToIntersect || get(nodes)).filter((n) => {
|
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)) {
|
if (!internalNode || (!isRect && n.id === nodeOrRect.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -457,7 +484,7 @@ export function useSvelteFlow(): {
|
|||||||
edgesToRemove,
|
edgesToRemove,
|
||||||
nodes: get(nodes),
|
nodes: get(nodes),
|
||||||
edges: get(edges),
|
edges: get(edges),
|
||||||
onBeforeDelete: get(onbeforedelete)
|
onBeforeDelete: store.onbeforedelete
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matchingNodes) {
|
if (matchingNodes) {
|
||||||
@@ -481,15 +508,13 @@ export function useSvelteFlow(): {
|
|||||||
position: XYPosition,
|
position: XYPosition,
|
||||||
options: { snapToGrid: boolean } = { snapToGrid: true }
|
options: { snapToGrid: boolean } = { snapToGrid: true }
|
||||||
) => {
|
) => {
|
||||||
const _domNode = get(domNode);
|
if (!store.domNode) {
|
||||||
|
|
||||||
if (!_domNode) {
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _snapGrid = options.snapToGrid ? get(snapGrid) : false;
|
const _snapGrid = options.snapToGrid ? store.snapGrid : false;
|
||||||
const { x, y, zoom } = get(viewport);
|
const { x, y, zoom } = get(viewport);
|
||||||
const { x: domX, y: domY } = _domNode.getBoundingClientRect();
|
const { x: domX, y: domY } = store.domNode.getBoundingClientRect();
|
||||||
const correctedPosition = {
|
const correctedPosition = {
|
||||||
x: position.x - domX,
|
x: position.x - domX,
|
||||||
y: position.y - domY
|
y: position.y - domY
|
||||||
@@ -508,14 +533,12 @@ export function useSvelteFlow(): {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
flowToScreenPosition: (position: XYPosition) => {
|
flowToScreenPosition: (position: XYPosition) => {
|
||||||
const _domNode = get(domNode);
|
if (!store.domNode) {
|
||||||
|
|
||||||
if (!_domNode) {
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x, y, zoom } = get(viewport);
|
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]);
|
const rendererPosition = rendererPointToPoint(position, [x, y, zoom]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -539,7 +562,7 @@ export function useSvelteFlow(): {
|
|||||||
},
|
},
|
||||||
updateNode,
|
updateNode,
|
||||||
updateNodeData: (id, dataUpdate, options) => {
|
updateNodeData: (id, dataUpdate, options) => {
|
||||||
const node = get(nodeLookup).get(id)?.internals.userNode;
|
const node = store.nodeLookup.get(id)?.internals.userNode;
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
@@ -551,18 +574,12 @@ export function useSvelteFlow(): {
|
|||||||
|
|
||||||
nodes.update((nds) => nds);
|
nodes.update((nds) => nds);
|
||||||
},
|
},
|
||||||
|
updateEdge,
|
||||||
getNodesBounds: (nodes) => {
|
getNodesBounds: (nodes) => {
|
||||||
const _nodeLookup = get(nodeLookup);
|
return getNodesBounds(nodes, { nodeLookup: store.nodeLookup, nodeOrigin: store.nodeOrigin });
|
||||||
const _nodeOrigin = get(nodeOrigin);
|
|
||||||
|
|
||||||
return getNodesBounds(nodes, { nodeLookup: _nodeLookup, nodeOrigin: _nodeOrigin });
|
|
||||||
},
|
},
|
||||||
getHandleConnections: ({ type, id, nodeId }) =>
|
getHandleConnections: ({ type, id, nodeId }) =>
|
||||||
Array.from(
|
Array.from(store.connectionLookup.get(`${nodeId}-${type}-${id ?? null}`)?.values() ?? []),
|
||||||
get(connectionLookup)
|
|
||||||
.get(`${nodeId}-${type}-${id ?? null}`)
|
|
||||||
?.values() ?? []
|
|
||||||
),
|
|
||||||
viewport
|
viewport
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export { useStore } from '$lib/store';
|
|||||||
export * from '$lib/utils';
|
export * from '$lib/utils';
|
||||||
|
|
||||||
//hooks
|
//hooks
|
||||||
export * from '$lib/hooks/useSvelteFlow';
|
export * from '$lib/hooks/useSvelteFlow.svelte';
|
||||||
export * from '$lib/hooks/useUpdateNodeInternals';
|
export * from '$lib/hooks/useUpdateNodeInternals';
|
||||||
export * from '$lib/hooks/useConnection';
|
export * from '$lib/hooks/useConnection';
|
||||||
export * from '$lib/hooks/useNodesEdges';
|
export * from '$lib/hooks/useNodesEdges';
|
||||||
|
|||||||
@@ -27,13 +27,17 @@
|
|||||||
class: className
|
class: className
|
||||||
}: BackgroundProps = $props();
|
}: BackgroundProps = $props();
|
||||||
|
|
||||||
const { viewport, flowId } = useStore();
|
const store = useStore();
|
||||||
|
const { viewport } = store;
|
||||||
|
|
||||||
|
$inspect($viewport);
|
||||||
|
|
||||||
const patternSize = size ?? defaultSize[variant!];
|
const patternSize = size ?? defaultSize[variant!];
|
||||||
const isDots = variant === BackgroundVariant.Dots;
|
const isDots = variant === BackgroundVariant.Dots;
|
||||||
const isCross = variant === BackgroundVariant.Cross;
|
const isCross = variant === BackgroundVariant.Cross;
|
||||||
const gapXY: number[] = Array.isArray(gap!) ? gap! : [gap!, gap!];
|
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 scaledGap = $derived([gapXY[0] * $viewport.zoom || 1, gapXY[1] * $viewport.zoom || 1]);
|
||||||
let scaledSize = $derived(patternSize * $viewport.zoom);
|
let scaledSize = $derived(patternSize * $viewport.zoom);
|
||||||
let patternDimensions = $derived(
|
let patternDimensions = $derived(
|
||||||
|
|||||||
@@ -32,17 +32,8 @@
|
|||||||
after
|
after
|
||||||
}: ControlsProps = $props();
|
}: ControlsProps = $props();
|
||||||
|
|
||||||
const {
|
const store = useStore();
|
||||||
viewport,
|
const { viewport } = store;
|
||||||
minZoom,
|
|
||||||
maxZoom,
|
|
||||||
nodesDraggable,
|
|
||||||
nodesConnectable,
|
|
||||||
elementsSelectable,
|
|
||||||
zoomIn,
|
|
||||||
zoomOut,
|
|
||||||
fitView
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
const buttonProps = {
|
const buttonProps = {
|
||||||
bgColor: buttonBgColor,
|
bgColor: buttonBgColor,
|
||||||
@@ -52,29 +43,31 @@
|
|||||||
borderColor: buttonBorderColor
|
borderColor: buttonBorderColor
|
||||||
};
|
};
|
||||||
|
|
||||||
let isInteractive = $state($nodesDraggable || $nodesConnectable || $elementsSelectable);
|
let isInteractive = $state(
|
||||||
let minZoomReached = $derived($viewport.zoom <= $minZoom);
|
store.nodesDraggable || store.nodesConnectable || store.elementsSelectable
|
||||||
let maxZoomReached = $derived($viewport.zoom >= $maxZoom);
|
);
|
||||||
|
let minZoomReached = $derived($viewport.zoom <= store.minZoom);
|
||||||
|
let maxZoomReached = $derived($viewport.zoom >= store.maxZoom);
|
||||||
let orientationClass = $derived(orientation === 'horizontal' ? 'horizontal' : 'vertical');
|
let orientationClass = $derived(orientation === 'horizontal' ? 'horizontal' : 'vertical');
|
||||||
|
|
||||||
const onZoomInHandler = () => {
|
const onZoomInHandler = () => {
|
||||||
zoomIn();
|
store.zoomIn();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onZoomOutHandler = () => {
|
const onZoomOutHandler = () => {
|
||||||
zoomOut();
|
store.zoomOut();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFitViewHandler = () => {
|
const onFitViewHandler = () => {
|
||||||
fitView(fitViewOptions);
|
store.fitView(fitViewOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleInteractivity = () => {
|
const onToggleInteractivity = () => {
|
||||||
isInteractive = !isInteractive;
|
isInteractive = !isInteractive;
|
||||||
|
|
||||||
nodesDraggable.set(isInteractive);
|
store.nodesDraggable = isInteractive;
|
||||||
nodesConnectable.set(isInteractive);
|
store.nodesConnectable = isInteractive;
|
||||||
elementsSelectable.set(isInteractive);
|
store.elementsSelectable = isInteractive;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -42,16 +42,10 @@
|
|||||||
class: className
|
class: className
|
||||||
}: MiniMapProps = $props();
|
}: MiniMapProps = $props();
|
||||||
|
|
||||||
const {
|
let store = useStore();
|
||||||
nodes,
|
const { viewport } = store;
|
||||||
nodeLookup,
|
|
||||||
viewport,
|
let nodes = $derived(store.nodes);
|
||||||
width: containerWidth,
|
|
||||||
height: containerHeight,
|
|
||||||
flowId,
|
|
||||||
panZoom,
|
|
||||||
translateExtent
|
|
||||||
} = useStore();
|
|
||||||
|
|
||||||
const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor);
|
const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor);
|
||||||
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
||||||
@@ -59,17 +53,20 @@
|
|||||||
const shapeRendering =
|
const shapeRendering =
|
||||||
// @ts-expect-error - TS doesn't know about chrome
|
// @ts-expect-error - TS doesn't know about chrome
|
||||||
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
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
|
// TODO: simplify this
|
||||||
let viewBB = $derived({
|
let viewBB = $derived({
|
||||||
x: -$viewport.x / $viewport.zoom,
|
x: -$viewport.x / $viewport.zoom,
|
||||||
y: -$viewport.y / $viewport.zoom,
|
y: -$viewport.y / $viewport.zoom,
|
||||||
width: $containerWidth / $viewport.zoom,
|
width: store.width / $viewport.zoom,
|
||||||
height: $containerHeight / $viewport.zoom
|
height: store.height / $viewport.zoom
|
||||||
});
|
});
|
||||||
let boundingRect = $derived(
|
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 scaledWidth = $derived(boundingRect.width / width);
|
||||||
let scaledHeight = $derived(boundingRect.height / height);
|
let scaledHeight = $derived(boundingRect.height / height);
|
||||||
@@ -91,7 +88,7 @@
|
|||||||
class={cc(['svelte-flow__minimap', className])}
|
class={cc(['svelte-flow__minimap', className])}
|
||||||
data-testid="svelte-flow__minimap"
|
data-testid="svelte-flow__minimap"
|
||||||
>
|
>
|
||||||
{#if $panZoom}
|
{#if store.panZoom}
|
||||||
<svg
|
<svg
|
||||||
{width}
|
{width}
|
||||||
{height}
|
{height}
|
||||||
@@ -105,12 +102,12 @@
|
|||||||
? maskStrokeWidth * viewScale
|
? maskStrokeWidth * viewScale
|
||||||
: undefined}
|
: undefined}
|
||||||
use:interactive={{
|
use:interactive={{
|
||||||
panZoom: $panZoom,
|
panZoom: store.panZoom,
|
||||||
viewport,
|
viewport: store.viewport,
|
||||||
getViewScale,
|
getViewScale,
|
||||||
translateExtent: $translateExtent,
|
translateExtent: store.translateExtent,
|
||||||
width: $containerWidth,
|
width: store.width,
|
||||||
height: $containerHeight,
|
height: store.height,
|
||||||
inversePan,
|
inversePan,
|
||||||
zoomStep,
|
zoomStep,
|
||||||
pannable,
|
pannable,
|
||||||
@@ -120,7 +117,7 @@
|
|||||||
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
|
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
|
||||||
|
|
||||||
{#each $nodes as userNode (userNode.id)}
|
{#each $nodes as userNode (userNode.id)}
|
||||||
{@const node = $nodeLookup.get(userNode.id)}
|
{@const node = store.nodeLookup.get(userNode.id)}
|
||||||
{#if node && nodeHasDimensions(node)}
|
{#if node && nodeHasDimensions(node)}
|
||||||
{@const nodeDimesions = getNodeDimensions(node)}
|
{@const nodeDimesions = getNodeDimensions(node)}
|
||||||
<MinimapNode
|
<MinimapNode
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { useSvelteFlow } from '$lib/hooks/useSvelteFlow';
|
import { useSvelteFlow } from '$lib/hooks';
|
||||||
|
|
||||||
import type { InternalNode } from '$lib/types';
|
import type { InternalNode } from '$lib/types';
|
||||||
import type { NodeToolbarProps } from './types';
|
import type { NodeToolbarProps } from './types';
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import { getContext, setContext } from 'svelte';
|
import { get } from 'svelte/store';
|
||||||
import { derived, get, writable } from 'svelte/store';
|
|
||||||
import {
|
import {
|
||||||
createMarkerIds,
|
|
||||||
fitView as fitViewSystem,
|
fitView as fitViewSystem,
|
||||||
panBy as panBySystem,
|
panBy as panBySystem,
|
||||||
updateNodeInternals as updateNodeInternalsSystem,
|
updateNodeInternals as updateNodeInternalsSystem,
|
||||||
addEdge as addEdgeUtil,
|
addEdge as addEdgeUtil,
|
||||||
initialConnection,
|
initialConnection,
|
||||||
errorMessages,
|
errorMessages,
|
||||||
pointToRendererPoint,
|
|
||||||
type UpdateNodePositions,
|
type UpdateNodePositions,
|
||||||
type InternalNodeUpdate,
|
type InternalNodeUpdate,
|
||||||
type ViewportHelperFunctionOptions,
|
type ViewportHelperFunctionOptions,
|
||||||
@@ -17,60 +14,37 @@ import {
|
|||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
type ConnectionState,
|
type ConnectionState,
|
||||||
type NodeOrigin,
|
|
||||||
getFitViewNodes,
|
getFitViewNodes,
|
||||||
updateAbsolutePositions,
|
updateAbsolutePositions,
|
||||||
getDimensions
|
getDimensions
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
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';
|
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store.svelte';
|
||||||
import type { SvelteFlowStore } from './types';
|
import { type StoreSignals, type SvelteFlowStore, type SvelteFlowStoreActions } from './types';
|
||||||
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
|
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
|
||||||
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 function createStore({
|
export { useStore } from './hook.svelte';
|
||||||
nodes,
|
|
||||||
edges,
|
export function createStore(signals: StoreSignals): SvelteFlowStore {
|
||||||
width,
|
const store = getInitialStore(signals);
|
||||||
height,
|
|
||||||
fitView: fitViewOnCreate,
|
|
||||||
nodeOrigin,
|
|
||||||
nodeExtent
|
|
||||||
}: {
|
|
||||||
nodes?: Node[];
|
|
||||||
edges?: Edge[];
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
fitView?: boolean;
|
|
||||||
nodeOrigin?: NodeOrigin;
|
|
||||||
nodeExtent?: CoordinateExtent;
|
|
||||||
}): SvelteFlowStore {
|
|
||||||
const store = getInitialStore({
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
fitView: fitViewOnCreate,
|
|
||||||
nodeOrigin,
|
|
||||||
nodeExtent
|
|
||||||
});
|
|
||||||
|
|
||||||
function setNodeTypes(nodeTypes: NodeTypes) {
|
function setNodeTypes(nodeTypes: NodeTypes) {
|
||||||
store.nodeTypes.set({
|
store.nodeTypes = {
|
||||||
...initialNodeTypes,
|
...initialNodeTypes,
|
||||||
...nodeTypes
|
...nodeTypes
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEdgeTypes(edgeTypes: EdgeTypes) {
|
function setEdgeTypes(edgeTypes: EdgeTypes) {
|
||||||
store.edgeTypes.set({
|
store.edgeTypes = {
|
||||||
...initialEdgeTypes,
|
...initialEdgeTypes,
|
||||||
...edgeTypes
|
...edgeTypes
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEdge(edgeParams: Edge | Connection) {
|
function addEdge(edgeParams: Edge | Connection) {
|
||||||
@@ -79,10 +53,8 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
||||||
const nodeLookup = get(store.nodeLookup);
|
|
||||||
|
|
||||||
for (const [id, dragItem] of nodeDragItems) {
|
for (const [id, dragItem] of nodeDragItems) {
|
||||||
const node = nodeLookup.get(id)?.internals.userNode;
|
const node = store.nodeLookup.get(id)?.internals.userNode;
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
continue;
|
continue;
|
||||||
@@ -96,33 +68,33 @@ export function createStore({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function updateNodeInternals(updates: Map<string, InternalNodeUpdate>) {
|
function updateNodeInternals(updates: Map<string, InternalNodeUpdate>) {
|
||||||
const nodeLookup = get(store.nodeLookup);
|
|
||||||
const parentLookup = get(store.parentLookup);
|
|
||||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||||
updates,
|
updates,
|
||||||
nodeLookup,
|
store.nodeLookup,
|
||||||
get(store.parentLookup),
|
store.parentLookup,
|
||||||
get(store.domNode),
|
store.domNode,
|
||||||
get(store.nodeOrigin)
|
store.nodeOrigin
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!updatedInternals) {
|
if (!updatedInternals) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
|
updateAbsolutePositions(store.nodeLookup, store.parentLookup, {
|
||||||
|
nodeOrigin: store.nodeOrigin,
|
||||||
|
nodeExtent: store.nodeExtent
|
||||||
|
});
|
||||||
|
|
||||||
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
|
if (!store.fitViewOnInitDone && store.fitViewOnInit) {
|
||||||
const fitViewOptions = get(store.fitViewOptions);
|
|
||||||
const fitViewOnInitDone = fitViewSync({
|
const fitViewOnInitDone = fitViewSync({
|
||||||
...fitViewOptions,
|
...store.fitViewOptions,
|
||||||
nodes: fitViewOptions?.nodes
|
nodes: store.fitViewOptions?.nodes
|
||||||
});
|
});
|
||||||
store.fitViewOnInitDone.set(fitViewOnInitDone);
|
store.fitViewOnInitDone = fitViewOnInitDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const node = nodeLookup.get(change.id)?.internals.userNode;
|
const node = store.nodeLookup.get(change.id)?.internals.userNode;
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
continue;
|
continue;
|
||||||
@@ -149,14 +121,14 @@ export function createStore({
|
|||||||
|
|
||||||
store.nodes.update((nds) => nds);
|
store.nodes.update((nds) => nds);
|
||||||
|
|
||||||
if (!get(store.nodesInitialized)) {
|
if (!store.nodesInitialized) {
|
||||||
store.nodesInitialized.set(true);
|
store.nodesInitialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitView(options?: FitViewOptions) {
|
function fitView(options?: FitViewOptions) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
const domNode = get(store.domNode);
|
const domNode = store.domNode;
|
||||||
|
|
||||||
if (!panZoom || !domNode) {
|
if (!panZoom || !domNode) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
@@ -164,15 +136,15 @@ export function createStore({
|
|||||||
|
|
||||||
const { width, height } = getDimensions(domNode);
|
const { width, height } = getDimensions(domNode);
|
||||||
|
|
||||||
const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
|
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
|
||||||
|
|
||||||
return fitViewSystem(
|
return fitViewSystem(
|
||||||
{
|
{
|
||||||
nodes: fitViewNodes,
|
nodes: fitViewNodes,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
minZoom: get(store.minZoom),
|
minZoom: store.minZoom,
|
||||||
maxZoom: get(store.maxZoom),
|
maxZoom: store.maxZoom,
|
||||||
panZoom
|
panZoom
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
@@ -180,21 +152,21 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fitViewSync(options?: FitViewOptions) {
|
function fitViewSync(options?: FitViewOptions) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
|
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
|
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
|
||||||
|
|
||||||
fitViewSystem(
|
fitViewSystem(
|
||||||
{
|
{
|
||||||
nodes: fitViewNodes,
|
nodes: fitViewNodes,
|
||||||
width: get(store.width),
|
width: store.width,
|
||||||
height: get(store.height),
|
height: store.height,
|
||||||
minZoom: get(store.minZoom),
|
minZoom: store.minZoom,
|
||||||
maxZoom: get(store.maxZoom),
|
maxZoom: store.maxZoom,
|
||||||
panZoom
|
panZoom
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
@@ -204,7 +176,7 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
|
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
@@ -221,29 +193,29 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setMinZoom(minZoom: number) {
|
function setMinZoom(minZoom: number) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
|
|
||||||
if (panZoom) {
|
if (panZoom) {
|
||||||
panZoom.setScaleExtent([minZoom, get(store.maxZoom)]);
|
panZoom.setScaleExtent([minZoom, store.maxZoom]);
|
||||||
store.minZoom.set(minZoom);
|
store.minZoom = minZoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMaxZoom(maxZoom: number) {
|
function setMaxZoom(maxZoom: number) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
|
|
||||||
if (panZoom) {
|
if (panZoom) {
|
||||||
panZoom.setScaleExtent([get(store.minZoom), maxZoom]);
|
panZoom.setScaleExtent([store.minZoom, maxZoom]);
|
||||||
store.maxZoom.set(maxZoom);
|
store.maxZoom = maxZoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTranslateExtent(extent: CoordinateExtent) {
|
function setTranslateExtent(extent: CoordinateExtent) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = store.panZoom;
|
||||||
|
|
||||||
if (panZoom) {
|
if (panZoom) {
|
||||||
panZoom.setTranslateExtent(extent);
|
panZoom.setTranslateExtent(extent);
|
||||||
store.translateExtent.set(extent);
|
store.translateExtent = extent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +231,7 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setPaneClickDistance(distance: number) {
|
function setPaneClickDistance(distance: number) {
|
||||||
get(store.panZoom)?.setClickDistance(distance);
|
store.panZoom?.setClickDistance(distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
|
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
|
||||||
@@ -270,39 +242,8 @@ export function createStore({
|
|||||||
if (resetEdges) store.edges.set(get(store.edges));
|
if (resetEdges) store.edges.set(get(store.edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
// store.deleteKeyPressed.subscribe(async (deleteKeyPressed) => {
|
|
||||||
// if (deleteKeyPressed) {
|
|
||||||
// const nodes = get(store.nodes);
|
|
||||||
// const edges = get(store.edges);
|
|
||||||
// const selectedNodes = nodes.filter((node) => node.selected);
|
|
||||||
// const selectedEdges = edges.filter((edge) => edge.selected);
|
|
||||||
|
|
||||||
// const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
|
|
||||||
// nodesToRemove: selectedNodes,
|
|
||||||
// edgesToRemove: selectedEdges,
|
|
||||||
// nodes,
|
|
||||||
// edges,
|
|
||||||
// onBeforeDelete: get(store.onbeforedelete)
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (matchingNodes.length || matchingEdges.length) {
|
|
||||||
// store.nodes.update((nds) =>
|
|
||||||
// nds.filter((node) => !matchingNodes.some((mN) => mN.id === node.id))
|
|
||||||
// );
|
|
||||||
// store.edges.update((eds) =>
|
|
||||||
// eds.filter((edge) => !matchingEdges.some((mE) => mE.id === edge.id))
|
|
||||||
// );
|
|
||||||
|
|
||||||
// get(store.ondelete)?.({
|
|
||||||
// nodes: matchingNodes,
|
|
||||||
// edges: matchingEdges
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
function addSelectedNodes(ids: string[]) {
|
function addSelectedNodes(ids: string[]) {
|
||||||
const isMultiSelection = get(store.multiselectionKeyPressed);
|
const isMultiSelection = store.multiselectionKeyPressed;
|
||||||
|
|
||||||
store.nodes.update((ns) =>
|
store.nodes.update((ns) =>
|
||||||
ns.map((node) => {
|
ns.map((node) => {
|
||||||
@@ -329,7 +270,7 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addSelectedEdges(ids: string[]) {
|
function addSelectedEdges(ids: string[]) {
|
||||||
const isMultiSelection = get(store.multiselectionKeyPressed);
|
const isMultiSelection = store.multiselectionKeyPressed;
|
||||||
|
|
||||||
store.edges.update((edges) =>
|
store.edges.update((edges) =>
|
||||||
edges.map((edge) => {
|
edges.map((edge) => {
|
||||||
@@ -362,12 +303,12 @@ export function createStore({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.selectionRect.set(null);
|
store.selectionRect = null;
|
||||||
store.selectionRectMode.set(null);
|
store.selectionRectMode = null;
|
||||||
|
|
||||||
if (!node.selected) {
|
if (!node.selected) {
|
||||||
addSelectedNodes([id]);
|
addSelectedNodes([id]);
|
||||||
} else if (node.selected && get(store.multiselectionKeyPressed)) {
|
} else if (node.selected && store.multiselectionKeyPressed) {
|
||||||
unselectNodesAndEdges({ nodes: [node], edges: [] });
|
unselectNodesAndEdges({ nodes: [node], edges: [] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,78 +317,33 @@ export function createStore({
|
|||||||
const viewport = get(store.viewport);
|
const viewport = get(store.viewport);
|
||||||
return panBySystem({
|
return panBySystem({
|
||||||
delta,
|
delta,
|
||||||
panZoom: get(store.panZoom),
|
panZoom: store.panZoom,
|
||||||
transform: [viewport.x, viewport.y, viewport.zoom],
|
transform: [viewport.x, viewport.y, viewport.zoom],
|
||||||
translateExtent: get(store.translateExtent),
|
translateExtent: store.translateExtent,
|
||||||
width: get(store.width),
|
width: store.width,
|
||||||
height: get(store.height)
|
height: store.height
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const _connection = writable<ConnectionState>(initialConnection);
|
// const _connection = writable<ConnectionState>(initialConnection);
|
||||||
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
|
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
|
||||||
_connection.set({ ...newConnection });
|
store.rawConnection = { ...newConnection };
|
||||||
};
|
};
|
||||||
|
|
||||||
function cancelConnection() {
|
function cancelConnection() {
|
||||||
_connection.set(initialConnection);
|
store.rawConnection = initialConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
store.fitViewOnInitDone.set(false);
|
store.fitViewOnInitDone = false;
|
||||||
store.selectionRect.set(null);
|
store.selectionRect = null;
|
||||||
store.selectionRectMode.set(null);
|
store.selectionRectMode = null;
|
||||||
store.snapGrid.set(null);
|
|
||||||
store.isValidConnection.set(() => true);
|
|
||||||
|
|
||||||
unselectNodesAndEdges();
|
unselectNodesAndEdges();
|
||||||
cancelConnection();
|
cancelConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const storeWithActions = Object.assign(store, {
|
||||||
// state
|
|
||||||
...store,
|
|
||||||
|
|
||||||
// derived state
|
|
||||||
visibleEdges: getVisibleEdges(store),
|
|
||||||
visibleNodes: getVisibleNodes(store),
|
|
||||||
connection: derived([_connection, store.viewport], ([connection, viewport]) => {
|
|
||||||
return connection.inProgress
|
|
||||||
? {
|
|
||||||
...connection,
|
|
||||||
to: pointToRendererPoint(connection.to, [viewport.x, viewport.y, viewport.zoom])
|
|
||||||
}
|
|
||||||
: { ...connection };
|
|
||||||
}),
|
|
||||||
markers: derived(
|
|
||||||
[store.edges, store.defaultMarkerColor, store.flowId],
|
|
||||||
([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
|
||||||
),
|
|
||||||
initialized: (() => {
|
|
||||||
let initialized = false;
|
|
||||||
const initialNodesLength = get(store.nodes).length;
|
|
||||||
const initialEdgesLength = get(store.edges).length;
|
|
||||||
return derived(
|
|
||||||
[store.nodesInitialized, store.edgesInitialized, store.viewportInitialized],
|
|
||||||
([nodesInitialized, edgesInitialized, viewportInitialized]) => {
|
|
||||||
// If it was already initialized, return true from then on
|
|
||||||
if (initialized) return initialized;
|
|
||||||
|
|
||||||
// if it hasn't been initialised check if it's now
|
|
||||||
if (initialNodesLength === 0) {
|
|
||||||
initialized = viewportInitialized;
|
|
||||||
} else if (initialEdgesLength === 0) {
|
|
||||||
initialized = viewportInitialized && nodesInitialized;
|
|
||||||
} else {
|
|
||||||
initialized = viewportInitialized && nodesInitialized && edgesInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialized;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})(),
|
|
||||||
|
|
||||||
// actions
|
|
||||||
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
|
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
|
||||||
syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
|
syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
|
||||||
syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
|
syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
|
||||||
@@ -471,43 +367,76 @@ export function createStore({
|
|||||||
updateConnection,
|
updateConnection,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
reset
|
reset
|
||||||
};
|
} satisfies SvelteFlowStoreActions);
|
||||||
}
|
|
||||||
|
|
||||||
export function useStore(): SvelteFlowStore {
|
return storeWithActions;
|
||||||
const store = getContext<{ getStore: () => SvelteFlowStore }>(key);
|
|
||||||
|
|
||||||
if (!store) {
|
// return {
|
||||||
throw new Error(
|
// // state
|
||||||
'In order to use useStore you need to wrap your component in a <SvelteFlowProvider />'
|
// ...store,
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.getStore();
|
// // derived state
|
||||||
}
|
// // visibleEdges: getVisibleEdges(store),
|
||||||
|
// // visibleNodes: getVisibleNodes(store),
|
||||||
|
// // connection: derived([_connection, store.viewport], ([connection, viewport]) => {
|
||||||
|
// // return connection.inProgress
|
||||||
|
// // ? {
|
||||||
|
// // ...connection,
|
||||||
|
// // to: pointToRendererPoint(connection.to, [viewport.x, viewport.y, viewport.zoom])
|
||||||
|
// // }
|
||||||
|
// // : { ...connection };
|
||||||
|
// // }),
|
||||||
|
// // markers: derived(
|
||||||
|
// // [store.edges, store.defaultMarkerColor, store.flowId],
|
||||||
|
// // ([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
||||||
|
// // ),
|
||||||
|
// // initialized: (() => {
|
||||||
|
// // let initialized = false;
|
||||||
|
// // const initialNodesLength = get(store.nodes).length;
|
||||||
|
// // const initialEdgesLength = get(store.edges).length;
|
||||||
|
// // return derived(
|
||||||
|
// // [store.nodesInitialized, store.edgesInitialized, store.viewportInitialized],
|
||||||
|
// // ([nodesInitialized, edgesInitialized, viewportInitialized]) => {
|
||||||
|
// // // If it was already initialized, return true from then on
|
||||||
|
// // if (initialized) return initialized;
|
||||||
|
|
||||||
export function createStoreContext({
|
// // // if it hasn't been initialised check if it's now
|
||||||
nodes,
|
// // if (initialNodesLength === 0) {
|
||||||
edges,
|
// // initialized = viewportInitialized;
|
||||||
width,
|
// // } else if (initialEdgesLength === 0) {
|
||||||
height,
|
// // initialized = viewportInitialized && nodesInitialized;
|
||||||
fitView,
|
// // } else {
|
||||||
nodeOrigin,
|
// // initialized = viewportInitialized && nodesInitialized && edgesInitialized;
|
||||||
nodeExtent
|
// // }
|
||||||
}: {
|
|
||||||
nodes?: Node[];
|
|
||||||
edges?: Edge[];
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
fitView?: boolean;
|
|
||||||
nodeOrigin?: NodeOrigin;
|
|
||||||
nodeExtent?: CoordinateExtent;
|
|
||||||
}) {
|
|
||||||
const store = createStore({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent });
|
|
||||||
|
|
||||||
setContext(key, {
|
// // return initialized;
|
||||||
getStore: () => store
|
// // }
|
||||||
});
|
// // );
|
||||||
|
// // })(),
|
||||||
|
|
||||||
return store;
|
// // actions
|
||||||
|
// syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
|
||||||
|
// syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
|
||||||
|
// // syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
|
||||||
|
// setNodeTypes,
|
||||||
|
// setEdgeTypes,
|
||||||
|
// addEdge,
|
||||||
|
// updateNodePositions,
|
||||||
|
// updateNodeInternals,
|
||||||
|
// zoomIn,
|
||||||
|
// zoomOut,
|
||||||
|
// fitView: (options?: FitViewOptions) => fitView(options),
|
||||||
|
// setMinZoom,
|
||||||
|
// setMaxZoom,
|
||||||
|
// setTranslateExtent,
|
||||||
|
// setPaneClickDistance,
|
||||||
|
// unselectNodesAndEdges,
|
||||||
|
// addSelectedNodes,
|
||||||
|
// addSelectedEdges,
|
||||||
|
// handleNodeSelection,
|
||||||
|
// panBy,
|
||||||
|
// updateConnection,
|
||||||
|
// cancelConnection,
|
||||||
|
// reset
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
import { get, writable, type 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 OnConnect,
|
||||||
|
type OnConnectStart,
|
||||||
|
type OnConnectEnd,
|
||||||
|
type NodeLookup,
|
||||||
|
type ConnectionState,
|
||||||
|
getInternalNodesBounds,
|
||||||
|
type ColorMode,
|
||||||
|
createMarkerIds,
|
||||||
|
type EdgeLookup,
|
||||||
|
type ConnectionLookup,
|
||||||
|
type ParentLookup,
|
||||||
|
pointToRendererPoint
|
||||||
|
} 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,
|
||||||
|
FitViewOptions,
|
||||||
|
OnDelete,
|
||||||
|
OnEdgeCreate,
|
||||||
|
OnBeforeDelete,
|
||||||
|
IsValidConnection
|
||||||
|
} from '$lib/types';
|
||||||
|
import { createNodesStore, createEdgesStore } from './utils';
|
||||||
|
import type { StoreSignals } from './types';
|
||||||
|
|
||||||
|
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 = (signals: StoreSignals) => {
|
||||||
|
class SvelteFlowStore {
|
||||||
|
nodes: ReturnType<typeof createNodesStore>;
|
||||||
|
edges: ReturnType<typeof createEdgesStore>;
|
||||||
|
|
||||||
|
nodeLookup: NodeLookup = new Map();
|
||||||
|
parentLookup: ParentLookup = new Map();
|
||||||
|
connectionLookup: ConnectionLookup = new Map();
|
||||||
|
edgeLookup: EdgeLookup = new Map();
|
||||||
|
|
||||||
|
domNode: HTMLDivElement | null = $derived(
|
||||||
|
signals.container?.domNode ?? signals.props.domNode ?? null
|
||||||
|
);
|
||||||
|
width: number = $derived(signals.container?.width ?? signals.props.width ?? 0);
|
||||||
|
height: number = $derived(signals.container?.height ?? signals.props.height ?? 0);
|
||||||
|
|
||||||
|
flowId: string = $derived(signals.props.id ?? '1');
|
||||||
|
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
|
||||||
|
maxZoom: number = $derived(signals.props.maxZoom ?? 2);
|
||||||
|
|
||||||
|
nodeOrigin: NodeOrigin = $derived(signals.props.nodeOrigin ?? [0, 0]);
|
||||||
|
nodeExtent: CoordinateExtent = $derived(signals.props.nodeExtent ?? infiniteExtent);
|
||||||
|
translateExtent: CoordinateExtent = $derived(signals.props.translateExtent ?? infiniteExtent);
|
||||||
|
|
||||||
|
nodeDragThreshold: number = $derived(signals.props.nodeDragThreshold ?? 1);
|
||||||
|
autoPanOnNodeDrag: boolean = $derived(signals.props.autoPanOnNodeDrag ?? true);
|
||||||
|
autoPanOnConnect: boolean = $derived(signals.props.autoPanOnConnect ?? true);
|
||||||
|
|
||||||
|
fitViewOnInitDone: boolean = $state(false);
|
||||||
|
fitViewOnInit: boolean = $derived(signals.props.fitView ?? false);
|
||||||
|
fitViewOptions: FitViewOptions | undefined = $derived(signals.props.fitViewOptions);
|
||||||
|
|
||||||
|
panZoom: PanZoomInstance | null = $state(null);
|
||||||
|
snapGrid: SnapGrid | null = $derived(signals.props.snapGrid ?? null);
|
||||||
|
|
||||||
|
dragging: boolean = $state(false);
|
||||||
|
selectionRect: SelectionRect | null = $state(null);
|
||||||
|
|
||||||
|
selectionKeyPressed: boolean = $state(false);
|
||||||
|
multiselectionKeyPressed: boolean = $state(false);
|
||||||
|
deleteKeyPressed: boolean = $state(false);
|
||||||
|
panActivationKeyPressed: boolean = $state(false);
|
||||||
|
zoomActivationKeyPressed: boolean = $state(false);
|
||||||
|
selectionRectMode: string | null = $state(null);
|
||||||
|
selectionMode: SelectionMode = $derived(signals.props.selectionMode ?? SelectionMode.Partial);
|
||||||
|
|
||||||
|
nodeTypes: NodeTypes = $derived({ ...initialNodeTypes, ...signals.props.nodeTypes });
|
||||||
|
edgeTypes: EdgeTypes = $derived({ ...initialEdgeTypes, ...signals.props.edgeTypes });
|
||||||
|
|
||||||
|
viewport: Writable<Viewport> = writable(
|
||||||
|
signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionMode: ConnectionMode = $derived(
|
||||||
|
signals.props.connectionMode ?? ConnectionMode.Strict
|
||||||
|
);
|
||||||
|
rawConnection: ConnectionState = $state(initialConnection);
|
||||||
|
connection: ConnectionState = $derived.by(() => {
|
||||||
|
if (this.rawConnection.inProgress) {
|
||||||
|
const viewport = get(this.viewport);
|
||||||
|
return {
|
||||||
|
...this.rawConnection,
|
||||||
|
to: pointToRendererPoint(this.rawConnection.to, [viewport.x, viewport.y, viewport.zoom])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { ...this.rawConnection };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connectionLineType: ConnectionLineType = $derived(
|
||||||
|
signals.props.connectionLineType ?? ConnectionLineType.Bezier
|
||||||
|
);
|
||||||
|
connectionRadius: number = $derived(signals.props.connectionRadius ?? 20);
|
||||||
|
isValidConnection: IsValidConnection = $derived(
|
||||||
|
signals.props.isValidConnection ?? (() => true)
|
||||||
|
);
|
||||||
|
nodesDraggable: boolean = $derived(signals.props.nodesDraggable ?? true);
|
||||||
|
nodesConnectable: boolean = $derived(signals.props.nodesConnectable ?? true);
|
||||||
|
elementsSelectable: boolean = $derived(signals.props.elementsSelectable ?? true);
|
||||||
|
selectNodesOnDrag: boolean = $derived(signals.props.selectNodesOnDrag ?? true);
|
||||||
|
// // [store.edges, store.defaultMarkerColor, store.flowId],
|
||||||
|
// // ([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
||||||
|
defaultMarkerColor: string = $derived(signals.props.defaultMarkerColor ?? '#b1b1b7');
|
||||||
|
markers: MarkerProps[] = $derived.by(() => {
|
||||||
|
const edges = signals.props.edges ?? writable([]);
|
||||||
|
return createMarkerIds(get(edges), {
|
||||||
|
defaultColor: this.defaultMarkerColor,
|
||||||
|
id: this.flowId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onlyRenderVisibleElements: boolean = $derived(signals.props.onlyRenderVisibleElements ?? false);
|
||||||
|
onerror: OnError = $derived(signals.props.onerror ?? devWarn);
|
||||||
|
ondelete?: OnDelete = $derived(signals.props.ondelete ?? undefined);
|
||||||
|
onedgecreate?: OnEdgeCreate = $derived(signals.props.onedgecreate);
|
||||||
|
onconnect?: OnConnect = $derived(signals.props.onconnect);
|
||||||
|
onconnectstart?: OnConnectStart = $derived(signals.props.onconnectstart);
|
||||||
|
onconnectend?: OnConnectEnd = $derived(signals.props.onconnectend);
|
||||||
|
onbeforedelete?: OnBeforeDelete = $derived(signals.props.onbeforedelete);
|
||||||
|
|
||||||
|
nodesInitialized: boolean = $state(false);
|
||||||
|
edgesInitialized: boolean = $state(false);
|
||||||
|
viewportInitialized: boolean = $state(false);
|
||||||
|
|
||||||
|
initialNodesLength: number = signals.props.nodes ? get(signals.props.nodes)?.length : 0;
|
||||||
|
initialEdgesLength: number = signals.props.edges ? get(signals.props.edges)?.length : 0;
|
||||||
|
initialized: boolean = $derived.by(() => {
|
||||||
|
let initialized = false;
|
||||||
|
// if it hasn't been initialised check if it's now
|
||||||
|
if (this.initialNodesLength === 0) {
|
||||||
|
initialized = this.viewportInitialized;
|
||||||
|
} else if (this.initialEdgesLength === 0) {
|
||||||
|
initialized = this.viewportInitialized && this.nodesInitialized;
|
||||||
|
} else {
|
||||||
|
initialized = this.viewportInitialized && this.nodesInitialized && this.edgesInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialized;
|
||||||
|
});
|
||||||
|
colorMode: ColorMode = $derived(signals.props.colorMode ?? 'light');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const nodes = signals.props.nodes ? get(signals.props.nodes) : [];
|
||||||
|
const edges = signals.props.edges ? get(signals.props.edges) : [];
|
||||||
|
|
||||||
|
this.nodes = createNodesStore(
|
||||||
|
nodes,
|
||||||
|
this.nodeLookup,
|
||||||
|
this.parentLookup,
|
||||||
|
this.nodeOrigin,
|
||||||
|
this.nodeExtent
|
||||||
|
);
|
||||||
|
|
||||||
|
adoptUserNodes(nodes, this.nodeLookup, this.parentLookup, {
|
||||||
|
nodeExtent: this.nodeExtent,
|
||||||
|
nodeOrigin: this.nodeOrigin,
|
||||||
|
elevateNodesOnSelect: false,
|
||||||
|
checkEquality: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.edges = createEdgesStore(edges, this.connectionLookup, this.edgeLookup);
|
||||||
|
updateConnectionLookup(this.connectionLookup, this.edgeLookup, edges);
|
||||||
|
|
||||||
|
if (signals.props.fitView && this.width && this.height) {
|
||||||
|
const bounds = getInternalNodesBounds(this.nodeLookup, {
|
||||||
|
filter: (node) =>
|
||||||
|
!!((node.width || node.initialWidth) && (node.height || node.initialHeight))
|
||||||
|
});
|
||||||
|
this.viewport.set(getViewportForBounds(bounds, this.width, this.height, 0.5, 2, 0.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStoreValues() {
|
||||||
|
// TODO: reset all store values here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
// };
|
||||||
|
};
|
||||||
@@ -10,8 +10,9 @@ import type {
|
|||||||
Viewport
|
Viewport
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { getInitialStore } from './initial-store';
|
import type { getInitialStore } from './initial-store.svelte';
|
||||||
import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/types';
|
import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/types';
|
||||||
|
import type { SvelteFlowProps } from '$lib/container/SvelteFlow';
|
||||||
|
|
||||||
export type SvelteFlowStoreActions = {
|
export type SvelteFlowStoreActions = {
|
||||||
syncNodeStores: (nodesStore: Writable<Node[]>) => void;
|
syncNodeStores: (nodesStore: Writable<Node[]>) => void;
|
||||||
@@ -39,6 +40,21 @@ export type SvelteFlowStoreActions = {
|
|||||||
reset(): void;
|
reset(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ContainerSignals = { domNode?: HTMLDivElement | null; width?: number; height?: number };
|
||||||
|
export type StoreSignals = {
|
||||||
|
props: Partial<SvelteFlowProps> & ContainerSignals;
|
||||||
|
container?: ContainerSignals;
|
||||||
|
};
|
||||||
|
|
||||||
export type SvelteFlowStoreState = ReturnType<typeof getInitialStore>;
|
export type SvelteFlowStoreState = ReturnType<typeof getInitialStore>;
|
||||||
|
|
||||||
export type SvelteFlowStore = SvelteFlowStoreState & SvelteFlowStoreActions;
|
export type SvelteFlowStore = SvelteFlowStoreState & SvelteFlowStoreActions;
|
||||||
|
|
||||||
|
export type StoreContext = {
|
||||||
|
getStore: () => SvelteFlowStore;
|
||||||
|
provider: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProviderContext = StoreContext & {
|
||||||
|
setStore: (store: SvelteFlowStore) => void;
|
||||||
|
};
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export function syncEdgeStores(
|
|||||||
// it is possible to pass a viewport store to SvelteFlow for having more control
|
// 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
|
// if that's the case we need to sync the internal viewport with the user viewport
|
||||||
export const syncViewportStores = (
|
export const syncViewportStores = (
|
||||||
panZoomStore: Writable<PanZoomInstance | null>,
|
panZoomStore: PanZoomInstance | null,
|
||||||
viewportStore: Writable<Viewport>,
|
viewportStore: Writable<Viewport>,
|
||||||
userViewportStore?: Writable<Viewport>
|
userViewportStore?: Writable<Viewport>
|
||||||
) => {
|
) => {
|
||||||
@@ -86,7 +86,7 @@ export const syncViewportStores = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const panZoom = get(panZoomStore);
|
const panZoom = panZoomStore;
|
||||||
|
|
||||||
const viewportStoreSetter = viewportStore.set;
|
const viewportStoreSetter = viewportStore.set;
|
||||||
const userViewportStoreSetter = userViewportStore.set;
|
const userViewportStoreSetter = userViewportStore.set;
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/** @typedef {typeof __propDef.props} PageProps */
|
||||||
|
/** @typedef {typeof __propDef.events} PageEvents */
|
||||||
|
/** @typedef {typeof __propDef.slots} PageSlots */
|
||||||
|
export default class Page extends SvelteComponentTyped<{
|
||||||
|
[x: string]: never;
|
||||||
|
}, {
|
||||||
|
[evt: string]: CustomEvent<any>;
|
||||||
|
}, {}> {
|
||||||
|
}
|
||||||
|
export type PageProps = typeof __propDef.props;
|
||||||
|
export type PageEvents = typeof __propDef.events;
|
||||||
|
export type PageSlots = typeof __propDef.slots;
|
||||||
|
import { SvelteComponentTyped } from "svelte";
|
||||||
|
declare const __propDef: {
|
||||||
|
props: {
|
||||||
|
[x: string]: never;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
[evt: string]: CustomEvent<any>;
|
||||||
|
};
|
||||||
|
slots: {};
|
||||||
|
};
|
||||||
|
export {};
|
||||||
Generated
+536
-211
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user