streamlined connections
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user