diff --git a/packages/svelte/src/example-components/Header/Header.svelte b/packages/svelte/src/example-components/Header/Header.svelte index edf34a58..078f8ed9 100644 --- a/packages/svelte/src/example-components/Header/Header.svelte +++ b/packages/svelte/src/example-components/Header/Header.svelte @@ -4,7 +4,8 @@ const routes = [ 'overview', 'stress', - 'usesvelteflow' + 'usesvelteflow', + 'validation' ]; const onChange = (event: Event) => { diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index dd5a3cc3..d26e98df 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -13,14 +13,14 @@ export let id: $$Props['id'] = undefined; export let type: $$Props['type'] = 'source'; export let position: $$Props['position'] = Position.Top; - export let isConnectable: $$Props['isConnectable'] = true; export let style: $$Props['style'] = undefined; - export let isValidConnection: $$Props['isValidConnection'] = (_: Connection) => true; let className: $$Props['class'] = undefined; export { className as class }; const isTarget = type === 'target'; - const nodeId = getContext('rf_nodeid'); + const nodeId = getContext('svelteflow__node_id'); + const connectable = getContext('svelteflow__node_connectable'); + const handleId = id || null; const dispatch = createEventDispatcher(); @@ -30,10 +30,11 @@ nodes, connectionRadius, transform, + isValidConnection, addEdge, panBy, cancelConnection, - updateConnection + updateConnection, } = useStore(); function dispatchEvent(eventName: string, params?: Connection) { @@ -56,10 +57,10 @@ isTarget, connectionRadius: $connectionRadius, domNode: $domNode, - nodes: $nodes, + nodes, connectionMode: $connectionMode, transform, - isValidConnection: isValidConnection!, + isValidConnection: $isValidConnection, onConnect: onConnectExtended, updateConnection, cancelConnection, @@ -86,7 +87,7 @@ ])} class:source={!isTarget} class:target={isTarget} - class:connectable={isConnectable} + class:connectable on:mousedown={onPointerDown} on:touchstart={onPointerDown} {style} diff --git a/packages/svelte/src/lib/components/Handle/handler.ts b/packages/svelte/src/lib/components/Handle/handler.ts index 972fd303..0275f333 100644 --- a/packages/svelte/src/lib/components/Handle/handler.ts +++ b/packages/svelte/src/lib/components/Handle/handler.ts @@ -22,10 +22,9 @@ import { getHandleType, isValidHandle, resetRecentHandle, - type ConnectionHandle, - type ValidConnectionFunc + type ConnectionHandle } from './utils'; -import type { ConnectionData, Node } from '$lib/types'; +import type { ConnectionData, IsValidConnection, Node } from '$lib/types'; export function handlePointerDown({ event, @@ -54,9 +53,9 @@ export function handlePointerDown({ isTarget: boolean; connectionMode: ConnectionMode; domNode: HTMLDivElement | null; - nodes: Node[]; + nodes: Writable; connectionRadius: number; - isValidConnection: ValidConnectionFunc; + isValidConnection: IsValidConnection; transform: Writable; updateConnection: (connection: Partial) => void; cancelConnection: () => void; @@ -90,7 +89,7 @@ export function handlePointerDown({ const autoPanOnConnect = true; const handleLookup = getHandleLookup({ - nodes, + nodes: get(nodes), nodeId, handleId, handleType @@ -116,8 +115,6 @@ export function handlePointerDown({ status: null }); - // @todo add prop - // onConnectStart?.(event, { nodeId, handleId, handleType }); onConnectStart(); @@ -144,7 +141,8 @@ export function handlePointerDown({ handleId, isTarget ? 'target' : 'source', isValidConnection, - doc + doc, + get(nodes) ); handleDomNode = result.handleDomNode; diff --git a/packages/svelte/src/lib/components/Handle/utils.ts b/packages/svelte/src/lib/components/Handle/utils.ts index 673831ad..18322718 100644 --- a/packages/svelte/src/lib/components/Handle/utils.ts +++ b/packages/svelte/src/lib/components/Handle/utils.ts @@ -2,7 +2,7 @@ import { internalsSymbol, ConnectionMode, type ConnectionStatus } from '@reactfl import type { Connection, HandleType, XYPosition, NodeHandleBounds } from '@reactflow/system'; import { getEventPosition } from '@reactflow/utils'; -import type { Node } from '$lib/types'; +import type { IsValidConnection, Node } from '$lib/types'; export type ConnectionHandle = { id: string | null; @@ -76,8 +76,9 @@ export function isValidHandle( fromNodeId: string, fromHandleId: string | null, fromType: string, - isValidConnection: ValidConnectionFunc, - doc: Document | ShadowRoot + isValidConnection: IsValidConnection, + doc: Document | ShadowRoot, + nodes: Node[] ) { const isTarget = fromType === 'target'; const handleDomNode = doc.querySelector( @@ -116,7 +117,10 @@ export function isValidHandle( : handleNodeId !== fromNodeId || handleId !== fromHandleId; if (isValid) { - result.isValid = isValidConnection(connection); + const fromNode: Node | undefined = nodes.find((n) => n.id === connection.source); + const toNode: Node | undefined = nodes.find((n) => n.id === connection.target); + + if (fromNode && toNode) result.isValid = isValidConnection(connection, { fromNode, toNode }); } } diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index ec79f3aa..bbbe42e5 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -15,6 +15,7 @@ export let data: NodeWrapperProps['data'] = {}; export let selected: NodeWrapperProps['selected'] = false; export let draggable: NodeWrapperProps['draggable'] = undefined; + export let connectable: NodeWrapperProps['connectable'] = true; export let dragging: boolean = false; export let resizeObserver: NodeWrapperProps['resizeObserver'] = null; export let style: NodeWrapperProps['style'] = undefined; @@ -45,7 +46,8 @@ const selectNodesOnDrag = false; const dispatch = createEventDispatcher(); - setContext('rf_nodeid', id); + setContext('svelteflow__node_id', id); + setContext('svelteflow__node_connectable', connectable); onMount(() => { resizeObserver?.observe(nodeRef); @@ -79,6 +81,7 @@ class:dragging class:selected class:draggable + class:connectable style:transform={`translate(${positionOrigin?.x ?? 0}px, ${positionOrigin?.y ?? 0}px)`} {style} on:click={onSelectNodeHandler} @@ -93,7 +96,6 @@ {selected} {sourcePosition} {targetPosition} - isConnectable={true} xPos={positionAbsolute?.x ?? 0} yPos={positionAbsolute?.y ?? 0} on:connect:start diff --git a/packages/svelte/src/lib/components/NodeWrapper/types.ts b/packages/svelte/src/lib/components/NodeWrapper/types.ts index 1dd04945..564aaa41 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/types.ts +++ b/packages/svelte/src/lib/components/NodeWrapper/types.ts @@ -5,6 +5,7 @@ export type NodeWrapperProps = Pick< Node, | 'id' | 'class' + | 'connectable' | 'data' | 'draggable' | 'dragging' diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte index 473e2ec9..6b99962a 100644 --- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte +++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte @@ -38,6 +38,7 @@ data={node.data} selected={node.selected} draggable={node.draggable || node.draggable === undefined} + connectable={node.connectable || node.connectable === undefined} positionAbsolute={node.positionAbsolute} positionOrigin={posOrigin} width={node.width} diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index 5c55c073..dbbb2a2b 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -27,7 +27,9 @@ export let selectionKey: $$Props['selectionKey'] = undefined; export let deleteKey: $$Props['deleteKey'] = undefined; export let connectionRadius: $$Props['connectionRadius'] = undefined; - export let connectionLineType: $$Props['connectionLineType'] = undefined + export let connectionLineType: $$Props['connectionLineType'] = undefined; + export let isValidConnection: $$Props['isValidConnection'] = undefined; + export let style: $$Props['style'] = undefined; let className: $$Props['class'] = undefined; export { className as class }; @@ -52,6 +54,7 @@ id, connectionLineType, connectionRadius, + isValidConnection }; Object.keys(updatableProps).forEach(prop => { diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts index 352604c0..52552a51 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/types.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts @@ -6,9 +6,14 @@ import type { Viewport } from '@reactflow/system'; -import type { Edge, Node, NodeTypes, KeyDefinition, EdgeTypes } from '$lib/types'; -import type { Writable } from 'svelte/store'; -import type { createNodes } from '$lib/utils'; +import type { + Edge, + Node, + NodeTypes, + KeyDefinition, + EdgeTypes, + IsValidConnection +} from '$lib/types'; export type SvelteFlowProps = { id?: string; @@ -27,6 +32,7 @@ export type SvelteFlowProps = { style?: string; connectionLineType?: ConnectionLineType; + isValidConnection?: IsValidConnection; }; export type SvelteFlowEvents = { diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 9f29aa1b..943e2684 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -15,7 +15,15 @@ import OutputNode from '$lib/components/nodes/OutputNode.svelte'; import BezierEdge from '$lib/components/edges/BezierEdge.svelte'; import StraightEdge from '$lib/components/edges/StraightEdge.svelte'; import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte'; -import type { ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted, Edge, Node } from '$lib/types'; +import type { + ConnectionData, + NodeTypes, + EdgeTypes, + EdgeLayouted, + Edge, + Node, + IsValidConnection +} from '$lib/types'; export const initConnectionData = { nodeId: null, @@ -67,5 +75,6 @@ export const initialStoreState = { connectionPath: readable(null), connection: writable(initConnectionData), connectionRadius: writable(25), - connectionLineType: writable(ConnectionLineType.Bezier) + connectionLineType: writable(ConnectionLineType.Bezier), + isValidConnection: writable(() => true) }; diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index a329c840..09530b3a 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -1,5 +1,11 @@ import type { ShortcutModifierDefinition } from '@svelte-put/shortcut'; -import type { FitViewOptionsBase, HandleProps, HandleType, XYPosition } from '@reactflow/system'; +import type { + Connection, + FitViewOptionsBase, + HandleProps, + HandleType, + XYPosition +} from '@reactflow/system'; import type { Node } from './nodes'; @@ -15,9 +21,14 @@ export type ConnectionData = { status: string | null; }; -export type HandleComponentProps = HandleProps & { +export type HandleComponentProps = Omit & { class?: string; style?: string; }; export type FitViewOptions = FitViewOptionsBase; + +export type IsValidConnection = ( + connection: Connection, + { fromNode, toNode }: { fromNode: Node; toNode: Node } +) => boolean; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index 1cb4bebc..35fe7e1a 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -26,4 +26,4 @@ export type NodeProps = Pick< export type NodeTypes = Record>>; -export type DefaultNodeOptions = Omit; +export type DefaultNodeOptions = Partial>; diff --git a/packages/svelte/src/routes/validation/+page.svelte b/packages/svelte/src/routes/validation/+page.svelte new file mode 100644 index 00000000..7b08fb5f --- /dev/null +++ b/packages/svelte/src/routes/validation/+page.svelte @@ -0,0 +1,51 @@ + + + + + + + + + +