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