Merge branch 'xyflow' of github.com:wbkd/react-flow into xyflow

This commit is contained in:
moklick
2023-09-27 16:11:03 +02:00
14 changed files with 198 additions and 64 deletions
@@ -13,7 +13,8 @@
'subflows', 'subflows',
'usesvelteflow', 'usesvelteflow',
'useupdatenodeinternals', 'useupdatenodeinternals',
'validation' 'validation',
'custom-connection-line'
]; ];
const onChange = (event: Event) => { const onChange = (event: Event) => {
@@ -0,0 +1,35 @@
<script lang="ts">
import { SvelteFlow } from '@xyflow/svelte';
import { Background, BackgroundVariant, type Edge, type Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import CustomNode from './CustomNode.svelte';
import ConnectionLine from './ConnectionLine.svelte';
import '@xyflow/svelte/dist/style.css';
const nodeTypes = {
custom: CustomNode
};
const nodes = writable<Node[]>([
{
id: 'connectionline-1',
type: 'custom',
data: { label: 'Node 1' },
position: { x: 250, y: 5 }
}
]);
const edges = writable<Edge[]>([]);
</script>
<div style="height:100vh;">
<SvelteFlow {nodeTypes} {nodes} {edges} fitView>
<ConnectionLine slot="connectionLine" />
<Background variant={BackgroundVariant.Lines} />
</SvelteFlow>
</div>
<style>
</style>
@@ -0,0 +1,9 @@
<script lang="ts">
import { useConnection } from '@xyflow/svelte';
const connection = useConnection();
</script>
{#if $connection.path}
<path d={$connection.path} fill="none" stroke={$connection.startHandle?.handleId} />
{/if}
@@ -0,0 +1,34 @@
<script lang="ts">
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
export let isConnectable: $$Props['isConnectable'];
const DEFAULT_HANDLE_STYLE = 'width: 10px; height: 10px; bottom: -5px;';
</script>
<div style="background: #DDD; padding: 25px">
<div>Node</div>
<Handle
type="source"
id="red"
position={Position.Bottom}
style={DEFAULT_HANDLE_STYLE + 'left: 15%; background: red;'}
{isConnectable}
/>
<Handle
type="source"
position={Position.Bottom}
id="blue"
style={DEFAULT_HANDLE_STYLE + 'left: 50%; background: blue;'}
{isConnectable}
/>
<Handle
type="source"
position={Position.Bottom}
id="orange"
style={DEFAULT_HANDLE_STYLE + 'left: 85%; background: orange;'}
{isConnectable}
/>
</div>
@@ -3,13 +3,21 @@
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
const { connectionPath, width, height, connection } = useStore(); export let containerStyle: string = '';
export let style: string = '';
export let usingCustomLine: boolean = false;
const { width, height, connection } = useStore();
</script> </script>
{#if $connectionPath} {#if $connection.path}
<svg width={$width} height={$height} class="svelte-flow__connectionline"> <svg width={$width} height={$height} class="svelte-flow__connectionline" style={containerStyle}>
<g class={cc(['svelte-flow__connection', $connection.connectionStatus])}> <g class={cc(['svelte-flow__connection', $connection.status])} {style}>
<path d={$connectionPath} fill="none" class="svelte-flow__connection-path" /> <slot name="connectionLine" />
<!-- slot fallbacks do not work if slots are forwarded in parent -->
{#if !usingCustomLine}
<path d={$connection.path} fill="none" class="svelte-flow__connection-path" />
{/if}
</g> </g>
</svg> </svg>
{/if} {/if}
@@ -41,6 +41,8 @@
export let connectionRadius: $$Props['connectionRadius'] = undefined; export let connectionRadius: $$Props['connectionRadius'] = undefined;
export let connectionLineType: $$Props['connectionLineType'] = undefined; export let connectionLineType: $$Props['connectionLineType'] = undefined;
export let connectionMode: $$Props['connectionMode'] = ConnectionMode.Strict; export let connectionMode: $$Props['connectionMode'] = ConnectionMode.Strict;
export let connectionLineStyle: $$Props['connectionLineStyle'] = '';
export let connectionLineContainerStyle: $$Props['connectionLineContainerStyle'] = '';
export let onMoveStart: $$Props['onMoveStart'] = undefined; export let onMoveStart: $$Props['onMoveStart'] = undefined;
export let onMove: $$Props['onMove'] = undefined; export let onMove: $$Props['onMove'] = undefined;
export let onMoveEnd: $$Props['onMoveEnd'] = undefined; export let onMoveEnd: $$Props['onMoveEnd'] = undefined;
@@ -162,7 +164,13 @@
<Pane on:paneclick panOnDrag={panOnDrag === undefined ? true : panOnDrag}> <Pane on:paneclick panOnDrag={panOnDrag === undefined ? true : panOnDrag}>
<ViewportComponent> <ViewportComponent>
<EdgeRenderer on:edgeclick on:edgecontextmenu {defaultEdgeOptions} /> <EdgeRenderer on:edgeclick on:edgecontextmenu {defaultEdgeOptions} />
<ConnectionLine /> <ConnectionLine
containerStyle={connectionLineContainerStyle}
style={connectionLineStyle}
usingCustomLine={$$slots.connectionLine}
>
<slot name="connectionLine" slot="connectionLine" />
</ConnectionLine>
<div class="svelte-flow__edgelabel-renderer" /> <div class="svelte-flow__edgelabel-renderer" />
<NodeRenderer <NodeRenderer
on:nodeclick on:nodeclick
@@ -49,6 +49,8 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
initialViewport?: Viewport; initialViewport?: Viewport;
connectionRadius?: number; connectionRadius?: number;
connectionMode?: ConnectionMode; connectionMode?: ConnectionMode;
connectionLineStyle?: string;
connectionLineContainerStyle?: string;
selectionMode?: SelectionMode; selectionMode?: SelectionMode;
snapGrid?: SnapGrid; snapGrid?: SnapGrid;
defaultMarkerColor?: string; defaultMarkerColor?: string;
@@ -0,0 +1,10 @@
import type { Readable } from 'svelte/store';
import { useStore } from '$lib/store';
import type { ConnectionProps } from '$lib/store/derived-connection-props';
export function useConnection(): Readable<ConnectionProps> {
const { connection } = useStore();
return connection;
}
+1
View File
@@ -21,6 +21,7 @@ export { useStore } from '$lib/store';
export * from '$lib/utils'; export * from '$lib/utils';
export * from '$lib/hooks/useSvelteFlow'; export * from '$lib/hooks/useSvelteFlow';
export * from '$lib/hooks/useUpdateNodeInternals'; export * from '$lib/hooks/useUpdateNodeInternals';
export * from '$lib/hooks/useConnection';
// types // types
export type { Edge, EdgeProps, EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges'; export type { Edge, EdgeProps, EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges';
@@ -1,4 +1,4 @@
import { derived } from 'svelte/store'; import { derived, type Writable } from 'svelte/store';
import { import {
getBezierPath, getBezierPath,
getSmoothStepPath, getSmoothStepPath,
@@ -10,6 +10,35 @@ import {
} from '@xyflow/system'; } from '@xyflow/system';
import type { SvelteFlowStoreState } from './types'; 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 = { const oppositePosition = {
[Position.Left]: Position.Right, [Position.Left]: Position.Right,
@@ -18,10 +47,13 @@ const oppositePosition = {
[Position.Bottom]: Position.Top [Position.Bottom]: Position.Top
}; };
export function getConnectionPath(store: SvelteFlowStoreState) { export function getDerivedConnectionProps(
store: SvelteFlowStoreState,
currentConnection: Writable<ConnectionData>
) {
return derived( return derived(
[ [
store.connection, currentConnection,
store.connectionLineType, store.connectionLineType,
store.connectionMode, store.connectionMode,
store.nodes, store.nodes,
@@ -29,7 +61,7 @@ export function getConnectionPath(store: SvelteFlowStoreState) {
], ],
([connection, connectionLineType, connectionMode, nodes, transform]) => { ([connection, connectionLineType, connectionMode, nodes, transform]) => {
if (!connection.connectionStartHandle?.nodeId) { if (!connection.connectionStartHandle?.nodeId) {
return null; return initConnectionProps;
} }
const fromNode = nodes.find((n) => n.id === connection.connectionStartHandle?.nodeId); const fromNode = nodes.find((n) => n.id === connection.connectionStartHandle?.nodeId);
@@ -80,7 +112,14 @@ export function getConnectionPath(store: SvelteFlowStoreState) {
[path] = getStraightPath(pathParams); [path] = getStraightPath(pathParams);
} }
return path; return {
path,
...pathParams,
pointerPosition: connection.connectionPosition,
startHandle: connection.connectionStartHandle,
endHandle: connection.connectionEndHandle,
status: connection.connectionStatus
};
} }
); );
} }
+20 -26
View File
@@ -1,5 +1,5 @@
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
import { derived, get } from 'svelte/store'; import { derived, get, writable } from 'svelte/store';
import { import {
internalsSymbol, internalsSymbol,
createMarkerIds, createMarkerIds,
@@ -19,18 +19,13 @@ import {
} from '@xyflow/system'; } from '@xyflow/system';
import { addEdge as addEdgeUtil } from '$lib/utils'; import { addEdge as addEdgeUtil } from '$lib/utils';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types'; import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
import { getConnectionPath } from './connection-path'; import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import {
initConnectionData,
initialEdgeTypes,
initialNodeTypes,
getInitialStore
} from './initial-store';
import type { SvelteFlowStore } from './types'; import type { SvelteFlowStore } from './types';
import { syncNodeStores, syncEdgeStores } from './utils'; import { syncNodeStores, syncEdgeStores } from './utils';
import { getEdgeTree } from './edge-tree'; import { getEdgeTree } from './edge-tree';
import { getVisibleNodes } from './visible-nodes'; import { getVisibleNodes } from './visible-nodes';
import { getDerivedConnectionProps } from './derived-connection-props';
export const key = Symbol(); export const key = Symbol();
@@ -256,25 +251,22 @@ export function createStore(): SvelteFlowStore {
}); });
} }
const updateConnection: UpdateConnection = (update) => { const initConnectionUpdateData = {
const currentConnectionData = get(store.connection); connectionStartHandle: null,
connectionEndHandle: null,
connectionPosition: null,
connectionStatus: null
};
const nextConnectionData = currentConnectionData // by creating an internal, unexposed store and using a derived store
? { // we prevent using slow get() calls
...initConnectionData, const currentConnection = writable<ConnectionData>(initConnectionUpdateData);
...currentConnectionData, const updateConnection: UpdateConnection = (newConnection: ConnectionData) => {
...update currentConnection.set(newConnection);
}
: {
...initConnectionData,
...update
};
store.connection.set(nextConnectionData);
}; };
function cancelConnection() { function cancelConnection() {
updateConnection(initConnectionData); updateConnection(initConnectionUpdateData);
} }
function reset() { function reset() {
@@ -296,7 +288,7 @@ export function createStore(): SvelteFlowStore {
// derived state // derived state
edgeTree: getEdgeTree(store), edgeTree: getEdgeTree(store),
connectionPath: getConnectionPath(store), connection: getDerivedConnectionProps(store, currentConnection),
visibleNodes: getVisibleNodes(store), visibleNodes: getVisibleNodes(store),
markers: derived( markers: derived(
[store.edges, store.defaultMarkerColor, store.flowId], [store.edges, store.defaultMarkerColor, store.flowId],
@@ -313,7 +305,9 @@ export function createStore(): SvelteFlowStore {
updateNodeDimensions, updateNodeDimensions,
zoomIn, zoomIn,
zoomOut, zoomOut,
fitView, fitView: (options?: FitViewOptions) => {
return fitView(get(store.nodes), options);
},
setMinZoom, setMinZoom,
setMaxZoom, setMaxZoom,
setTranslateExtent, setTranslateExtent,
+3 -17
View File
@@ -24,22 +24,9 @@ import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
import StraightEdge from '$lib/components/edges/StraightEdge.svelte'; import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte'; import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
import StepEdge from '$lib/components/edges/StepEdge.svelte'; import StepEdge from '$lib/components/edges/StepEdge.svelte';
import type { import type { NodeTypes, EdgeTypes, EdgeLayouted, Node, FitViewOptions } from '$lib/types';
ConnectionData,
NodeTypes,
EdgeTypes,
EdgeLayouted,
Node,
FitViewOptions
} from '$lib/types';
import { createNodesStore, createEdgesStore } from './utils'; import { createNodesStore, createEdgesStore } from './utils';
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
export const initConnectionData = {
connectionStartHandle: null,
connectionEndHandle: null,
connectionPosition: null,
connectionStatus: null
};
export const initialNodeTypes = { export const initialNodeTypes = {
input: InputNode, input: InputNode,
@@ -88,8 +75,7 @@ export const getInitialStore = () => ({
transform: writable<Transform>([0, 0, 1]), transform: writable<Transform>([0, 0, 1]),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict), connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null), domNode: writable<HTMLDivElement | null>(null),
connectionPath: readable<string | null>(null), connection: readable<ConnectionProps>(initConnectionProps),
connection: writable<ConnectionData>(initConnectionData),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier), connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
connectionRadius: writable<number>(20), connectionRadius: writable<number>(20),
isValidConnection: writable<IsValidConnection>(() => true), isValidConnection: writable<IsValidConnection>(() => true),
+4 -4
View File
@@ -131,8 +131,8 @@ export type UpdateNodePositions = (
export type PanBy = (delta: XYPosition) => boolean; export type PanBy = (delta: XYPosition) => boolean;
export type UpdateConnection = (params: { export type UpdateConnection = (params: {
connectionPosition?: XYPosition | null; connectionPosition: XYPosition | null;
connectionStatus?: ConnectionStatus | null; connectionStatus: ConnectionStatus | null;
connectionStartHandle?: ConnectingHandle | null; connectionStartHandle: ConnectingHandle | null;
connectionEndHandle?: ConnectingHandle | null; connectionEndHandle: ConnectingHandle | null;
}) => void; }) => void;
+12 -5
View File
@@ -66,6 +66,8 @@ const nullConnection: Connection = { source: null, target: null, sourceHandle: n
const alwaysValid = () => true; const alwaysValid = () => true;
let connectionStartHandle: ConnectingHandle | null = null;
function onPointerDown( function onPointerDown(
event: MouseEvent | TouchEvent, event: MouseEvent | TouchEvent,
{ {
@@ -129,15 +131,18 @@ function onPointerDown(
autoPanId = requestAnimationFrame(autoPan); autoPanId = requestAnimationFrame(autoPan);
} }
// Stays the same for all consecutive pointermove events
connectionStartHandle = {
nodeId,
handleId,
type: handleType,
};
updateConnection({ updateConnection({
connectionPosition, connectionPosition,
connectionStatus: null, connectionStatus: null,
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle // connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
connectionStartHandle: { connectionStartHandle,
nodeId,
handleId,
type: handleType,
},
connectionEndHandle: null, connectionEndHandle: null,
}); });
@@ -173,6 +178,7 @@ function onPointerDown(
isValid = result.isValid; isValid = result.isValid;
updateConnection({ updateConnection({
connectionStartHandle,
connectionPosition: connectionPosition:
closestHandle && isValid closestHandle && isValid
? rendererPointToPoint( ? rendererPointToPoint(
@@ -220,6 +226,7 @@ function onPointerDown(
isValid = false; isValid = false;
connection = null; connection = null;
handleDomNode = null; handleDomNode = null;
connectionStartHandle = null;
doc.removeEventListener('mousemove', onPointerMove as EventListener); doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener); doc.removeEventListener('mouseup', onPointerUp as EventListener);