streamlined connections

This commit is contained in:
peterkogo
2024-06-26 15:03:12 +02:00
parent 44254648c8
commit 5493f045ff
25 changed files with 355 additions and 474 deletions
@@ -2,21 +2,59 @@
import cc from 'classcat';
import { useStore } from '$lib/store';
import {
ConnectionLineType,
getBezierPath,
getConnectionStatus,
getSmoothStepPath,
getStraightPath
} from '@xyflow/system';
export let containerStyle: string = '';
export let style: string = '';
export let isCustomComponent: boolean = false;
const { width, height, connection } = useStore();
const { width, height, connection, connectionLineType } = useStore();
let path: string | null = null;
$: if ($connection.inProgress && !isCustomComponent) {
const { from, to, fromPosition, toPosition } = $connection;
const pathParams = {
sourceX: from.x,
sourceY: from.y,
sourcePosition: fromPosition,
targetX: to.x,
targetY: to.y,
targetPosition: toPosition
};
switch ($connectionLineType) {
case ConnectionLineType.Bezier:
[path] = getBezierPath(pathParams);
break;
case ConnectionLineType.Step:
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
break;
case ConnectionLineType.SmoothStep:
[path] = getSmoothStepPath(pathParams);
break;
default:
[path] = getStraightPath(pathParams);
}
}
</script>
{#if $connection.path}
{#if $connection.inProgress}
<svg width={$width} height={$height} class="svelte-flow__connectionline" style={containerStyle}>
<g class={cc(['svelte-flow__connection', $connection.status])}>
<g class={cc(['svelte-flow__connection', getConnectionStatus($connection.isValid)])}>
<slot name="connectionLine" />
<!-- slot fallbacks do not work if slots are forwarded in parent -->
{#if !isCustomComponent}
<path d={$connection.path} {style} fill="none" class="svelte-flow__connection-path" />
<path d={path} {style} fill="none" class="svelte-flow__connection-path" />
{/if}
</g>
</svg>
@@ -103,7 +103,7 @@
$onConnectEndAction?.(event);
},
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
getFromHandle: () => $connection.startHandle
getFromHandle: () => $connection.fromHandle
});
}
}
@@ -128,21 +128,20 @@
prevConnections = connections ?? new Map();
}
$: connectionInProcess = !!$connection.startHandle;
$: connectionInProcess = !!$connection.fromHandle;
$: connectingFrom =
$connection.startHandle?.nodeId === nodeId &&
$connection.startHandle?.type === type &&
$connection.startHandle?.handleId === handleId;
$connection.fromHandle?.nodeId === nodeId &&
$connection.fromHandle?.type === type &&
$connection.fromHandle?.id === handleId;
$: connectingTo =
$connection.endHandle?.nodeId === nodeId &&
$connection.endHandle?.type === type &&
$connection.endHandle?.handleId === handleId;
$connection.toHandle?.nodeId === nodeId &&
$connection.toHandle?.type === type &&
$connection.toHandle?.id === handleId;
$: isPossibleEndHandle =
$connectionMode === ConnectionMode.Strict
? $connection.startHandle?.type !== type
: nodeId !== $connection.startHandle?.nodeId ||
handleId !== $connection.startHandle?.handleId;
$: valid = connectingTo && $connection.status === 'valid';
? $connection.fromHandle?.type !== type
: nodeId !== $connection.fromHandle?.nodeId || handleId !== $connection.fromHandle?.id;
$: valid = connectingTo && $connection.isValid;
</script>
<!--
@@ -1,7 +1,7 @@
import type { Readable } from 'svelte/store';
import { useStore } from '$lib/store';
import type { ConnectionProps } from '$lib/store/derived-connection-props';
import type { ConnectionState } from '@xyflow/system';
/**
* Hook for receiving the current connection.
@@ -9,7 +9,7 @@ import type { ConnectionProps } from '$lib/store/derived-connection-props';
* @public
* @returns current connection as a readable store
*/
export function useConnection(): Readable<ConnectionProps> {
export function useConnection(): Readable<ConnectionState> {
const { connection } = useStore();
return connection;
@@ -1,130 +0,0 @@
import { derived, type Writable } from 'svelte/store';
import {
getBezierPath,
getSmoothStepPath,
getStraightPath,
ConnectionLineType,
ConnectionMode,
Position,
type HandleElement
} from '@xyflow/system';
import type { SvelteFlowStoreState } from './types';
import type { ConnectionData } from '$lib/types';
export type ConnectionProps = {
path: string | null;
sourceX: number | null;
sourceY: number | null;
sourcePosition: Position | undefined | null;
targetX: number | null;
targetY: number | null;
targetPosition: Position | undefined | null;
pointerPosition: ConnectionData['connectionPosition'] | null;
startHandle: ConnectionData['connectionStartHandle'] | null;
endHandle: ConnectionData['connectionEndHandle'] | null;
status: ConnectionData['connectionStatus'] | null;
};
export const initConnectionProps = {
path: null,
sourceX: null,
sourceY: null,
sourcePosition: null,
targetX: null,
targetY: null,
targetPosition: null,
pointerPosition: null,
startHandle: null,
endHandle: null,
status: null
};
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
export function getDerivedConnectionProps(
store: SvelteFlowStoreState,
currentConnection: Writable<ConnectionData>
) {
return derived(
[
currentConnection,
store.connectionLineType,
store.connectionMode,
store.nodeLookup,
store.viewport
],
([connection, connectionLineType, connectionMode, nodeLookup, viewport]) => {
if (!connection.connectionStartHandle?.nodeId) {
return initConnectionProps;
}
// TODO: it should bail out if the node is not found
const fromNode = nodeLookup.get(connection.connectionStartHandle?.nodeId);
const fromHandleBounds = fromNode?.internals.handleBounds;
const handleBoundsStrict =
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
const handleBoundsLoose: HandleElement[] | undefined | null = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[
connection?.connectionStartHandle?.type === 'source' ? 'target' : 'source'
];
const handleBounds =
connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = connection.connectionStartHandle?.handleId
? handleBounds?.find((d) => d.id === connection.connectionStartHandle?.handleId)
: handleBounds?.[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.measured.width ?? 0) / 2;
const fromHandleY = fromHandle
? fromHandle.y + fromHandle.height / 2
: fromNode?.measured.height ?? 0;
const fromX = (fromNode?.internals.positionAbsolute.x ?? 0) + fromHandleX;
const fromY = (fromNode?.internals.positionAbsolute.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition =
connection.connectionEndHandle?.position ??
(fromPosition ? oppositePosition[fromPosition] : undefined);
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: ((connection.connectionPosition?.x ?? 0) - viewport.x) / viewport.zoom,
targetY: ((connection.connectionPosition?.y ?? 0) - viewport.y) / viewport.zoom,
targetPosition: toPosition
};
let path = '';
if (connectionLineType === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if (connectionLineType === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return {
path,
...pathParams,
pointerPosition: connection.connectionPosition,
startHandle: connection.connectionStartHandle,
endHandle: connection.connectionEndHandle,
status: connection.connectionStatus
};
}
);
}
+6 -22
View File
@@ -1,5 +1,5 @@
import { getContext, setContext } from 'svelte';
import { derived, get, writable } from 'svelte/store';
import { derived, get } from 'svelte/store';
import {
createMarkerIds,
fitView as fitViewSystem,
@@ -7,6 +7,8 @@ import {
panBy as panBySystem,
updateNodeInternals as updateNodeInternalsSystem,
addEdge as addEdgeUtil,
initialConnection,
errorMessages,
type UpdateNodePositions,
type InternalNodeUpdate,
type ViewportHelperFunctionOptions,
@@ -14,17 +16,15 @@ import {
type XYPosition,
type CoordinateExtent,
type UpdateConnection,
errorMessages,
type ConnectionState
} from '@xyflow/system';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types';
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
import { getVisibleEdges } from './visible-edges';
import { getVisibleNodes } from './visible-nodes';
import { getDerivedConnectionProps } from './derived-connection-props';
export const key = Symbol();
@@ -331,27 +331,12 @@ export function createStore({
});
}
const initConnectionUpdateData = {
connectionStartHandle: null,
connectionEndHandle: null,
connectionPosition: null,
connectionStatus: null
};
// by creating an internal, unexposed store and using a derived store
// we prevent using slow get() calls
const currentConnection = writable<ConnectionData>(initConnectionUpdateData);
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
currentConnection.set({
connectionStartHandle: newConnection.fromHandle,
connectionEndHandle: newConnection.toHandle,
connectionPosition: newConnection.position,
connectionStatus: newConnection.isValid ? 'valid' : 'invalid'
});
store.connection.set({ ...newConnection });
};
function cancelConnection() {
currentConnection.set(initConnectionUpdateData);
store.connection.set(initialConnection);
}
function reset() {
@@ -370,7 +355,6 @@ export function createStore({
...store,
// derived state
connection: getDerivedConnectionProps(store, currentConnection),
visibleEdges: getVisibleEdges(store),
visibleNodes: getVisibleNodes(store),
markers: derived(
@@ -9,6 +9,7 @@ import {
getNodesBounds,
getViewportForBounds,
updateConnectionLookup,
initialConnection,
type SelectionRect,
type SnapGrid,
type MarkerProps,
@@ -22,7 +23,8 @@ import {
type OnConnectStart,
type OnConnectEnd,
type NodeLookup,
type EdgeLookup
type EdgeLookup,
type ConnectionState
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -51,7 +53,6 @@ import type {
InternalNode
} from '$lib/types';
import { createNodesStore, createEdgesStore } from './utils';
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
export const initialNodeTypes = {
input: InputNode,
@@ -142,7 +143,7 @@ export const getInitialStore = ({
viewport: writable<Viewport>(viewport),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connection: readable<ConnectionProps>(initConnectionProps),
connection: writable<ConnectionState>(initialConnection),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
connectionRadius: writable<number>(20),
isValidConnection: writable<IsValidConnection>(() => true),
+3 -3
View File
@@ -2,7 +2,7 @@ import type { ShortcutModifierDefinition } from '@svelte-put/shortcut';
import type {
FitViewOptionsBase,
XYPosition,
ConnectingHandle,
Handle,
Connection,
OnBeforeDeleteBase,
HandleProps as HandlePropsSystem
@@ -17,8 +17,8 @@ export type KeyDefinition = string | KeyDefinitionObject;
export type ConnectionData = {
connectionPosition: XYPosition | null;
connectionStartHandle: ConnectingHandle | null;
connectionEndHandle: ConnectingHandle | null;
connectionStartHandle: Handle | null;
connectionEndHandle: Handle | null;
connectionStatus: string | null;
};