From 7193b55ff2add22333b5be41073772511a5ebdf2 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 27 Nov 2023 11:29:43 +0100 Subject: [PATCH 01/32] chore(examples): cleanup --- examples/svelte/src/components/Header/Header.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte index 7d229796..ad34d289 100644 --- a/examples/svelte/src/components/Header/Header.svelte +++ b/examples/svelte/src/components/Header/Header.svelte @@ -4,7 +4,7 @@ const routes = [ 'add-node-on-drop', - 'colormode', + 'color-mode', 'custom-connection-line', 'customnode', 'dagre', From 9f538c6b7ceb8c1b5cca9c413f24ea19de86aad8 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 28 Nov 2023 12:33:54 +0100 Subject: [PATCH 02/32] fix(svelte) split edges into internal/external. external edges dont require id, source, target anymore --- .../components/EdgeWrapper/EdgeWrapper.svelte | 2 +- .../lib/components/edges/BezierEdge.svelte | 2 +- .../edges/InternalBezierEdge.svelte | 29 ++++++++++++++++++ .../edges/InternalSmoothStepEdge.svelte | 29 ++++++++++++++++++ .../components/edges/InternalStepEdge.svelte | 30 +++++++++++++++++++ .../edges/InternalStraightEdge.svelte | 27 +++++++++++++++++ .../components/edges/SmoothStepEdge.svelte | 2 +- .../src/lib/components/edges/StepEdge.svelte | 2 +- .../lib/components/edges/StraightEdge.svelte | 2 +- .../svelte/src/lib/store/initial-store.ts | 8 ++--- 10 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 packages/svelte/src/lib/components/edges/InternalBezierEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/InternalSmoothStepEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/InternalStepEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/InternalStraightEdge.svelte diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 7a8df68c..7f5b9bbd 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -6,7 +6,7 @@ import { errorMessages, getMarkerId } from '@xyflow/system'; import { useStore } from '$lib/store'; - import BezierEdge from '$lib/components/edges/BezierEdge.svelte'; + import BezierEdge from '$lib/components/edges/InternalBezierEdge.svelte'; import type { EdgeLayouted, Edge } from '$lib/types'; import { get } from 'svelte/store'; diff --git a/packages/svelte/src/lib/components/edges/BezierEdge.svelte b/packages/svelte/src/lib/components/edges/BezierEdge.svelte index 60921eb6..56ccaf31 100644 --- a/packages/svelte/src/lib/components/edges/BezierEdge.svelte +++ b/packages/svelte/src/lib/components/edges/BezierEdge.svelte @@ -4,7 +4,7 @@ import type { EdgeProps } from '$lib/types'; import { BaseEdge } from '$lib/components/BaseEdge'; - type $$Props = EdgeProps; + type $$Props = Partial; $: [path, labelX, labelY] = getBezierPath({ sourceX: $$props.sourceX, diff --git a/packages/svelte/src/lib/components/edges/InternalBezierEdge.svelte b/packages/svelte/src/lib/components/edges/InternalBezierEdge.svelte new file mode 100644 index 00000000..a1ca0658 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/InternalBezierEdge.svelte @@ -0,0 +1,29 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/InternalSmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/InternalSmoothStepEdge.svelte new file mode 100644 index 00000000..73324a89 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/InternalSmoothStepEdge.svelte @@ -0,0 +1,29 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/InternalStepEdge.svelte b/packages/svelte/src/lib/components/edges/InternalStepEdge.svelte new file mode 100644 index 00000000..f793376b --- /dev/null +++ b/packages/svelte/src/lib/components/edges/InternalStepEdge.svelte @@ -0,0 +1,30 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/InternalStraightEdge.svelte b/packages/svelte/src/lib/components/edges/InternalStraightEdge.svelte new file mode 100644 index 00000000..8cc984d7 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/InternalStraightEdge.svelte @@ -0,0 +1,27 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte index 30872b68..39acb6e6 100644 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte @@ -4,7 +4,7 @@ import type { EdgeProps } from '$lib/types'; import { BaseEdge } from '$lib/components/BaseEdge'; - type $$Props = EdgeProps; + type $$Props = Partial; $: [path, labelX, labelY] = getSmoothStepPath({ sourceX: $$props.sourceX, diff --git a/packages/svelte/src/lib/components/edges/StepEdge.svelte b/packages/svelte/src/lib/components/edges/StepEdge.svelte index 65012595..ac6118ca 100644 --- a/packages/svelte/src/lib/components/edges/StepEdge.svelte +++ b/packages/svelte/src/lib/components/edges/StepEdge.svelte @@ -4,7 +4,7 @@ import type { EdgeProps } from '$lib/types'; import { BaseEdge } from '$lib/components/BaseEdge'; - type $$Props = EdgeProps; + type $$Props = Partial; $: [path, labelX, labelY] = getSmoothStepPath({ sourceX: $$props.sourceX, diff --git a/packages/svelte/src/lib/components/edges/StraightEdge.svelte b/packages/svelte/src/lib/components/edges/StraightEdge.svelte index 50f282ff..9e6c88da 100644 --- a/packages/svelte/src/lib/components/edges/StraightEdge.svelte +++ b/packages/svelte/src/lib/components/edges/StraightEdge.svelte @@ -4,7 +4,7 @@ import type { EdgeProps } from '$lib/types'; import { BaseEdge } from '$lib/components/BaseEdge'; - type $$Props = EdgeProps; + type $$Props = Partial; $: [path, labelX, labelY] = getStraightPath({ sourceX: $$props.sourceX, diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index dbbb17fc..bf0166ab 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -24,10 +24,10 @@ 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 BezierEdge from '$lib/components/edges/BezierEdge.svelte'; -import StraightEdge from '$lib/components/edges/StraightEdge.svelte'; -import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte'; -import StepEdge from '$lib/components/edges/StepEdge.svelte'; +import BezierEdge from '$lib/components/edges/InternalBezierEdge.svelte'; +import StraightEdge from '$lib/components/edges/InternalStraightEdge.svelte'; +import SmoothStepEdge from '$lib/components/edges/InternalSmoothStepEdge.svelte'; +import StepEdge from '$lib/components/edges/InternalStepEdge.svelte'; import type { NodeTypes, EdgeTypes, From 0393409e898d4c462110c35190a192bb17ed1696 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 28 Nov 2023 13:03:50 +0100 Subject: [PATCH 03/32] fix(system) exportet Optional type, fix(svelte) adjusted exported edge types --- .../svelte/src/routes/examples/customnode/CustomNode.svelte | 2 +- packages/svelte/src/lib/components/edges/BezierEdge.svelte | 4 ++-- .../svelte/src/lib/components/edges/SmoothStepEdge.svelte | 4 ++-- packages/svelte/src/lib/components/edges/StepEdge.svelte | 4 ++-- packages/svelte/src/lib/components/edges/StraightEdge.svelte | 3 ++- packages/system/src/types/utils.ts | 2 ++ packages/system/src/utils/index.ts | 1 + 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte index 25d37d32..51ba33ea 100644 --- a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte +++ b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte @@ -1,6 +1,6 @@ - + { + console.log('on edge create', e); + return e; + }} +> diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index 655bcc6b..7d3010bd 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -55,6 +55,7 @@ isValidConnection, lib, addEdge, + onedgecreate, panBy, cancelConnection, updateConnection, @@ -80,8 +81,16 @@ cancelConnection, panBy, onConnect: (connection) => { - addEdge(connection); + if ($onedgecreate) { + const modifiedConnection = $onedgecreate(connection); + if (modifiedConnection) { + addEdge(modifiedConnection); + dispatch('connect', { connection }); + } + return; + } + addEdge(connection); // @todo: should we change/ improve the stuff we are passing here? // instead of source/target we could pass fromNodeId, fromHandleId, etc dispatch('connect', { connection }); diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index 59bff1f4..21ceec49 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -71,6 +71,7 @@ export let autoPanOnNodeDrag: $$Props['autoPanOnNodeDrag'] = true; export let onerror: $$Props['onerror'] = undefined; export let ondelete: $$Props['ondelete'] = undefined; + export let onedgecreate: $$Props['onedgecreate'] = undefined; export let attributionPosition: $$Props['attributionPosition'] = undefined; export let proOptions: $$Props['proOptions'] = undefined; export let defaultEdgeOptions: $$Props['defaultEdgeOptions'] = undefined; @@ -149,6 +150,7 @@ autoPanOnNodeDrag, onerror, ondelete, + onedgecreate, connectionMode, nodeDragThreshold }; diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts index daab48e9..9116dc60 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/types.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts @@ -26,7 +26,8 @@ import type { EdgeTypes, DefaultEdgeOptions, FitViewOptions, - OnDelete + OnDelete, + OnEdgeCreate } from '$lib/types'; import type { Writable } from 'svelte/store'; @@ -89,4 +90,6 @@ export type SvelteFlowProps = DOMAttributes & { onMoveEnd?: OnMoveEnd; onerror?: OnError; ondelete?: OnDelete; + + onedgecreate?: OnEdgeCreate; }; diff --git a/packages/svelte/src/lib/container/SvelteFlow/utils.ts b/packages/svelte/src/lib/container/SvelteFlow/utils.ts index 9abdb3e9..0281cd55 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/utils.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/utils.ts @@ -64,6 +64,7 @@ export type UpdatableStoreProps = { connectionMode?: UnwrapWritable; onerror?: UnwrapWritable; ondelete?: UnwrapWritable; + onedgecreate?: UnwrapWritable; nodeDragThreshold?: UnwrapWritable; }; diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index dbbb17fc..8f358cf9 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -35,7 +35,8 @@ import type { Node, Edge, FitViewOptions, - OnDelete + OnDelete, + OnEdgeCreate } from '$lib/types'; import { createNodesStore, createEdgesStore } from './utils'; import { initConnectionProps, type ConnectionProps } from './derived-connection-props'; @@ -130,6 +131,7 @@ export const getInitialStore = ({ lib: readable('svelte'), onlyRenderVisibleElements: writable(false), onerror: writable(devWarn), - ondelete: writable(undefined) + ondelete: writable(undefined), + onedgecreate: writable(undefined) }; }; diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index 14764318..62666705 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -4,7 +4,8 @@ import type { HandleType, Position, XYPosition, - ConnectingHandle + ConnectingHandle, + Connection } from '@xyflow/system'; import type { Node } from './nodes'; @@ -35,3 +36,4 @@ export type HandleComponentProps = { export type FitViewOptions = FitViewOptionsBase; export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void; +export type OnEdgeCreate = (connection: Connection) => Edge | Connection | void; From a1e585fdccce432e237ad85b45d459223d0ec38c Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 28 Nov 2023 16:35:44 +0100 Subject: [PATCH 09/32] refactor(css-vars): prefix with xy and fix bgColor prop closes #3669 --- .../react/src/examples/ColorMode/index.tsx | 2 + .../react/src/examples/ColorMode/style.css | 0 .../routes/examples/customnode/+page.svelte | 2 +- .../Background/Background.tsx | 4 +- .../additional-components/MiniMap/MiniMap.tsx | 8 +- .../lib/plugins/Background/Background.svelte | 4 +- .../lib/plugins/Controls/ControlButton.svelte | 10 +- .../src/lib/plugins/Minimap/Minimap.svelte | 8 +- packages/system/src/styles/base.css | 24 ++-- packages/system/src/styles/init.css | 108 ++++++++--------- packages/system/src/styles/style.css | 109 +++++++++--------- 11 files changed, 144 insertions(+), 135 deletions(-) create mode 100644 examples/react/src/examples/ColorMode/style.css diff --git a/examples/react/src/examples/ColorMode/index.tsx b/examples/react/src/examples/ColorMode/index.tsx index f867c650..eae8ad86 100644 --- a/examples/react/src/examples/ColorMode/index.tsx +++ b/examples/react/src/examples/ColorMode/index.tsx @@ -15,6 +15,8 @@ import { Position, } from '@xyflow/react'; +import './style.css'; + const nodeDefaults = { sourcePosition: Position.Right, targetPosition: Position.Left, diff --git a/examples/react/src/examples/ColorMode/style.css b/examples/react/src/examples/ColorMode/style.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/svelte/src/routes/examples/customnode/+page.svelte b/examples/svelte/src/routes/examples/customnode/+page.svelte index 14130a47..123932f9 100644 --- a/examples/svelte/src/routes/examples/customnode/+page.svelte +++ b/examples/svelte/src/routes/examples/customnode/+page.svelte @@ -85,7 +85,7 @@ {nodes} {edges} {nodeTypes} - style="--background-color: {$bgColor}" + style="--xy-background-color: {$bgColor}" fitView on:connect={onConnect} > diff --git a/packages/react/src/additional-components/Background/Background.tsx b/packages/react/src/additional-components/Background/Background.tsx index 843daf19..3e71d723 100644 --- a/packages/react/src/additional-components/Background/Background.tsx +++ b/packages/react/src/additional-components/Background/Background.tsx @@ -55,8 +55,8 @@ function Background({ { ...style, ...containerStyle, - '--background-color-props': bgColor, - '--background-pattern-color-props': color, + '--xy-background-color-props': bgColor, + '--xy-background-pattern-color-props': color, } as CSSProperties } ref={ref} diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index 33848836..8d1f0538 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -129,10 +129,10 @@ function MiniMap({ style={ { ...style, - '--minimap-mask-color-props': typeof maskColor === 'string' ? maskColor : undefined, - '--minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined, - '--minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined, - '--minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined, + '--xy-minimap-mask-color-props': typeof maskColor === 'string' ? maskColor : undefined, + '--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined, + '--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined, + '--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined, } as CSSProperties } className={cc(['react-flow__minimap', className])} diff --git a/packages/svelte/src/lib/plugins/Background/Background.svelte b/packages/svelte/src/lib/plugins/Background/Background.svelte index 67689ab9..c6a67d48 100644 --- a/packages/svelte/src/lib/plugins/Background/Background.svelte +++ b/packages/svelte/src/lib/plugins/Background/Background.svelte @@ -45,8 +45,8 @@ diff --git a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte index 67f93698..7b2896d8 100644 --- a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte +++ b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte @@ -94,10 +94,10 @@ viewBox="{x} {y} {viewboxWidth} {viewboxHeight}" role="img" aria-labelledby={labelledBy} - style:--minimap-background-color-props={bgColor} - style:--minimap-mask-color-props={maskColor} - style:--minimap-mask-stroke-color-props={maskStrokeColor} - style:--minimap-mask-stroke-width-props={maskStrokeWidth} + style:--xy-minimap-background-color-props={bgColor} + style:--xy-minimap-mask-color-props={maskColor} + style:--xy-minimap-mask-stroke-color-props={maskStrokeColor} + style:--xy-minimap-mask-stroke-width-props={maskStrokeWidth} use:interactive={{ panZoom: $panZoom, viewport, diff --git a/packages/system/src/styles/base.css b/packages/system/src/styles/base.css index 1cb026b6..3a5afe6c 100644 --- a/packages/system/src/styles/base.css +++ b/packages/system/src/styles/base.css @@ -1,38 +1,38 @@ .xy-flow { - --node-border-default: 1px solid #bbb; - --node-border-selected-default: 1px solid #555; + --xy-node-border-default: 1px solid #bbb; + --xy-node-border-selected-default: 1px solid #555; - --handle-background-color-default: #333; + --xy-handle-background-color-default: #333; - --selection-background-color-default: rgba(150, 150, 180, 0.1); - --selection-border-default: 1px dotted rgba(155, 155, 155, 0.8); + --xy-selection-background-color-default: rgba(150, 150, 180, 0.1); + --xy-selection-border-default: 1px dotted rgba(155, 155, 155, 0.8); } .xy-flow.dark { - --node-color-default: #f8f8f8; + --xy-node-color-default: #f8f8f8; } .xy-flow__handle { - background-color: var(--handle-background-color, var(--handle-background-color-default)); + background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default)); } .xy-flow__node-input, .xy-flow__node-default, .xy-flow__node-output, .xy-flow__node-group { - border: var(--node-border, var(--node-border-default)); - color: var(--node-color, var(--node-color-default)); + border: var(--xy-node-border, var(--xy-node-border-default)); + color: var(--xy-node-color, var(--xy-node-color-default)); &.selected, &:focus, &:focus-visible { outline: none; - border: var(--node-border-selected, var(--node-border-selected-default)); + border: var(--xy-node-border-selected, var(--xy-node-border-selected-default)); } } .xy-flow__nodesselection-rect, .xy-flow__selection { - background: var(--selection-background-color, var(--selection-background-color-default)); - border: var(--selection-border, var(--selection-border-default)); + background: var(--xy-selection-background-color, var(--xy-selection-background-color-default)); + border: var(--xy-selection-border, var(--xy-selection-border-default)); } diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 474b1322..2aff146f 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -1,51 +1,55 @@ /* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */ .xy-flow { - --edge-stroke-default: #b1b1b7; - --edge-stroke-width-default: 1; - --edge-stroke-selected-default: #555; + --xy-edge-stroke-default: #b1b1b7; + --xy-edge-stroke-width-default: 1; + --xy-edge-stroke-selected-default: #555; - --connectionline-stroke-default: #b1b1b7; - --connectionline-stroke-width-default: 1; + --xy-connectionline-stroke-default: #b1b1b7; + --xy-connectionline-stroke-width-default: 1; - --attribution-background-color-default: rgba(255, 255, 255, 0.5); + --xy-attribution-background-color-default: rgba(255, 255, 255, 0.5); - --minimap-background-color-default: #fff; - --minimap-mask-background-color-default: rgb(240, 240, 240, 0.6); - --minimap-node-background-color-default: #e2e2e2; - --minimap-node-stroke-color-default: transparent; - --minimap-node-stroke-width-default: 2; + --xy-minimap-background-color-default: #fff; + --xy-minimap-mask-background-color-default: rgb(240, 240, 240, 0.6); + --xy-minimap-node-background-color-default: #e2e2e2; + --xy-minimap-node-stroke-color-default: transparent; + --xy-minimap-node-stroke-width-default: 2; - --background-color-default: transparent; - --background-pattern-dots-color-default: #91919a; - --background-pattern-lines-color-default: #eee; - --background-pattern-cross-color-default: #e2e2e2; + --xy-background-color-default: transparent; + --xy-background-pattern-dots-color-default: #91919a; + --xy-background-pattern-lines-color-default: #eee; + --xy-background-pattern-cross-color-default: #e2e2e2; } .xy-flow.dark { - --edge-stroke-default: #3c3c3c; - --edge-stroke-width-default: 1; - --edge-stroke-selected-default: #727272; + --xy-edge-stroke-default: #3c3c3c; + --xy-edge-stroke-width-default: 1; + --xy-edge-stroke-selected-default: #727272; - --connectionline-stroke-default: #b1b1b7; - --connectionline-stroke-width-default: 1; + --xy-connectionline-stroke-default: #b1b1b7; + --xy-connectionline-stroke-width-default: 1; - --attribution-background-color-default: rgba(150, 150, 150, 0.25); + --xy-attribution-background-color-default: rgba(150, 150, 150, 0.25); - --minimap-background-color-default: #141414; - --minimap-mask-background-color-default: rgb(60, 60, 60, 0.6); - --minimap-node-background-color-default: #2b2b2b; - --minimap-node-stroke-color-default: transparent; - --minimap-node-stroke-width-default: 2; + --xy-minimap-background-color-default: #141414; + --xy-minimap-mask-background-color-default: rgb(60, 60, 60, 0.6); + --xy-minimap-node-background-color-default: #2b2b2b; + --xy-minimap-node-stroke-color-default: transparent; + --xy-minimap-node-stroke-width-default: 2; - --background-color-default: #141414; - --background-pattern-dots-color-default: #777; - --background-pattern-lines-color-default: #777; - --background-pattern-cross-color-default: #777; + --xy-background-color-default: #141414; + --xy-background-pattern-dots-color-default: #777; + --xy-background-pattern-lines-color-default: #777; + --xy-background-pattern-cross-color-default: #777; } .xy-flow { - background-color: var(--background-color-props, var(--background-color-default, 'transparent')); + background-color: var(--xy-background-color, var(--xy-background-color-default)); +} + +.xy-flow__background { + background-color: var(--xy-background-color, var(--xy-background-color-props, var(--xy-background-color-default))); } .xy-flow__container { @@ -94,14 +98,14 @@ } .xy-flow__edge-path { - stroke: var(--edge-stroke, var(--edge-stroke-default)); - stroke-width: var(--edge-stroke-width, var(--edge-stroke-width-default)); + stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default)); + stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default)); fill: none; } .xy-flow__connection-path { - stroke: var(--connectionline-stroke, var(--connectionline-stroke-default)); - stroke-width: var(--connectionline-stroke-width, var(--connectionline-stroke-width-default)); + stroke: var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default)); + stroke-width: var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default)); fill: none; } @@ -132,7 +136,7 @@ &.selected .xy-flow__edge-path, &:focus .xy-flow__edge-path, &:focus-visible .xy-flow__edge-path { - stroke: var(--edge-stroke-selected, var(--edge-stroke-selected-default)); + stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default)); } &-textwrapper { @@ -266,7 +270,7 @@ .xy-flow__attribution { font-size: 10px; - background: var(--attribution-background-color, var(--attribution-background-color-default)); + background: var(--xy-attribution-background-color, var(--xy-attribution-background-color-default)); padding: 2px 3px; margin: 0; @@ -293,27 +297,27 @@ } .xy-flow__minimap { - background: var(--minimap-background-color, var(--minimap-background-color-default)); + background: var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)); &-mask { fill: var( - --minimap-mask-background-color-props, - var(--minimap-mask-background-color, var(--minimap-mask-background-color-default)) + --xy-minimap-mask-background-color-props, + var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) ); } &-node { fill: var( - --minimap-node-background-color-props, - var(--minimap-node-background-color, var(--minimap-node-background-color-default)) + --xy-minimap-node-background-color-props, + var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) ); stroke: var( - --minimap-node-stroke-color-props, - var(--minimap-node-stroke-color, var(--minimap-node-stroke-color-default)) + --xy-minimap-node-stroke-color-props, + var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) ); stroke-width: var( - --minimap-node-stroke-width-props, - var(--minimap-node-stroke-width, var(--minimap-node-stroke-width-default)) + --xy-minimap-node-stroke-width-props, + var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) ); } } @@ -326,22 +330,22 @@ .xy-flow__background-pattern { &.dots { fill: var( - --background-pattern-color-props, - var(--background-pattern-color, var(--background-pattern-dots-color-default)) + --xy-background-pattern-color-props, + var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) ); } &.lines { stroke: var( - --background-pattern-color-props, - var(--background-pattern-color, var(--background-pattern-lines-color-default)) + --xy-background-pattern-color-props, + var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) ); } &.cross { stroke: var( - --background-pattern-color-props, - var(--background-pattern-color, var(--background-pattern-cross-color-default)) + --xy-background-pattern-color-props, + var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) ); } } diff --git a/packages/system/src/styles/style.css b/packages/system/src/styles/style.css index e88cc3f1..880ebaf7 100644 --- a/packages/system/src/styles/style.css +++ b/packages/system/src/styles/style.css @@ -1,46 +1,46 @@ .xy-flow { - --node-color-default: inherit; - --node-border-default: 1px solid #1a192b; - --node-background-color-default: #fff; - --node-group-background-color-default: rgba(240, 240, 240, 0.25); - --node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08); - --node-boxshadow-selected-default: 0 0 0 0.5px #1a192b; - --node-border-radius-default: 3px; + --xy-node-color-default: inherit; + --xy-node-border-default: 1px solid #1a192b; + --xy-node-background-color-default: #fff; + --xy-node-group-background-color-default: rgba(240, 240, 240, 0.25); + --xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08); + --xy-node-boxshadow-selected-default: 0 0 0 0.5px #1a192b; + --xy-node-border-radius-default: 3px; - --handle-background-color-default: #1a192b; - --handle-border-color-default: #fff; + --xy-handle-background-color-default: #1a192b; + --xy-handle-border-color-default: #fff; - --selection-background-color-default: rgba(0, 89, 220, 0.08); - --selection-border-default: 1px dotted rgba(0, 89, 220, 0.8); + --xy-selection-background-color-default: rgba(0, 89, 220, 0.08); + --xy-selection-border-default: 1px dotted rgba(0, 89, 220, 0.8); - --controls-button-background-color-default: #fefefe; - --controls-button-background-color-hover-default: #f4f4f4; - --controls-button-color-default: inherit; - --controls-button-color-hover-default: inherit; - --controls-button-border-color-default: #eee; - --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); + --xy-controls-button-background-color-default: #fefefe; + --xy-controls-button-background-color-hover-default: #f4f4f4; + --xy-controls-button-color-default: inherit; + --xy-controls-button-color-hover-default: inherit; + --xy-controls-button-border-color-default: #eee; + --xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } .xy-flow.dark { - --node-color-default: #f8f8f8; - --node-border-default: 1px solid #3c3c3c; - --node-background-color-default: #1e1e1e; - --node-group-background-color-default: rgba(240, 240, 240, 0.25); - --node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08); - --node-boxshadow-selected-default: 0 0 0 0.5px #999; + --xy-node-color-default: #f8f8f8; + --xy-node-border-default: 1px solid #3c3c3c; + --xy-node-background-color-default: #1e1e1e; + --xy-node-group-background-color-default: rgba(240, 240, 240, 0.25); + --xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08); + --xy-node-boxshadow-selected-default: 0 0 0 0.5px #999; - --handle-background-color-default: #bebebe; - --handle-border-color-default: #1e1e1e; + --xy-handle-background-color-default: #bebebe; + --xy-handle-border-color-default: #1e1e1e; - --selection-background-color-default: rgba(200, 200, 220, 0.08); - --selection-border-default: 1px dotted rgba(200, 200, 220, 0.8); + --xy-selection-background-color-default: rgba(200, 200, 220, 0.08); + --xy-selection-border-default: 1px dotted rgba(200, 200, 220, 0.8); - --controls-button-background-color-default: #2b2b2b; - --controls-button-background-color-hover-default: #3e3e3e; - --controls-button-color-default: #f8f8f8; - --controls-button-color-hover-default: #fff; - --controls-button-border-color-default: #5b5b5b; - --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); + --xy-controls-button-background-color-default: #2b2b2b; + --xy-controls-button-background-color-hover-default: #3e3e3e; + --xy-controls-button-color-default: #f8f8f8; + --xy-controls-button-color-hover-default: #fff; + --xy-controls-button-border-color-default: #5b5b5b; + --xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } .xy-flow__edge { @@ -67,35 +67,35 @@ .xy-flow__node-output, .xy-flow__node-group { padding: 10px; - border-radius: var(--node-border-radius, var(--node-border-radius-default)); + border-radius: var(--xy-node-border-radius, var(--xy-node-border-radius-default)); width: 150px; font-size: 12px; - color: var(--node-color, var(--node-color-default)); + color: var(--xy-node-color, var(--xy-node-color-default)); text-align: center; - border: var(--node-border, var(--node-border-default)); - background-color: var(--node-background-color, var(--node-background-color-default)); + border: var(--xy-node-border, var(--xy-node-border-default)); + background-color: var(--xy-node-background-color, var(--xy-node-background-color-default)); &.selectable { &:hover { - box-shadow: var(--node-boxshadow-hover, var(--node-boxshadow-hover-default)); + box-shadow: var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default)); } &.selected, &:focus, &:focus-visible { - box-shadow: var(--node-boxshadow-selected, var(--node-boxshadow-selected-default)); + box-shadow: var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default)); } } } .xy-flow__node-group { - background-color: var(--node-group-background-color, var(--node-group-background-color-default)); + background-color: var(--xy-node-group-background-color, var(--xy-node-group-background-color-default)); } .xy-flow__nodesselection-rect, .xy-flow__selection { - background: var(--selection-background-color, var(--selection-background-color-default)); - border: var(--selection-border, var(--selection-border-default)); + background: var(--xy-selection-background-color, var(--xy-selection-background-color-default)); + border: var(--xy-selection-border, var(--xy-selection-border-default)); &:focus, &:focus-visible { @@ -106,34 +106,37 @@ .xy-flow__handle { width: 6px; height: 6px; - background-color: var(--handle-background-color, var(--handle-background-color-default)); - border: 1px solid var(--handle-border-color, var(--handle-border-color-default)); + background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default)); + border: 1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default)); border-radius: 100%; } .xy-flow__controls { - box-shadow: var(--controls-box-shadow, var(--controls-box-shadow-default)); + box-shadow: var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default)); &-button { border: none; - background: var(--controls-button-background-color, var(--controls-button-background-color-default)); + background: var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default)); border-bottom: 1px solid var( - --controls-button-border-color-props, - var(--controls-button-border-color, var(--controls-button-border-color-default)) + --xy-controls-button-border-color-props, + var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) ); - color: var(--controls-button-color-props, var(--controls-button-color, var(--controls-button-color-default))); + color: var( + --xy-controls-button-color-props, + var(--xy-controls-button-color, var(--xy-controls-button-color-default)) + ); cursor: pointer; user-select: none; &:hover { background: var( - --controls-button-background-color-hover-props, - var(--controls-button-background-color-hover, var(--controls-button-background-color-hover-default)) + --xy-controls-button-background-color-hover-props, + var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) ); color: var( - --controls-button-color-hover-props, - var(--controls-button-color-hover, var(--controls-button-color-hover-default)) + --xy-controls-button-color-hover-props, + var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) ); } From 1d7803aff7a0812c3f07a559defc69fbdcf59e2b Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 28 Nov 2023 16:50:17 +0100 Subject: [PATCH 10/32] chore(svelte/onConnect): cleanup --- .../svelte/src/lib/components/Handle/Handle.svelte | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index 7d3010bd..745a0f28 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -80,20 +80,17 @@ updateConnection, cancelConnection, panBy, - onConnect: (connection) => { - if ($onedgecreate) { - const modifiedConnection = $onedgecreate(connection); - if (modifiedConnection) { - addEdge(modifiedConnection); - dispatch('connect', { connection }); - } + onConnect: (_connection) => { + let connection = $onedgecreate ? $onedgecreate(_connection) : _connection; + + if (!connection) { return; } addEdge(connection); // @todo: should we change/ improve the stuff we are passing here? // instead of source/target we could pass fromNodeId, fromHandleId, etc - dispatch('connect', { connection }); + dispatch('connect', { connection: _connection }); }, onConnectStart: (event, startParams) => { dispatch('connectstart', { From 95acea54d1dfab616a42ee1900797377c87d123d Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 29 Nov 2023 16:33:57 +0100 Subject: [PATCH 11/32] chore(react/svelte): cleanup edge creation --- .../src/routes/examples/edges/+page.svelte | 19 +++++++++++++++---- .../src/lib/components/Handle/Handle.svelte | 10 +++++----- packages/system/src/utils/edges/general.ts | 10 +++++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/svelte/src/routes/examples/edges/+page.svelte b/examples/svelte/src/routes/examples/edges/+page.svelte index 575984ca..f201a02d 100644 --- a/examples/svelte/src/routes/examples/edges/+page.svelte +++ b/examples/svelte/src/routes/examples/edges/+page.svelte @@ -6,7 +6,8 @@ Background, BackgroundVariant, MiniMap, - MarkerType + MarkerType, + type Connection } from '@xyflow/svelte'; import '@xyflow/svelte/dist/style.css'; @@ -143,6 +144,12 @@ } } ]); + + $: console.log('edges', $edges); + + function getEdgeId(connection: Connection) { + return `edge-${connection.source}-${connection.target}}`; + } { - console.log('on edge create', e); - return e; + onedgecreate={(connection) => { + console.log('on edge create', connection); + + return { + ...connection, + id: getEdgeId(connection) + }; }} > diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index 745a0f28..e4b6a823 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -80,17 +80,17 @@ updateConnection, cancelConnection, panBy, - onConnect: (_connection) => { - let connection = $onedgecreate ? $onedgecreate(_connection) : _connection; + onConnect: (connection) => { + const edge = $onedgecreate ? $onedgecreate(connection) : connection; - if (!connection) { + if (!edge) { return; } - addEdge(connection); + addEdge(edge); // @todo: should we change/ improve the stuff we are passing here? // instead of source/target we could pass fromNodeId, fromHandleId, etc - dispatch('connect', { connection: _connection }); + dispatch('connect', { connection }); }, onConnectStart: (event, startParams) => { dispatch('connectstart', { diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index bdffbbd8..9ffd8e24 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -112,7 +112,7 @@ export function isEdgeVisible({ sourceNode, targetNode, width, height, transform } const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection | EdgeBase): string => - `xyflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`; + `xy-edge__${source}${sourceHandle || ''}-${target}${targetHandle || ''}`; const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { return edges.some( @@ -148,6 +148,14 @@ export const addEdgeBase = ( return edges; } + if (edge.sourceHandle === null) { + delete edge.sourceHandle; + } + + if (edge.targetHandle === null) { + delete edge.targetHandle; + } + return edges.concat(edge); }; From 8f43160236ce989136e529eb483e1508a2ac1f52 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 29 Nov 2023 18:01:11 +0100 Subject: [PATCH 12/32] refactor(react/edges): simplify edge creation --- .../react/src/components/Edges/BezierEdge.tsx | 105 ++++++++------- .../components/Edges/BezierEdgeInternal.tsx | 59 --------- .../src/components/Edges/SimpleBezierEdge.tsx | 103 ++++++++------- .../Edges/SimpleBezierEdgeInternal.tsx | 124 ------------------ .../src/components/Edges/SmoothStepEdge.tsx | 107 ++++++++------- .../Edges/SmoothStepEdgeInternal.tsx | 60 --------- .../react/src/components/Edges/StepEdge.tsx | 32 +++-- .../src/components/Edges/StepEdgeInternal.tsx | 15 --- .../src/components/Edges/StraightEdge.tsx | 89 +++++++------ .../components/Edges/StraightEdgeInternal.tsx | 48 ------- packages/react/src/components/Edges/index.ts | 10 +- .../react/src/components/Edges/internal.ts | 5 - .../react/src/container/EdgeRenderer/utils.ts | 20 ++- .../react/src/container/ReactFlow/index.tsx | 18 ++- packages/react/src/index.ts | 10 +- packages/react/src/types/edges.ts | 11 +- 16 files changed, 287 insertions(+), 529 deletions(-) delete mode 100644 packages/react/src/components/Edges/BezierEdgeInternal.tsx delete mode 100644 packages/react/src/components/Edges/SimpleBezierEdgeInternal.tsx delete mode 100644 packages/react/src/components/Edges/SmoothStepEdgeInternal.tsx delete mode 100644 packages/react/src/components/Edges/StepEdgeInternal.tsx delete mode 100644 packages/react/src/components/Edges/StraightEdgeInternal.tsx delete mode 100644 packages/react/src/components/Edges/internal.ts diff --git a/packages/react/src/components/Edges/BezierEdge.tsx b/packages/react/src/components/Edges/BezierEdge.tsx index 9187131c..2a0b54a2 100644 --- a/packages/react/src/components/Edges/BezierEdge.tsx +++ b/packages/react/src/components/Edges/BezierEdge.tsx @@ -1,61 +1,70 @@ import { memo } from 'react'; -import { type Optional, Position, getBezierPath } from '@xyflow/system'; +import { Position, getBezierPath } from '@xyflow/system'; import BaseEdge from './BaseEdge'; import type { BezierEdgeProps } from '../../types'; -const BezierEdge = memo( - ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - pathOptions, - interactionWidth, - }: Optional) => { - const [path, labelX, labelY] = getBezierPath({ +function createBezierEdge(params: { isInternal: boolean }) { + // eslint-disable-next-line react/display-name + return memo( + ({ + id, sourceX, sourceY, - sourcePosition, targetX, targetY, - targetPosition, - curvature: pathOptions?.curvature, - }); + sourcePosition = Position.Bottom, + targetPosition = Position.Top, + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + markerEnd, + markerStart, + pathOptions, + interactionWidth, + }: BezierEdgeProps) => { + const [path, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + curvature: pathOptions?.curvature, + }); - return ( - - ); - } -); + const _id = params.isInternal ? undefined : id; + + return ( + + ); + } + ); +} + +const BezierEdge = createBezierEdge({ isInternal: false }); +const BezierEdgeInternal = createBezierEdge({ isInternal: true }); BezierEdge.displayName = 'BezierEdge'; +BezierEdgeInternal.displayName = 'BezierEdgeInternal'; -export default BezierEdge; +export { BezierEdge, BezierEdgeInternal }; diff --git a/packages/react/src/components/Edges/BezierEdgeInternal.tsx b/packages/react/src/components/Edges/BezierEdgeInternal.tsx deleted file mode 100644 index 8f446393..00000000 --- a/packages/react/src/components/Edges/BezierEdgeInternal.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { memo } from 'react'; -import { Position, getBezierPath } from '@xyflow/system'; - -import BaseEdge from './BaseEdge'; -import type { BezierEdgeProps } from '../../types'; - -const BezierEdge = memo( - ({ - sourceX, - sourceY, - targetX, - targetY, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - pathOptions, - interactionWidth, - }: BezierEdgeProps) => { - const [path, labelX, labelY] = getBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - curvature: pathOptions?.curvature, - }); - - return ( - - ); - } -); - -BezierEdge.displayName = 'BezierEdge'; - -export default BezierEdge; diff --git a/packages/react/src/components/Edges/SimpleBezierEdge.tsx b/packages/react/src/components/Edges/SimpleBezierEdge.tsx index 1132e216..09c7a752 100644 --- a/packages/react/src/components/Edges/SimpleBezierEdge.tsx +++ b/packages/react/src/components/Edges/SimpleBezierEdge.tsx @@ -1,8 +1,8 @@ import { memo } from 'react'; -import { type Optional, Position, getBezierEdgeCenter } from '@xyflow/system'; +import { Position, getBezierEdgeCenter } from '@xyflow/system'; import BaseEdge from './BaseEdge'; -import type { EdgeProps } from '../../types'; +import type { SimpleBezierEdgeProps } from '../../types'; export interface GetSimpleBezierPathParams { sourceX: number; @@ -71,56 +71,65 @@ export function getSimpleBezierPath({ ]; } -const SimpleBezierEdge = memo( - ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - interactionWidth, - }: Optional) => { - const [path, labelX, labelY] = getSimpleBezierPath({ +function createSimpleBezierEdge(params: { isInternal: boolean }) { + // eslint-disable-next-line react/display-name + return memo( + ({ + id, sourceX, sourceY, - sourcePosition, targetX, targetY, - targetPosition, - }); + sourcePosition = Position.Bottom, + targetPosition = Position.Top, + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + markerEnd, + markerStart, + interactionWidth, + }: SimpleBezierEdgeProps) => { + const [path, labelX, labelY] = getSimpleBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); - return ( - - ); - } -); + const _id = params.isInternal ? undefined : id; + + return ( + + ); + } + ); +} + +const SimpleBezierEdge = createSimpleBezierEdge({ isInternal: false }); +const SimpleBezierEdgeInternal = createSimpleBezierEdge({ isInternal: true }); SimpleBezierEdge.displayName = 'SimpleBezierEdge'; +SimpleBezierEdgeInternal.displayName = 'SimpleBezierEdgeInternal'; -export default SimpleBezierEdge; +export { SimpleBezierEdge, SimpleBezierEdgeInternal }; diff --git a/packages/react/src/components/Edges/SimpleBezierEdgeInternal.tsx b/packages/react/src/components/Edges/SimpleBezierEdgeInternal.tsx deleted file mode 100644 index d9fa50c4..00000000 --- a/packages/react/src/components/Edges/SimpleBezierEdgeInternal.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { memo } from 'react'; -import { Position, getBezierEdgeCenter } from '@xyflow/system'; - -import BaseEdge from './BaseEdge'; -import type { EdgeProps } from '../../types'; - -export interface GetSimpleBezierPathParams { - sourceX: number; - sourceY: number; - sourcePosition?: Position; - targetX: number; - targetY: number; - targetPosition?: Position; -} - -interface GetControlParams { - pos: Position; - x1: number; - y1: number; - x2: number; - y2: number; -} - -function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] { - if (pos === Position.Left || pos === Position.Right) { - return [0.5 * (x1 + x2), y1]; - } - - return [x1, 0.5 * (y1 + y2)]; -} - -export function getSimpleBezierPath({ - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, -}: GetSimpleBezierPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { - const [sourceControlX, sourceControlY] = getControl({ - pos: sourcePosition, - x1: sourceX, - y1: sourceY, - x2: targetX, - y2: targetY, - }); - const [targetControlX, targetControlY] = getControl({ - pos: targetPosition, - x1: targetX, - y1: targetY, - x2: sourceX, - y2: sourceY, - }); - const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, - sourceControlX, - sourceControlY, - targetControlX, - targetControlY, - }); - - return [ - `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`, - labelX, - labelY, - offsetX, - offsetY, - ]; -} - -const SimpleBezierEdge = memo( - ({ - sourceX, - sourceY, - targetX, - targetY, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - interactionWidth, - }: EdgeProps) => { - const [path, labelX, labelY] = getSimpleBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - }); - - return ( - - ); - } -); - -SimpleBezierEdge.displayName = 'SimpleBezierEdge'; - -export default SimpleBezierEdge; diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx index a6965ca7..8eb10d89 100644 --- a/packages/react/src/components/Edges/SmoothStepEdge.tsx +++ b/packages/react/src/components/Edges/SmoothStepEdge.tsx @@ -1,62 +1,71 @@ import { memo } from 'react'; -import { type Optional, Position, getSmoothStepPath } from '@xyflow/system'; +import { Position, getSmoothStepPath } from '@xyflow/system'; import BaseEdge from './BaseEdge'; import type { SmoothStepEdgeProps } from '../../types'; -const SmoothStepEdge = memo( - ({ - id, - sourceX, - sourceY, - targetX, - targetY, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - markerEnd, - markerStart, - pathOptions, - interactionWidth, - }: Optional) => { - const [path, labelX, labelY] = getSmoothStepPath({ +function createSmoothStepEdge(params: { isInternal: boolean }) { + // eslint-disable-next-line react/display-name + return memo( + ({ + id, sourceX, sourceY, - sourcePosition, targetX, targetY, - targetPosition, - borderRadius: pathOptions?.borderRadius, - offset: pathOptions?.offset, - }); + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + sourcePosition = Position.Bottom, + targetPosition = Position.Top, + markerEnd, + markerStart, + pathOptions, + interactionWidth, + }: SmoothStepEdgeProps) => { + const [path, labelX, labelY] = getSmoothStepPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + borderRadius: pathOptions?.borderRadius, + offset: pathOptions?.offset, + }); - return ( - - ); - } -); + const _id = params.isInternal ? undefined : id; + + return ( + + ); + } + ); +} + +const SmoothStepEdge = createSmoothStepEdge({ isInternal: false }); +const SmoothStepEdgeInternal = createSmoothStepEdge({ isInternal: true }); SmoothStepEdge.displayName = 'SmoothStepEdge'; +SmoothStepEdgeInternal.displayName = 'SmoothStepEdgeInternal'; -export default SmoothStepEdge; +export { SmoothStepEdge, SmoothStepEdgeInternal }; diff --git a/packages/react/src/components/Edges/SmoothStepEdgeInternal.tsx b/packages/react/src/components/Edges/SmoothStepEdgeInternal.tsx deleted file mode 100644 index be635a4e..00000000 --- a/packages/react/src/components/Edges/SmoothStepEdgeInternal.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { memo } from 'react'; -import { Position, getSmoothStepPath } from '@xyflow/system'; - -import BaseEdge from './BaseEdge'; -import type { SmoothStepEdgeProps } from '../../types'; - -const SmoothStepEdge = memo( - ({ - sourceX, - sourceY, - targetX, - targetY, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - sourcePosition = Position.Bottom, - targetPosition = Position.Top, - markerEnd, - markerStart, - pathOptions, - interactionWidth, - }: SmoothStepEdgeProps) => { - const [path, labelX, labelY] = getSmoothStepPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - borderRadius: pathOptions?.borderRadius, - offset: pathOptions?.offset, - }); - - return ( - - ); - } -); - -SmoothStepEdge.displayName = 'SmoothStepEdge'; - -export default SmoothStepEdge; diff --git a/packages/react/src/components/Edges/StepEdge.tsx b/packages/react/src/components/Edges/StepEdge.tsx index 8edd819a..bb1833d4 100644 --- a/packages/react/src/components/Edges/StepEdge.tsx +++ b/packages/react/src/components/Edges/StepEdge.tsx @@ -1,16 +1,30 @@ import { memo, useMemo } from 'react'; -import { Optional } from '@xyflow/system'; -import SmoothStepEdge from './SmoothStepEdge'; +import { SmoothStepEdge } from './SmoothStepEdge'; import type { SmoothStepEdgeProps } from '../../types'; -const StepEdge = memo((props: Optional) => ( - ({ borderRadius: 0, offset: props.pathOptions?.offset }), [props.pathOptions?.offset])} - /> -)); +function createStepEdge(params: { isInternal: boolean }) { + // eslint-disable-next-line react/display-name + return memo(({ id, ...props }: SmoothStepEdgeProps) => { + const _id = params.isInternal ? undefined : id; + + return ( + ({ borderRadius: 0, offset: props.pathOptions?.offset }), + [props.pathOptions?.offset] + )} + /> + ); + }); +} + +const StepEdge = createStepEdge({ isInternal: false }); +const StepEdgeInternal = createStepEdge({ isInternal: true }); StepEdge.displayName = 'StepEdge'; +StepEdgeInternal.displayName = 'StepEdgeInternal'; -export default StepEdge; +export { StepEdge, StepEdgeInternal }; diff --git a/packages/react/src/components/Edges/StepEdgeInternal.tsx b/packages/react/src/components/Edges/StepEdgeInternal.tsx deleted file mode 100644 index 2772424d..00000000 --- a/packages/react/src/components/Edges/StepEdgeInternal.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { memo, useMemo } from 'react'; - -import SmoothStepEdgeInternal from './SmoothStepEdgeInternal'; -import type { SmoothStepEdgeProps } from '../../types'; - -const StepEdge = memo((props: SmoothStepEdgeProps) => ( - ({ borderRadius: 0, offset: props.pathOptions?.offset }), [props.pathOptions?.offset])} - /> -)); - -StepEdge.displayName = 'StepEdge'; - -export default StepEdge; diff --git a/packages/react/src/components/Edges/StraightEdge.tsx b/packages/react/src/components/Edges/StraightEdge.tsx index 094eb7f9..a9a453df 100644 --- a/packages/react/src/components/Edges/StraightEdge.tsx +++ b/packages/react/src/components/Edges/StraightEdge.tsx @@ -4,47 +4,56 @@ import { type Optional, getStraightPath } from '@xyflow/system'; import BaseEdge from './BaseEdge'; import type { EdgeProps } from '../../types'; -const StraightEdge = memo( - ({ - id, - sourceX, - sourceY, - targetX, - targetY, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - interactionWidth, - }: Optional) => { - const [path, labelX, labelY] = getStraightPath({ sourceX, sourceY, targetX, targetY }); +function createStraightEdge(params: { isInternal: boolean }) { + // eslint-disable-next-line react/display-name + return memo( + ({ + id, + sourceX, + sourceY, + targetX, + targetY, + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + markerEnd, + markerStart, + interactionWidth, + }: Optional) => { + const [path, labelX, labelY] = getStraightPath({ sourceX, sourceY, targetX, targetY }); - return ( - - ); - } -); + const _id = params.isInternal ? undefined : id; + + return ( + + ); + } + ); +} + +const StraightEdge = createStraightEdge({ isInternal: false }); +const StraightEdgeInternal = createStraightEdge({ isInternal: true }); StraightEdge.displayName = 'StraightEdge'; +StraightEdgeInternal.displayName = 'StraightEdgeInternal'; -export default StraightEdge; +export { StraightEdge, StraightEdgeInternal }; diff --git a/packages/react/src/components/Edges/StraightEdgeInternal.tsx b/packages/react/src/components/Edges/StraightEdgeInternal.tsx deleted file mode 100644 index 06f4a161..00000000 --- a/packages/react/src/components/Edges/StraightEdgeInternal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { memo } from 'react'; -import { getStraightPath } from '@xyflow/system'; - -import BaseEdge from './BaseEdge'; -import type { EdgeProps } from '../../types'; - -const StraightEdge = memo( - ({ - sourceX, - sourceY, - targetX, - targetY, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - markerEnd, - markerStart, - interactionWidth, - }: EdgeProps) => { - const [path, labelX, labelY] = getStraightPath({ sourceX, sourceY, targetX, targetY }); - - return ( - - ); - } -); - -StraightEdge.displayName = 'StraightEdge'; - -export default StraightEdge; diff --git a/packages/react/src/components/Edges/index.ts b/packages/react/src/components/Edges/index.ts index e4d30109..dcd45701 100644 --- a/packages/react/src/components/Edges/index.ts +++ b/packages/react/src/components/Edges/index.ts @@ -1,5 +1,5 @@ -export { default as SimpleBezierEdge } from './SimpleBezierEdge'; -export { default as SmoothStepEdge } from './SmoothStepEdge'; -export { default as StepEdge } from './StepEdge'; -export { default as StraightEdge } from './StraightEdge'; -export { default as BezierEdge } from './BezierEdge'; +export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge'; +export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge'; +export { StepEdge, StepEdgeInternal } from './StepEdge'; +export { StraightEdge, StraightEdgeInternal } from './StraightEdge'; +export { BezierEdge, BezierEdgeInternal } from './BezierEdge'; diff --git a/packages/react/src/components/Edges/internal.ts b/packages/react/src/components/Edges/internal.ts deleted file mode 100644 index 11b86932..00000000 --- a/packages/react/src/components/Edges/internal.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as SimpleBezierEdge } from './SimpleBezierEdgeInternal'; -export { default as SmoothStepEdge } from './SmoothStepEdgeInternal'; -export { default as StepEdge } from './StepEdgeInternal'; -export { default as StraightEdge } from './StraightEdgeInternal'; -export { default as BezierEdge } from './BezierEdgeInternal'; diff --git a/packages/react/src/container/EdgeRenderer/utils.ts b/packages/react/src/container/EdgeRenderer/utils.ts index 03e0dba3..433bb0f0 100644 --- a/packages/react/src/container/EdgeRenderer/utils.ts +++ b/packages/react/src/container/EdgeRenderer/utils.ts @@ -1,6 +1,12 @@ import type { ComponentType } from 'react'; -import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges/internal'; +import { + BezierEdgeInternal, + SmoothStepEdgeInternal, + StepEdgeInternal, + StraightEdgeInternal, + SimpleBezierEdgeInternal, +} from '../../components/Edges'; import wrapEdge from '../../components/Edges/wrapEdge'; import type { EdgeProps, EdgeTypes, EdgeTypesWrapped } from '../../types'; @@ -8,18 +14,18 @@ export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped; export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped { const standardTypes: EdgeTypesWrapped = { - default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType), - straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType), - step: wrapEdge((edgeTypes.step || StepEdge) as ComponentType), - smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdge) as ComponentType), - simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdge) as ComponentType), + default: wrapEdge((edgeTypes.default || BezierEdgeInternal) as ComponentType), + straight: wrapEdge((edgeTypes.bezier || StraightEdgeInternal) as ComponentType), + step: wrapEdge((edgeTypes.step || StepEdgeInternal) as ComponentType), + smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdgeInternal) as ComponentType), + simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdgeInternal) as ComponentType), }; const wrappedTypes = {} as EdgeTypesWrapped; const specialTypes: EdgeTypesWrapped = Object.keys(edgeTypes) .filter((k) => !['default', 'bezier'].includes(k)) .reduce((res, key) => { - res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType); + res[key] = wrapEdge((edgeTypes[key] || BezierEdgeInternal) as ComponentType); return res; }, wrappedTypes); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index d780046e..a7360a46 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -12,7 +12,13 @@ import { } from '@xyflow/system'; import Attribution from '../../components/Attribution'; -import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges/internal'; +import { + BezierEdgeInternal, + SmoothStepEdgeInternal, + StepEdgeInternal, + StraightEdgeInternal, + SimpleBezierEdgeInternal, +} from '../../components/Edges'; import DefaultNode from '../../components/Nodes/DefaultNode'; import InputNode from '../../components/Nodes/InputNode'; import OutputNode from '../../components/Nodes/OutputNode'; @@ -33,11 +39,11 @@ const defaultNodeTypes: NodeTypes = { }; const defaultEdgeTypes: EdgeTypes = { - default: BezierEdge, - straight: StraightEdge, - step: StepEdge, - smoothstep: SmoothStepEdge, - simplebezier: SimpleBezierEdge, + default: BezierEdgeInternal, + straight: StraightEdgeInternal, + step: StepEdgeInternal, + smoothstep: SmoothStepEdgeInternal, + simplebezier: SimpleBezierEdgeInternal, }; const initNodeOrigin: NodeOrigin = [0, 0]; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 4fe922e5..2a693638 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,11 +1,11 @@ export { default as ReactFlow } from './container/ReactFlow'; export { default as Handle } from './components/Handle'; export { default as EdgeText } from './components/Edges/EdgeText'; -export { default as StraightEdge } from './components/Edges/StraightEdge'; -export { default as StepEdge } from './components/Edges/StepEdge'; -export { default as BezierEdge } from './components/Edges/BezierEdge'; -export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/Edges/SimpleBezierEdge'; -export { default as SmoothStepEdge } from './components/Edges/SmoothStepEdge'; +export { StraightEdge } from './components/Edges/StraightEdge'; +export { StepEdge } from './components/Edges/StepEdge'; +export { BezierEdge } from './components/Edges/BezierEdge'; +export { SimpleBezierEdge, getSimpleBezierPath } from './components/Edges/SimpleBezierEdge'; +export { SmoothStepEdge } from './components/Edges/SmoothStepEdge'; export { default as BaseEdge } from './components/Edges/BaseEdge'; export { default as ReactFlowProvider } from './components/ReactFlowProvider'; export { default as Panel, type PanelProps } from './components/Panel'; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index cd29a85f..db34416d 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -13,6 +13,7 @@ import type { HandleElement, ConnectionStatus, EdgePosition, + Optional, } from '@xyflow/system'; import { Node } from '.'; @@ -100,14 +101,20 @@ export type BaseEdgeProps = Pick = EdgeProps & { +export type EdgeComponentProps = Optional, 'source' | 'target'>, 'id'>; + +export type StraightEdgeProps = EdgeComponentProps; + +export type SmoothStepEdgeProps = EdgeComponentProps & { pathOptions?: SmoothStepPathOptions; }; -export type BezierEdgeProps = EdgeProps & { +export type BezierEdgeProps = EdgeComponentProps & { pathOptions?: BezierPathOptions; }; +export type SimpleBezierEdgeProps = EdgeComponentProps; + export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void; export type ConnectionLineComponentProps = { From 6825b486ecea591ef61324324fc3e72996da8874 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 30 Nov 2023 10:41:18 +0100 Subject: [PATCH 13/32] refactor(svelte/edges): simplify --- .../react/src/components/Edges/StepEdge.tsx | 4 +-- packages/react/src/components/Edges/index.ts | 4 +++ packages/react/src/types/edges.ts | 12 ++++++- .../components/EdgeWrapper/EdgeWrapper.svelte | 4 +-- .../lib/components/edges/BezierEdge.svelte | 30 ------------------ .../BaseBezierEdge.svelte} | 10 ++++-- .../edges/BezierEdge/BezierEdge.svelte | 10 ++++++ .../BezierEdge/BezierEdgeInternal.svelte | 10 ++++++ .../components/edges/SmoothStepEdge.svelte | 30 ------------------ .../BaseSmoothStepEdge.svelte} | 11 +++++-- .../SmoothStepEdge/SmoothStepEdge.svelte | 10 ++++++ .../SmoothStepEdgeInternal.svelte | 10 ++++++ .../src/lib/components/edges/StepEdge.svelte | 31 ------------------- .../BaseStepEdge.svelte} | 10 ++++-- .../components/edges/StepEdge/StepEdge.svelte | 10 ++++++ .../edges/StepEdge/StepEdgeInternal.svelte | 10 ++++++ .../lib/components/edges/StraightEdge.svelte | 28 ----------------- .../BaseStraightEdge.svelte} | 7 +++-- .../edges/StraightEdge/StraightEdge.svelte | 10 ++++++ .../StraightEdge/StraightEdgeInternal.svelte | 10 ++++++ .../svelte/src/lib/components/edges/index.ts | 19 +++++++++--- .../src/lib/components/edges/internal.ts | 4 --- .../svelte/src/lib/store/initial-store.ts | 15 ++++++--- packages/svelte/src/lib/types/edges.ts | 23 ++++++++++++-- packages/system/src/types/edges.ts | 4 +++ 25 files changed, 176 insertions(+), 150 deletions(-) delete mode 100644 packages/svelte/src/lib/components/edges/BezierEdge.svelte rename packages/svelte/src/lib/components/edges/{BezierEdgeInternal.svelte => BezierEdge/BaseBezierEdge.svelte} (69%) create mode 100644 packages/svelte/src/lib/components/edges/BezierEdge/BezierEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte delete mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte rename packages/svelte/src/lib/components/edges/{SmoothStepEdgeInternal.svelte => SmoothStepEdge/BaseSmoothStepEdge.svelte} (65%) create mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StepEdge.svelte rename packages/svelte/src/lib/components/edges/{StepEdgeInternal.svelte => StepEdge/BaseStepEdge.svelte} (73%) create mode 100644 packages/svelte/src/lib/components/edges/StepEdge/StepEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StraightEdge.svelte rename packages/svelte/src/lib/components/edges/{StraightEdgeInternal.svelte => StraightEdge/BaseStraightEdge.svelte} (75%) create mode 100644 packages/svelte/src/lib/components/edges/StraightEdge/StraightEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte delete mode 100644 packages/svelte/src/lib/components/edges/internal.ts diff --git a/packages/react/src/components/Edges/StepEdge.tsx b/packages/react/src/components/Edges/StepEdge.tsx index bb1833d4..6385e8f0 100644 --- a/packages/react/src/components/Edges/StepEdge.tsx +++ b/packages/react/src/components/Edges/StepEdge.tsx @@ -1,11 +1,11 @@ import { memo, useMemo } from 'react'; import { SmoothStepEdge } from './SmoothStepEdge'; -import type { SmoothStepEdgeProps } from '../../types'; +import type { StepEdgeProps } from '../../types'; function createStepEdge(params: { isInternal: boolean }) { // eslint-disable-next-line react/display-name - return memo(({ id, ...props }: SmoothStepEdgeProps) => { + return memo(({ id, ...props }: StepEdgeProps) => { const _id = params.isInternal ? undefined : id; return ( diff --git a/packages/react/src/components/Edges/index.ts b/packages/react/src/components/Edges/index.ts index dcd45701..21af9667 100644 --- a/packages/react/src/components/Edges/index.ts +++ b/packages/react/src/components/Edges/index.ts @@ -1,3 +1,7 @@ +// We distinguish between internal and exported edges +// The internal edges are used directly like custom edges and always get an id, source and target props +// If you import an edge from the library, the id is optional and source and target are not used at all + export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge'; export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge'; export { StepEdge, StepEdgeInternal } from './StepEdge'; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index db34416d..896c4605 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -14,6 +14,7 @@ import type { ConnectionStatus, EdgePosition, Optional, + StepPathOptions, } from '@xyflow/system'; import { Node } from '.'; @@ -47,7 +48,12 @@ type BezierEdgeType = DefaultEdge & { pathOptions?: BezierPathOptions; }; -export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeType; +type StepEdgeType = DefaultEdge & { + type: 'step'; + pathOptions?: StepPathOptions; +}; + +export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeType | StepEdgeType; export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void; @@ -113,6 +119,10 @@ export type BezierEdgeProps = EdgeComponentProps & { pathOptions?: BezierPathOptions; }; +export type StepEdgeProps = EdgeComponentProps & { + pathOptions?: StepPathOptions; +}; + export type SimpleBezierEdgeProps = EdgeComponentProps; export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void; diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index fa4adc92..aad6d2b1 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -6,7 +6,7 @@ import { errorMessages, getMarkerId } from '@xyflow/system'; import { useStore } from '$lib/store'; - import { BezierEdge } from '$lib/components/edges/internal'; + import { BezierEdgeInternal } from '$lib/components/edges'; import type { EdgeLayouted, Edge } from '$lib/types'; import { get } from 'svelte/store'; @@ -57,7 +57,7 @@ edgecontextmenu: { edge: Edge; event: MouseEvent }; }>(); - $: edgeComponent = $edgeTypes[type!] || BezierEdge; + $: edgeComponent = $edgeTypes[type!] || BezierEdgeInternal; $: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined; $: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined; diff --git a/packages/svelte/src/lib/components/edges/BezierEdge.svelte b/packages/svelte/src/lib/components/edges/BezierEdge.svelte deleted file mode 100644 index ef5fd3db..00000000 --- a/packages/svelte/src/lib/components/edges/BezierEdge.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte similarity index 69% rename from packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte rename to packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte index a1ca0658..749a5096 100644 --- a/packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte +++ b/packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte @@ -1,10 +1,10 @@ + import BaseBezierEdge from './BaseBezierEdge.svelte'; + import type { BezierEdgeProps } from '$lib/types'; + + type $$Props = BezierEdgeProps; + + $: props = $$props as $$Props; + + + diff --git a/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte new file mode 100644 index 00000000..75d168f4 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte deleted file mode 100644 index f528da0e..00000000 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte similarity index 65% rename from packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte rename to packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte index 73324a89..5ec4dd89 100644 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte @@ -1,10 +1,10 @@ + import BaseSmoothStepEdge from './BaseSmoothStepEdge.svelte'; + import type { SmoothStepEdgeProps } from '$lib/types'; + + type $$Props = SmoothStepEdgeProps; + + $: props = $$props as $$Props; + + + diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte new file mode 100644 index 00000000..803247c2 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StepEdge.svelte b/packages/svelte/src/lib/components/edges/StepEdge.svelte deleted file mode 100644 index b0860d03..00000000 --- a/packages/svelte/src/lib/components/edges/StepEdge.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte similarity index 73% rename from packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte rename to packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte index f793376b..4099e7d5 100644 --- a/packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte +++ b/packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte @@ -1,10 +1,10 @@ + import BaseStepEdge from './BaseStepEdge.svelte'; + import type { StepEdgeProps } from '$lib/types'; + + type $$Props = StepEdgeProps; + + $: props = $$props as $$Props; + + + diff --git a/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte new file mode 100644 index 00000000..21109992 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StraightEdge.svelte b/packages/svelte/src/lib/components/edges/StraightEdge.svelte deleted file mode 100644 index 979c4351..00000000 --- a/packages/svelte/src/lib/components/edges/StraightEdge.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte similarity index 75% rename from packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte rename to packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte index 8cc984d7..5db480ca 100644 --- a/packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte +++ b/packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte @@ -1,10 +1,10 @@ + import BaseStraighEdge from './BaseStraightEdge.svelte'; + import type { StraightEdgeProps } from '$lib/types'; + + type $$Props = StraightEdgeProps; + + $: props = $$props as $$Props; + + + diff --git a/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte new file mode 100644 index 00000000..bad893b2 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/index.ts b/packages/svelte/src/lib/components/edges/index.ts index 1f1567b6..19f1f129 100644 --- a/packages/svelte/src/lib/components/edges/index.ts +++ b/packages/svelte/src/lib/components/edges/index.ts @@ -1,4 +1,15 @@ -export { default as BezierEdge } from './BezierEdge.svelte'; -export { default as StraightEdge } from './StraightEdge.svelte'; -export { default as SmoothStepEdge } from './SmoothStepEdge.svelte'; -export { default as StepEdge } from './StepEdge.svelte'; +// We distinguish between internal and exported edges +// The internal edges are used directly like custom edges and always get an id, source and target props +// If you import an edge from the library, the id is optional and source and target are not used at all + +export { default as BezierEdge } from './BezierEdge/BezierEdge.svelte'; +export { default as BezierEdgeInternal } from './BezierEdge/BezierEdgeInternal.svelte'; + +export { default as SmoothStepEdge } from './SmoothStepEdge/BaseSmoothStepEdge.svelte'; +export { default as SmoothStepEdgeInternal } from './SmoothStepEdge/SmoothStepEdgeInternal.svelte'; + +export { default as StraightEdge } from './StraightEdge/StraightEdge.svelte'; +export { default as StraightEdgeInternal } from './StraightEdge/StraightEdgeInternal.svelte'; + +export { default as StepEdge } from './StepEdge/StepEdge.svelte'; +export { default as StepEdgeInternal } from './StepEdge/StepEdgeInternal.svelte'; diff --git a/packages/svelte/src/lib/components/edges/internal.ts b/packages/svelte/src/lib/components/edges/internal.ts deleted file mode 100644 index f2582b79..00000000 --- a/packages/svelte/src/lib/components/edges/internal.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as BezierEdge } from './BezierEdgeInternal.svelte'; -export { default as StraightEdge } from './StraightEdgeInternal.svelte'; -export { default as SmoothStepEdge } from './SmoothStepEdgeInternal.svelte'; -export { default as StepEdge } from './StepEdgeInternal.svelte'; diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 65acad86..dcfee4db 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -25,7 +25,12 @@ 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 { BezierEdge, StraightEdge, StepEdge, SmoothStepEdge } from '$lib/components/edges/internal'; +import { + BezierEdgeInternal, + SmoothStepEdgeInternal, + StraightEdgeInternal, + StepEdgeInternal +} from '$lib/components/edges'; import type { NodeTypes, @@ -47,10 +52,10 @@ export const initialNodeTypes = { }; export const initialEdgeTypes = { - straight: StraightEdge, - smoothstep: SmoothStepEdge, - default: BezierEdge, - step: StepEdge + straight: StraightEdgeInternal, + smoothstep: SmoothStepEdgeInternal, + default: BezierEdgeInternal, + step: StepEdgeInternal }; export const getInitialStore = ({ diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 33f0d908..0a666ce7 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -5,7 +5,9 @@ import type { BezierPathOptions, DefaultEdgeOptionsBase, EdgePosition, - SmoothStepPathOptions + SmoothStepPathOptions, + Optional, + StepPathOptions } from '@xyflow/system'; import type { Node } from '$lib/types'; @@ -29,6 +31,7 @@ type BezierEdgeType = DefaultEdge & { type StepEdgeType = DefaultEdge & { type: 'step'; + pathOptions?: StepPathOptions; }; export type Edge = @@ -37,7 +40,7 @@ export type Edge = | BezierEdgeType | StepEdgeType; -export type EdgeProps = Omit & +export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle'> & EdgePosition & { markerStart?: string; markerEnd?: string; @@ -45,6 +48,22 @@ export type EdgeProps = Omit & targetHandleId?: string | null; }; +export type EdgeComponentProps = Optional, 'source' | 'target'>, 'id'>; + +export type BezierEdgeProps = EdgeComponentProps & { + pathOptions?: BezierPathOptions; +}; + +export type SmoothStepEdgeProps = EdgeComponentProps & { + pathOptions?: SmoothStepPathOptions; +}; + +export type StepEdgeProps = EdgeComponentProps & { + pathOptions?: StepPathOptions; +}; + +export type StraightEdgeProps = EdgeComponentProps; + export type EdgeTypes = Record>>; export type DefaultEdgeOptions = Omit, 'focusable'>; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 855682d2..65c8e641 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -27,6 +27,10 @@ export type SmoothStepPathOptions = { borderRadius?: number; }; +export type StepPathOptions = { + offset?: number; +}; + export type BezierPathOptions = { curvature?: number; }; From a70e25b1a244861caf3fbf8a95e042a0ff1ba02b Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 30 Nov 2023 12:58:44 +0100 Subject: [PATCH 14/32] refactor(svelte/edges): cleanup types and components --- .../src/routes/examples/edges/+page.svelte | 14 ++- .../routes/examples/edges/ButtonEdge.svelte | 92 +++++++++++++++++++ .../examples/edges/CustomBezierEdge.svelte | 55 +++++++++++ .../src/components/Edges/StraightEdge.tsx | 6 +- packages/react/src/types/edges.ts | 2 +- .../lib/components/edges/BezierEdge.svelte | 48 ++++++++++ .../edges/BezierEdge/BaseBezierEdge.svelte | 33 ------- .../edges/BezierEdge/BezierEdge.svelte | 10 -- .../BezierEdge/BezierEdgeInternal.svelte | 10 -- .../edges/BezierEdgeInternal.svelte | 63 +++++++++++++ .../components/edges/SmoothStepEdge.svelte | 49 ++++++++++ .../SmoothStepEdge/BaseSmoothStepEdge.svelte | 34 ------- .../SmoothStepEdge/SmoothStepEdge.svelte | 10 -- .../SmoothStepEdgeInternal.svelte | 10 -- .../edges/SmoothStepEdgeInternal.svelte | 63 +++++++++++++ .../src/lib/components/edges/StepEdge.svelte | 49 ++++++++++ .../edges/StepEdge/BaseStepEdge.svelte | 34 ------- .../components/edges/StepEdge/StepEdge.svelte | 10 -- .../edges/StepEdge/StepEdgeInternal.svelte | 10 -- .../components/edges/StepEdgeInternal.svelte | 64 +++++++++++++ .../lib/components/edges/StraightEdge.svelte | 42 +++++++++ .../StraightEdge/BaseStraightEdge.svelte | 30 ------ .../edges/StraightEdge/StraightEdge.svelte | 10 -- .../StraightEdge/StraightEdgeInternal.svelte | 10 -- .../edges/StraightEdgeInternal.svelte | 63 +++++++++++++ .../svelte/src/lib/components/edges/index.ts | 18 ++-- packages/svelte/src/lib/index.ts | 13 ++- packages/svelte/src/lib/types/edges.ts | 15 ++- packages/svelte/tsconfig.json | 3 +- tooling/tsconfig/react.json | 3 +- 30 files changed, 640 insertions(+), 233 deletions(-) create mode 100644 examples/svelte/src/routes/examples/edges/ButtonEdge.svelte create mode 100644 examples/svelte/src/routes/examples/edges/CustomBezierEdge.svelte create mode 100644 packages/svelte/src/lib/components/edges/BezierEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/BezierEdge/BezierEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/StepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StepEdge/StepEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/StraightEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StraightEdge/StraightEdge.svelte delete mode 100644 packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte create mode 100644 packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte diff --git a/examples/svelte/src/routes/examples/edges/+page.svelte b/examples/svelte/src/routes/examples/edges/+page.svelte index 403b0ef6..8a2471b8 100644 --- a/examples/svelte/src/routes/examples/edges/+page.svelte +++ b/examples/svelte/src/routes/examples/edges/+page.svelte @@ -10,6 +10,8 @@ } from '@xyflow/svelte'; import '@xyflow/svelte/dist/style.css'; + import ButtonEdge from './ButtonEdge.svelte'; + import CustomBezierEdge from './CustomBezierEdge.svelte'; const nodes = writable([ { @@ -110,13 +112,14 @@ id: 'e5-8', source: '5', target: '8', - data: { text: 'custom edge' } + type: 'button' }, { id: 'e5-9', source: '5', target: '9', - data: { text: 'custom edge 2' } + type: 'customBezier', + label: 'custom bezier' }, { id: 'e5-6', @@ -143,9 +146,14 @@ } } ]); + + const edgeTypes = { + button: ButtonEdge, + customBezier: CustomBezierEdge + }; - + diff --git a/examples/svelte/src/routes/examples/edges/ButtonEdge.svelte b/examples/svelte/src/routes/examples/edges/ButtonEdge.svelte new file mode 100644 index 00000000..235aab98 --- /dev/null +++ b/examples/svelte/src/routes/examples/edges/ButtonEdge.svelte @@ -0,0 +1,92 @@ + + + + +
+ +
+ + + diff --git a/examples/svelte/src/routes/examples/edges/CustomBezierEdge.svelte b/examples/svelte/src/routes/examples/edges/CustomBezierEdge.svelte new file mode 100644 index 00000000..48d86c3d --- /dev/null +++ b/examples/svelte/src/routes/examples/edges/CustomBezierEdge.svelte @@ -0,0 +1,55 @@ + + + diff --git a/packages/react/src/components/Edges/StraightEdge.tsx b/packages/react/src/components/Edges/StraightEdge.tsx index a9a453df..5a3c824d 100644 --- a/packages/react/src/components/Edges/StraightEdge.tsx +++ b/packages/react/src/components/Edges/StraightEdge.tsx @@ -1,8 +1,8 @@ import { memo } from 'react'; -import { type Optional, getStraightPath } from '@xyflow/system'; +import { getStraightPath } from '@xyflow/system'; import BaseEdge from './BaseEdge'; -import type { EdgeProps } from '../../types'; +import type { StraightEdgeProps } from '../../types'; function createStraightEdge(params: { isInternal: boolean }) { // eslint-disable-next-line react/display-name @@ -23,7 +23,7 @@ function createStraightEdge(params: { isInternal: boolean }) { markerEnd, markerStart, interactionWidth, - }: Optional) => { + }: StraightEdgeProps) => { const [path, labelX, labelY] = getStraightPath({ sourceX, sourceY, targetX, targetY }); const _id = params.isInternal ? undefined : id; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 896c4605..287c277d 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -109,7 +109,7 @@ export type BaseEdgeProps = Pick = Optional, 'source' | 'target'>, 'id'>; -export type StraightEdgeProps = EdgeComponentProps; +export type StraightEdgeProps = Omit, 'sourcePosition' | 'targetPosition'>; export type SmoothStepEdgeProps = EdgeComponentProps & { pathOptions?: SmoothStepPathOptions; diff --git a/packages/svelte/src/lib/components/edges/BezierEdge.svelte b/packages/svelte/src/lib/components/edges/BezierEdge.svelte new file mode 100644 index 00000000..9d32f4c7 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/BezierEdge.svelte @@ -0,0 +1,48 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte b/packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte deleted file mode 100644 index 749a5096..00000000 --- a/packages/svelte/src/lib/components/edges/BezierEdge/BaseBezierEdge.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdge.svelte b/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdge.svelte deleted file mode 100644 index 9f93e7df..00000000 --- a/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdge.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte deleted file mode 100644 index 75d168f4..00000000 --- a/packages/svelte/src/lib/components/edges/BezierEdge/BezierEdgeInternal.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte new file mode 100644 index 00000000..9431a05d --- /dev/null +++ b/packages/svelte/src/lib/components/edges/BezierEdgeInternal.svelte @@ -0,0 +1,63 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte new file mode 100644 index 00000000..3e27368f --- /dev/null +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte @@ -0,0 +1,49 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte deleted file mode 100644 index 5ec4dd89..00000000 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge/BaseSmoothStepEdge.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdge.svelte deleted file mode 100644 index 1cac5d1c..00000000 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdge.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte deleted file mode 100644 index 803247c2..00000000 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge/SmoothStepEdgeInternal.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte new file mode 100644 index 00000000..28e87269 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdgeInternal.svelte @@ -0,0 +1,63 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StepEdge.svelte b/packages/svelte/src/lib/components/edges/StepEdge.svelte new file mode 100644 index 00000000..5ca6dc00 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StepEdge.svelte @@ -0,0 +1,49 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte b/packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte deleted file mode 100644 index 4099e7d5..00000000 --- a/packages/svelte/src/lib/components/edges/StepEdge/BaseStepEdge.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StepEdge/StepEdge.svelte b/packages/svelte/src/lib/components/edges/StepEdge/StepEdge.svelte deleted file mode 100644 index 49522e95..00000000 --- a/packages/svelte/src/lib/components/edges/StepEdge/StepEdge.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte deleted file mode 100644 index 21109992..00000000 --- a/packages/svelte/src/lib/components/edges/StepEdge/StepEdgeInternal.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte new file mode 100644 index 00000000..248e5912 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StepEdgeInternal.svelte @@ -0,0 +1,64 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StraightEdge.svelte b/packages/svelte/src/lib/components/edges/StraightEdge.svelte new file mode 100644 index 00000000..c868752f --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StraightEdge.svelte @@ -0,0 +1,42 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte b/packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte deleted file mode 100644 index 5db480ca..00000000 --- a/packages/svelte/src/lib/components/edges/StraightEdge/BaseStraightEdge.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdge.svelte b/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdge.svelte deleted file mode 100644 index d7153544..00000000 --- a/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdge.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte deleted file mode 100644 index bad893b2..00000000 --- a/packages/svelte/src/lib/components/edges/StraightEdge/StraightEdgeInternal.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte b/packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte new file mode 100644 index 00000000..0d436d52 --- /dev/null +++ b/packages/svelte/src/lib/components/edges/StraightEdgeInternal.svelte @@ -0,0 +1,63 @@ + + + diff --git a/packages/svelte/src/lib/components/edges/index.ts b/packages/svelte/src/lib/components/edges/index.ts index 19f1f129..4be51363 100644 --- a/packages/svelte/src/lib/components/edges/index.ts +++ b/packages/svelte/src/lib/components/edges/index.ts @@ -2,14 +2,16 @@ // The internal edges are used directly like custom edges and always get an id, source and target props // If you import an edge from the library, the id is optional and source and target are not used at all -export { default as BezierEdge } from './BezierEdge/BezierEdge.svelte'; -export { default as BezierEdgeInternal } from './BezierEdge/BezierEdgeInternal.svelte'; +// @todo: how can we prevent this duplication in ...Edge/ ...EdgeInternal? +// both are quite similar, it's just about 1-2 props that are different +export { default as BezierEdge } from './BezierEdge.svelte'; +export { default as BezierEdgeInternal } from './BezierEdgeInternal.svelte'; -export { default as SmoothStepEdge } from './SmoothStepEdge/BaseSmoothStepEdge.svelte'; -export { default as SmoothStepEdgeInternal } from './SmoothStepEdge/SmoothStepEdgeInternal.svelte'; +export { default as SmoothStepEdge } from './SmoothStepEdge.svelte'; +export { default as SmoothStepEdgeInternal } from './SmoothStepEdgeInternal.svelte'; -export { default as StraightEdge } from './StraightEdge/StraightEdge.svelte'; -export { default as StraightEdgeInternal } from './StraightEdge/StraightEdgeInternal.svelte'; +export { default as StraightEdge } from './StraightEdge.svelte'; +export { default as StraightEdgeInternal } from './StraightEdgeInternal.svelte'; -export { default as StepEdge } from './StepEdge/StepEdge.svelte'; -export { default as StepEdgeInternal } from './StepEdge/StepEdgeInternal.svelte'; +export { default as StepEdge } from './StepEdge.svelte'; +export { default as StepEdgeInternal } from './StepEdgeInternal.svelte'; diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index d9e0885f..a9107415 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -7,7 +7,7 @@ export * from '$lib/container/Panel'; export * from '$lib/components/SvelteFlowProvider'; export * from '$lib/components/EdgeLabelRenderer'; export * from '$lib/components/BaseEdge'; -export * from '$lib/components/edges'; +export { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '$lib/components/edges'; export * from '$lib/components/Handle'; // plugins @@ -29,7 +29,16 @@ export * from '$lib/hooks/useConnection'; export * from '$lib/hooks/useNodesEdges'; // types -export type { Edge, EdgeProps, EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges'; +export type { + Edge, + EdgeProps, + BezierEdgeProps, + SmoothStepEdgeProps, + StepEdgeProps, + StraightEdgeProps, + EdgeTypes, + DefaultEdgeOptions +} from '$lib/types/edges'; export type { HandleComponentProps, FitViewOptions } from '$lib/types/general'; export type { Node, NodeTypes, DefaultNodeOptions } from '$lib/types/nodes'; export type { SvelteFlowStore } from '$lib/store/types'; diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 0a666ce7..3f1f0e8d 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -40,7 +40,7 @@ export type Edge = | BezierEdgeType | StepEdgeType; -export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle'> & +export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle' | 'type'> & EdgePosition & { markerStart?: string; markerEnd?: string; @@ -48,7 +48,13 @@ export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle'> targetHandleId?: string | null; }; -export type EdgeComponentProps = Optional, 'source' | 'target'>, 'id'>; +export type EdgeComponentProps = Optional< + Omit< + EdgeProps, + 'source' | 'target' | 'sourceHandleId' | 'targetHandleId' | 'animated' | 'selected' | 'data' + >, + 'id' +>; export type BezierEdgeProps = EdgeComponentProps & { pathOptions?: BezierPathOptions; @@ -62,7 +68,10 @@ export type StepEdgeProps = EdgeComponentProps & { pathOptions?: StepPathOptions; }; -export type StraightEdgeProps = EdgeComponentProps; +export type StraightEdgeProps = Omit< + EdgeComponentProps, + 'sourcePosition' | 'targetPosition' +>; export type EdgeTypes = Record>>; diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 8fbe9e82..d9cfe5d6 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -8,7 +8,8 @@ "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - "strict": true + "strict": true, + "noErrorTruncation": true } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias diff --git a/tooling/tsconfig/react.json b/tooling/tsconfig/react.json index e3cb9853..33c4fc1f 100644 --- a/tooling/tsconfig/react.json +++ b/tooling/tsconfig/react.json @@ -6,6 +6,7 @@ "lib": ["dom", "esnext"], "module": "esnext", "target": "esnext", - "outDir": "dist" + "outDir": "dist", + "noErrorTruncation": true } } From b2a1ab06108cb6489dcfa697a8519cb63253132b Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 30 Nov 2023 14:46:07 +0100 Subject: [PATCH 15/32] refactor(svelte): do not update nodes on pane click #3638 --- packages/svelte/src/lib/store/index.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index ec771416..873d9b6f 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -192,15 +192,20 @@ export function createStore({ } function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) { - const nodeIdsToUnselect = (params?.nodes ? params.nodes : get(store.nodes)).map( - (item) => item.id - ); - const edgeIdsToUnselect = (params?.edges ? params.edges : get(store.edges)).map( - (item) => item.id - ); + const selectedNodeIds = (params?.nodes ? params.nodes : get(store.nodes)) + .filter((node) => node.selected) + .map((node) => node.id); + const selectedEdgeIds = (params?.edges ? params.edges : get(store.edges)) + .filter((edge) => edge.selected) + .map((edge) => edge.id); - store.nodes.update((ns) => ns.map(resetSelectedItem(nodeIdsToUnselect))); - store.edges.update((es) => es.map(resetSelectedItem(edgeIdsToUnselect))); + if (selectedNodeIds.length) { + store.nodes.update((ns) => ns.map(resetSelectedItem(selectedNodeIds))); + } + + if (selectedEdgeIds.length) { + store.edges.update((es) => es.map(resetSelectedItem(selectedEdgeIds))); + } } store.deleteKeyPressed.subscribe((deleteKeyPressed) => { From 124df00f60762518fc0b390af1702e70f74c8494 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 1 Dec 2023 12:10:01 +0100 Subject: [PATCH 16/32] feat(react): connection lookup draft --- examples/react/src/App/routes.ts | 6 + .../examples/CustomNode/ColorSelectorNode.tsx | 2 +- .../MultiHandleNode.tsx | 40 ++++++ .../SingleHandleNode.tsx | 42 +++++++ .../UseHandleConnectionStatus/index.tsx | 118 ++++++++++++++++++ .../src/hooks/useHandleConnectionStatus.ts | 85 +++++++++++++ packages/react/src/index.ts | 1 + packages/react/src/store/index.ts | 10 +- packages/react/src/store/initialState.ts | 4 + packages/react/src/store/utils.ts | 24 ++++ packages/react/src/types/store.ts | 2 + 11 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx create mode 100644 examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx create mode 100644 examples/react/src/examples/UseHandleConnectionStatus/index.tsx create mode 100644 packages/react/src/hooks/useHandleConnectionStatus.ts diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 2665abfa..18307c79 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -45,6 +45,7 @@ import InteractiveMinimap from '../examples/InteractiveMinimap'; import UseOnSelectionChange from '../examples/UseOnSelectionChange'; import NodeToolbar from '../examples/NodeToolbar'; import useNodesInitialized from '../examples/UseNodesInit'; +import useHandleConnectionStatus from '../examples/UseHandleConnectionStatus'; export interface IRoute { name: string; @@ -273,6 +274,11 @@ const routes: IRoute[] = [ path: 'usereactflow', component: UseReactFlow, }, + { + name: 'useHandleConnectionStatus', + path: 'usehandleconnectionstatus', + component: useHandleConnectionStatus, + }, { name: 'useUpdateNodeInternals', path: 'useupdatenodeinternals', diff --git a/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx b/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx index aebc8e0e..6857e1ac 100644 --- a/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx +++ b/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx @@ -1,4 +1,4 @@ -import React, { memo, FC, CSSProperties, useCallback } from 'react'; +import React, { memo, FC, CSSProperties, useCallback, useEffect } from 'react'; import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react'; const targetHandleStyle: CSSProperties = { background: '#555' }; diff --git a/examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx b/examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx new file mode 100644 index 00000000..f8a31143 --- /dev/null +++ b/examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx @@ -0,0 +1,40 @@ +import { memo, FC, useEffect, useCallback } from 'react'; +import { Handle, Position, NodeProps, useHandleConnectionStatus, Connection } from '@xyflow/react'; +import { HandleComponentProps } from '@xyflow/react/dist/esm/components/Handle'; + +function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { + const onConnect = useCallback( + (connections: Connection[]) => console.log('onConnect handler, node id:', nodeId, connections), + [nodeId] + ); + + const onDisconnect = useCallback( + (connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections), + [nodeId] + ); + const status = useHandleConnectionStatus({ + handleType: handleProps.type, + handleId: handleProps.id, + onConnect, + onDisconnect, + }); + + useEffect(() => { + console.log('useEffect, node id:', nodeId, handleProps.type, status); + }, [status]); + + return ; +} + +const CustomNode: FC = ({ id }) => { + return ( +
+ +
node {id}
+ + +
+ ); +}; + +export default memo(CustomNode); diff --git a/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx b/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx new file mode 100644 index 00000000..85d4f9e1 --- /dev/null +++ b/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx @@ -0,0 +1,42 @@ +import { memo, FC, useEffect, useCallback } from 'react'; +import { Handle, Position, NodeProps, useHandleConnectionStatus, Connection } from '@xyflow/react'; +import { HandleComponentProps } from '@xyflow/react/dist/esm/components/Handle'; + +function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { + const onConnect = useCallback( + (connections: Connection[]) => { + console.log('onConnect handler, node id:', nodeId, connections); + }, + [nodeId] + ); + const onDisconnect = useCallback( + (connections: Connection[]) => { + console.log('onDisconnect handler, node id:', nodeId, connections); + }, + [nodeId] + ); + const status = useHandleConnectionStatus({ + handleType: handleProps.type, + handleId: handleProps.id, + onConnect, + onDisconnect, + }); + + useEffect(() => { + // console.log('useEffect, node id:', nodeId, handleProps.type, status); + }, [status]); + + return ; +} + +const CustomNode: FC = ({ id }) => { + return ( +
+ +
node {id}
+ +
+ ); +}; + +export default memo(CustomNode); diff --git a/examples/react/src/examples/UseHandleConnectionStatus/index.tsx b/examples/react/src/examples/UseHandleConnectionStatus/index.tsx new file mode 100644 index 00000000..c6aa4a68 --- /dev/null +++ b/examples/react/src/examples/UseHandleConnectionStatus/index.tsx @@ -0,0 +1,118 @@ +import { useCallback } from 'react'; +import { + ReactFlow, + MiniMap, + Controls, + addEdge, + Connection, + useNodesState, + useEdgesState, + Background, +} from '@xyflow/react'; + +import MultiHandleNode from './MultiHandleNode'; +import SingleHandleNode from './SingleHandleNode'; + +const nodeTypes = { + multi: MultiHandleNode, + single: SingleHandleNode, +}; + +const initNodes = [ + { + id: '1', + type: 'single', + data: {}, + position: { x: 0, y: 0 }, + }, + { + id: '2', + type: 'single', + data: {}, + position: { x: 200, y: -100 }, + }, + { + id: '3', + type: 'single', + data: {}, + position: { x: 200, y: 100 }, + }, + + { + id: '4', + type: 'multi', + data: {}, + position: { x: 400, y: 0 }, + }, + { + id: '5', + type: 'multi', + data: {}, + position: { x: 600, y: -100 }, + }, + { + id: '6', + type: 'multi', + data: {}, + position: { x: 600, y: 100 }, + }, +]; + +const initEdges = [ + { + id: 'e1-2', + source: '1', + target: '2', + }, + { + id: 'e1-3', + source: '1', + target: '3', + }, + + { + id: 'e4a-5', + source: '4', + sourceHandle: 'a', + target: '5', + }, + { + id: 'e4b-5', + source: '4', + sourceHandle: 'b', + target: '6', + }, +]; + +const defaultEdgeOptions = { + animated: true, +}; + +const CustomNodeFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState(initNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); + + const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]); + + return ( + + + + + + ); +}; + +export default CustomNodeFlow; diff --git a/packages/react/src/hooks/useHandleConnectionStatus.ts b/packages/react/src/hooks/useHandleConnectionStatus.ts new file mode 100644 index 00000000..062d5d93 --- /dev/null +++ b/packages/react/src/hooks/useHandleConnectionStatus.ts @@ -0,0 +1,85 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { Connection, HandleType } from '@xyflow/system'; + +import { useStore } from './useStore'; +import { useNodeId } from '../contexts/NodeIdContext'; + +type useHandleConnectionStatusParams = { + handleType: HandleType; + nodeId?: string; + handleId?: string | null; + onConnect?: (connections: Connection[]) => void; + onDisconnect?: (connections: Connection[]) => void; +}; + +function connectionsEqual(a: Connection[] | null, b: Connection[] | null) { + if (!a && !b) { + return true; + } + + if (!a || !b) { + return false; + } + + if (a.length !== b.length) { + return false; + } + + return a.every((connA) => + b.find( + (connB) => + connA.source === connB.source && + connA.target === connB.target && + connA.sourceHandle === connB.sourceHandle && + connA.targetHandle === connB.targetHandle + ) + ); +} + +export function useHandleConnectionStatus({ + handleType, + nodeId, + handleId = null, + onConnect, + onDisconnect, +}: useHandleConnectionStatusParams): { + connected: boolean; + connections: Connection[] | null; +} { + const _nodeId = useNodeId(); + const prevConnections = useRef(null); + const currentNodeId = nodeId || _nodeId; + + const connections = useStore( + (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`) || null, + connectionsEqual + ); + + useEffect(() => { + // we don't want to trigger the handlers for the initial render + if (prevConnections.current && prevConnections.current !== connections) { + if (prevConnections.current?.length > (connections?.length ?? 0)) { + const disconnect = prevConnections.current.filter( + (prevConnection) => !connections?.find((connection) => connection.source === prevConnection.source) + ); + onDisconnect?.(disconnect); + } else if (connections?.length) { + const connect = connections.filter( + (connection) => + !prevConnections.current?.find((prevConnection) => prevConnection.source === connection.source) + ); + onConnect?.(connect); + } + } + + prevConnections.current = connections ?? []; + }, [connections, onConnect, onDisconnect]); + + return useMemo( + () => ({ + connected: !!connections, + connections, + }), + [connections] + ); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2a693638..d9013b7c 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -22,6 +22,7 @@ export { useStore, useStoreApi } from './hooks/useStore'; export { default as useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/useOnViewportChange'; export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange'; export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized'; +export { useHandleConnectionStatus } from './hooks/useHandleConnectionStatus'; export { useNodeId } from './contexts/NodeIdContext'; export { applyNodeChanges, applyEdgeChanges, handleParentExpand } from './utils/changes'; diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 0f50f385..0237491a 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -10,7 +10,7 @@ import { } from '@xyflow/system'; import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; -import { updateNodesAndEdgesSelections } from './utils'; +import { updateConnectionLookup, updateNodesAndEdgesSelections } from './utils'; import getInitialState from './initialState'; import type { ReactFlowState, @@ -49,8 +49,12 @@ const createRFStore = ({ set({ nodes: nextNodes }); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = {} } = get(); - set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); + const { defaultEdgeOptions = {}, connectionLookup } = get(); + const nextEdges = edges.map((e) => ({ ...defaultEdgeOptions, ...e })); + + updateConnectionLookup(connectionLookup, nextEdges); + + set({ edges: nextEdges }); }, // when the user works with an uncontrolled flow, // we set a flag `hasDefaultNodes` / `hasDefaultEdges` diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 48e5434f..1c843fe4 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -5,9 +5,11 @@ import { getNodesBounds, getViewportForBounds, Transform, + Connection, } from '@xyflow/system'; import type { Edge, Node, ReactFlowStore } from '../types'; +import { updateConnectionLookup } from './utils'; const getInitialState = ({ nodes = [], @@ -23,6 +25,7 @@ const getInitialState = ({ fitView?: boolean; } = {}): ReactFlowStore => { const nodeLookup = new Map(); + const connectionLookup = updateConnectionLookup(new Map(), edges); const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); let transform: Transform = [0, 0, 1]; @@ -42,6 +45,7 @@ const getInitialState = ({ nodes: nextNodes, nodeLookup, edges: edges, + connectionLookup, onNodesChange: null, onEdgesChange: null, hasDefaultNodes: false, diff --git a/packages/react/src/store/utils.ts b/packages/react/src/store/utils.ts index 86cddde7..ac9e43fa 100644 --- a/packages/react/src/store/utils.ts +++ b/packages/react/src/store/utils.ts @@ -1,5 +1,6 @@ import type { StoreApi } from 'zustand'; import type { Edge, EdgeSelectionChange, Node, NodeSelectionChange, ReactFlowState } from '../types'; +import { Connection } from '@xyflow/system'; export function handleControlledSelectionChange( changes: NodeSelectionChange[] | EdgeSelectionChange[], @@ -42,3 +43,26 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, onEdgesChange?.(changedEdges); } } + +export function updateConnectionLookup(lookup: Map, edges: Edge[]) { + lookup.clear(); + + edges.forEach((edge) => { + const { source, target, sourceHandle = null, targetHandle = null } = edge; + + if (source && target) { + const sourceKey = `${source}-source-${sourceHandle}`; + const targetKey = `${target}-target-${targetHandle}`; + + const prevSource = lookup.get(sourceKey); + const prevTarget = lookup.get(targetKey); + + const connection = { source, target, sourceHandle, targetHandle }; + + lookup.set(sourceKey, prevSource ? [...prevSource, connection] : [connection]); + lookup.set(targetKey, prevTarget ? [...prevTarget, connection] : [connection]); + } + }); + + return lookup; +} diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index c473cbd5..3ee1175d 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -24,6 +24,7 @@ import { type OnMoveEnd, type IsValidConnection, type UpdateConnection, + Connection, } from '@xyflow/system'; import type { @@ -49,6 +50,7 @@ export type ReactFlowStore = { nodes: Node[]; nodeLookup: Map; edges: Edge[]; + connectionLookup: Map; onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; From b6743a3cf3112fa44d6e6ecf225b6ab93b3c4e4e Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 4 Dec 2023 14:23:42 +0100 Subject: [PATCH 17/32] refactor(react/handle-connection-state): cleanup --- .../src/hooks/useHandleConnectionStatus.ts | 54 ++++++------------- packages/react/src/store/index.ts | 1 + packages/react/src/store/utils.ts | 4 +- packages/react/src/types/store.ts | 1 + packages/react/src/utils/general.ts | 30 +++++++++++ 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/packages/react/src/hooks/useHandleConnectionStatus.ts b/packages/react/src/hooks/useHandleConnectionStatus.ts index 062d5d93..42980ee7 100644 --- a/packages/react/src/hooks/useHandleConnectionStatus.ts +++ b/packages/react/src/hooks/useHandleConnectionStatus.ts @@ -3,6 +3,7 @@ import { Connection, HandleType } from '@xyflow/system'; import { useStore } from './useStore'; import { useNodeId } from '../contexts/NodeIdContext'; +import { areConnectionsEqual, isSameConnection } from '../utils/general'; type useHandleConnectionStatusParams = { handleType: HandleType; @@ -12,30 +13,6 @@ type useHandleConnectionStatusParams = { onDisconnect?: (connections: Connection[]) => void; }; -function connectionsEqual(a: Connection[] | null, b: Connection[] | null) { - if (!a && !b) { - return true; - } - - if (!a || !b) { - return false; - } - - if (a.length !== b.length) { - return false; - } - - return a.every((connA) => - b.find( - (connB) => - connA.source === connB.source && - connA.target === connB.target && - connA.sourceHandle === connB.sourceHandle && - connA.targetHandle === connB.targetHandle - ) - ); -} - export function useHandleConnectionStatus({ handleType, nodeId, @@ -52,23 +29,26 @@ export function useHandleConnectionStatus({ const connections = useStore( (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`) || null, - connectionsEqual + areConnectionsEqual ); useEffect(() => { - // we don't want to trigger the handlers for the initial render + // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts if (prevConnections.current && prevConnections.current !== connections) { - if (prevConnections.current?.length > (connections?.length ?? 0)) { - const disconnect = prevConnections.current.filter( - (prevConnection) => !connections?.find((connection) => connection.source === prevConnection.source) - ); - onDisconnect?.(disconnect); - } else if (connections?.length) { - const connect = connections.filter( - (connection) => - !prevConnections.current?.find((prevConnection) => prevConnection.source === connection.source) - ); - onConnect?.(connect); + const disconnectedConnections = prevConnections.current.filter( + (prevConnection) => !connections?.find((connection) => isSameConnection(connection, prevConnection)) + ); + + const newConnections = connections?.filter( + (connection) => !prevConnections.current?.find((prevConnection) => isSameConnection(prevConnection, connection)) + ); + + if (disconnectedConnections.length) { + onDisconnect?.(disconnectedConnections); + } + + if (newConnections?.length) { + onConnect?.(newConnections); } } diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 0237491a..0b308752 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -330,6 +330,7 @@ const createRFStore = ({ set(currentConnection); }, + reset: () => { // @todo: what should we do about this? Do we still need it? // if you are on a SPA with multiple flows, we want to make sure that the store gets resetted diff --git a/packages/react/src/store/utils.ts b/packages/react/src/store/utils.ts index ac9e43fa..2df777fd 100644 --- a/packages/react/src/store/utils.ts +++ b/packages/react/src/store/utils.ts @@ -47,9 +47,7 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, export function updateConnectionLookup(lookup: Map, edges: Edge[]) { lookup.clear(); - edges.forEach((edge) => { - const { source, target, sourceHandle = null, targetHandle = null } = edge; - + edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => { if (source && target) { const sourceKey = `${source}-source-${sourceHandle}`; const targetKey = `${target}-target-${targetHandle}`; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 3ee1175d..d14a1640 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -51,6 +51,7 @@ export type ReactFlowStore = { nodeLookup: Map; edges: Edge[]; connectionLookup: Map; + onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index e13247c1..018cd358 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -6,6 +6,7 @@ import { getIncomersBase, updateEdgeBase, getConnectedEdgesBase, + Connection, } from '@xyflow/system'; import type { Edge, Node } from '../types'; @@ -17,3 +18,32 @@ export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; export const updateEdge = updateEdgeBase; export const getConnectedEdges = getConnectedEdgesBase; + +export function isSameConnection(a: Connection, b: Connection) { + return ( + a.source === b.source && + a.target === b.target && + a.sourceHandle === b.sourceHandle && + a.targetHandle === b.targetHandle + ); +} + +export function areConnectionsEqual(a: Connection[] | null | undefined, b: Connection[] | null | undefined) { + if (!a && !b) { + return true; + } + + if (!a || !b) { + return false; + } + + if (a.length !== b.length) { + return false; + } + + if (!a.length && !b.length) { + return true; + } + + return !a.some((connA) => !b.find((connB) => isSameConnection(connA, connB))); +} From a58f9156971cfe1105eb69be86c7d16e83a28f86 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 6 Dec 2023 17:49:34 +0100 Subject: [PATCH 18/32] refactor(connectionlookup): use maps instead of arrays --- .../SingleHandleNode.tsx | 2 +- .../src/hooks/useHandleConnectionStatus.ts | 90 ++++++++++++++----- packages/react/src/store/initialState.ts | 2 +- packages/react/src/store/utils.ts | 11 ++- packages/react/src/types/store.ts | 2 +- packages/react/src/utils/general.ts | 30 ------- 6 files changed, 76 insertions(+), 61 deletions(-) diff --git a/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx b/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx index 85d4f9e1..ebbe584c 100644 --- a/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx +++ b/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx @@ -23,7 +23,7 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI }); useEffect(() => { - // console.log('useEffect, node id:', nodeId, handleProps.type, status); + console.log('useEffect, node id:', nodeId, handleProps.type, status); }, [status]); return ; diff --git a/packages/react/src/hooks/useHandleConnectionStatus.ts b/packages/react/src/hooks/useHandleConnectionStatus.ts index 42980ee7..470cc077 100644 --- a/packages/react/src/hooks/useHandleConnectionStatus.ts +++ b/packages/react/src/hooks/useHandleConnectionStatus.ts @@ -3,7 +3,6 @@ import { Connection, HandleType } from '@xyflow/system'; import { useStore } from './useStore'; import { useNodeId } from '../contexts/NodeIdContext'; -import { areConnectionsEqual, isSameConnection } from '../utils/general'; type useHandleConnectionStatusParams = { handleType: HandleType; @@ -13,10 +12,69 @@ type useHandleConnectionStatusParams = { onDisconnect?: (connections: Connection[]) => void; }; +function areConnectionMapsEqual(a?: Map, b?: Map) { + if (!a && !b) { + return true; + } + + if (!a || !b || a.size !== b.size) { + return false; + } + + if (!a.size && !b.size) { + return true; + } + + for (const key of a.keys()) { + if (!b.has(key)) { + return false; + } + } + + return true; +} + +/** + * We call the callback for all connections in a that are not in b + * @internal + */ +function handleConnectionChange( + a: Map, + b: Map, + cb?: (diff: Connection[]) => void +) { + if (!cb) { + return; + } + + const diff: Connection[] = []; + + a.forEach((connection, key) => { + if (!b?.has(key)) { + diff.push(connection); + } + }); + + if (diff.length) { + cb(diff); + } +} + +/** + * Hook to check if a is connected to another and get the connections. + * + * @public + * @param param.handleType - 'source' or 'target' + * @param param.handleId - the handle id (this is only needed if the node has multiple handles of the same type) + * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used + * @param param.onConnect - gets called when a connection is established + * @param param.onDisconnect - gets called when a connection is removed + * @returns a `connected` boolean and a connections array + */ export function useHandleConnectionStatus({ handleType, - nodeId, handleId = null, + nodeId, onConnect, onDisconnect, }: useHandleConnectionStatusParams): { @@ -24,41 +82,29 @@ export function useHandleConnectionStatus({ connections: Connection[] | null; } { const _nodeId = useNodeId(); - const prevConnections = useRef(null); + const prevConnections = useRef | null>(null); const currentNodeId = nodeId || _nodeId; const connections = useStore( - (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`) || null, - areConnectionsEqual + (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`), + areConnectionMapsEqual ); useEffect(() => { // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts if (prevConnections.current && prevConnections.current !== connections) { - const disconnectedConnections = prevConnections.current.filter( - (prevConnection) => !connections?.find((connection) => isSameConnection(connection, prevConnection)) - ); - - const newConnections = connections?.filter( - (connection) => !prevConnections.current?.find((prevConnection) => isSameConnection(prevConnection, connection)) - ); - - if (disconnectedConnections.length) { - onDisconnect?.(disconnectedConnections); - } - - if (newConnections?.length) { - onConnect?.(newConnections); - } + const _connections = connections ?? new Map(); + handleConnectionChange(prevConnections.current, _connections, onDisconnect); + handleConnectionChange(_connections, prevConnections.current, onConnect); } - prevConnections.current = connections ?? []; + prevConnections.current = connections ?? new Map(); }, [connections, onConnect, onDisconnect]); return useMemo( () => ({ connected: !!connections, - connections, + connections: Array.from(connections?.values() ?? []), }), [connections] ); diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 1c843fe4..f167a9aa 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -25,7 +25,7 @@ const getInitialState = ({ fitView?: boolean; } = {}): ReactFlowStore => { const nodeLookup = new Map(); - const connectionLookup = updateConnectionLookup(new Map(), edges); + const connectionLookup = updateConnectionLookup(new Map>(), edges); const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); let transform: Transform = [0, 0, 1]; diff --git a/packages/react/src/store/utils.ts b/packages/react/src/store/utils.ts index 2df777fd..7ad5ad43 100644 --- a/packages/react/src/store/utils.ts +++ b/packages/react/src/store/utils.ts @@ -44,7 +44,7 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, } } -export function updateConnectionLookup(lookup: Map, edges: Edge[]) { +export function updateConnectionLookup(lookup: Map>, edges: Edge[]) { lookup.clear(); edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => { @@ -52,13 +52,12 @@ export function updateConnectionLookup(lookup: Map, edges: const sourceKey = `${source}-source-${sourceHandle}`; const targetKey = `${target}-target-${targetHandle}`; - const prevSource = lookup.get(sourceKey); - const prevTarget = lookup.get(targetKey); - + const prevSource = lookup.get(sourceKey) || new Map(); + const prevTarget = lookup.get(targetKey) || new Map(); const connection = { source, target, sourceHandle, targetHandle }; - lookup.set(sourceKey, prevSource ? [...prevSource, connection] : [connection]); - lookup.set(targetKey, prevTarget ? [...prevTarget, connection] : [connection]); + lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection)); + lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection)); } }); diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index d14a1640..5aebb99d 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -50,7 +50,7 @@ export type ReactFlowStore = { nodes: Node[]; nodeLookup: Map; edges: Edge[]; - connectionLookup: Map; + connectionLookup: Map>; onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index 018cd358..e13247c1 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -6,7 +6,6 @@ import { getIncomersBase, updateEdgeBase, getConnectedEdgesBase, - Connection, } from '@xyflow/system'; import type { Edge, Node } from '../types'; @@ -18,32 +17,3 @@ export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; export const updateEdge = updateEdgeBase; export const getConnectedEdges = getConnectedEdgesBase; - -export function isSameConnection(a: Connection, b: Connection) { - return ( - a.source === b.source && - a.target === b.target && - a.sourceHandle === b.sourceHandle && - a.targetHandle === b.targetHandle - ); -} - -export function areConnectionsEqual(a: Connection[] | null | undefined, b: Connection[] | null | undefined) { - if (!a && !b) { - return true; - } - - if (!a || !b) { - return false; - } - - if (a.length !== b.length) { - return false; - } - - if (!a.length && !b.length) { - return true; - } - - return !a.some((connA) => !b.find((connB) => isSameConnection(connA, connB))); -} From 959935dfb52811f7f65af4331a6ddbfcf7d6c8f7 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 7 Dec 2023 13:23:25 +0100 Subject: [PATCH 19/32] feat(hooks): add useNodesData, useUpdateNodeData, simplify useHandleConnections --- examples/react/src/App/routes.ts | 18 ++-- .../MultiHandleNode.tsx | 9 +- .../SingleHandleNode.tsx | 9 +- .../index.tsx | 0 .../src/examples/UseNodesData/ResultNode.tsx | 25 +++++ .../src/examples/UseNodesData/TextNode.tsx | 20 ++++ .../react/src/examples/UseNodesData/index.tsx | 83 ++++++++++++++++ ...ctionStatus.ts => useHandleConnections.ts} | 97 +++++++++---------- packages/react/src/hooks/useNodesData.ts | 33 +++++++ packages/react/src/hooks/useUpdateNodeData.ts | 13 +++ packages/react/src/index.ts | 6 +- packages/system/src/types/general.ts | 4 +- 12 files changed, 246 insertions(+), 71 deletions(-) rename examples/react/src/examples/{UseHandleConnectionStatus => UseHandleConnections}/MultiHandleNode.tsx (82%) rename examples/react/src/examples/{UseHandleConnectionStatus => UseHandleConnections}/SingleHandleNode.tsx (81%) rename examples/react/src/examples/{UseHandleConnectionStatus => UseHandleConnections}/index.tsx (100%) create mode 100644 examples/react/src/examples/UseNodesData/ResultNode.tsx create mode 100644 examples/react/src/examples/UseNodesData/TextNode.tsx create mode 100644 examples/react/src/examples/UseNodesData/index.tsx rename packages/react/src/hooks/{useHandleConnectionStatus.ts => useHandleConnections.ts} (86%) create mode 100644 packages/react/src/hooks/useNodesData.ts create mode 100644 packages/react/src/hooks/useUpdateNodeData.ts diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 18307c79..4bab00f5 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -44,8 +44,9 @@ import CancelConnection from '../examples/CancelConnection'; import InteractiveMinimap from '../examples/InteractiveMinimap'; import UseOnSelectionChange from '../examples/UseOnSelectionChange'; import NodeToolbar from '../examples/NodeToolbar'; -import useNodesInitialized from '../examples/UseNodesInit'; -import useHandleConnectionStatus from '../examples/UseHandleConnectionStatus'; +import UseNodesInitialized from '../examples/UseNodesInit'; +import UseNodesData from '../examples/UseNodesData'; +import UseHandleConnections from '../examples/UseHandleConnections'; export interface IRoute { name: string; @@ -262,7 +263,7 @@ const routes: IRoute[] = [ { name: 'useNodesInitialized', path: 'use-nodes-initialized', - component: useNodesInitialized, + component: UseNodesInitialized, }, { name: 'useOnSelectionChange', @@ -275,9 +276,14 @@ const routes: IRoute[] = [ component: UseReactFlow, }, { - name: 'useHandleConnectionStatus', - path: 'usehandleconnectionstatus', - component: useHandleConnectionStatus, + name: 'useHandleConnections', + path: 'usehandleconnections', + component: UseHandleConnections, + }, + { + name: 'useNodesData', + path: 'usenodesdata', + component: UseNodesData, }, { name: 'useUpdateNodeInternals', diff --git a/examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx similarity index 82% rename from examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx rename to examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx index f8a31143..e04f3448 100644 --- a/examples/react/src/examples/UseHandleConnectionStatus/MultiHandleNode.tsx +++ b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx @@ -1,6 +1,5 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnectionStatus, Connection } from '@xyflow/react'; -import { HandleComponentProps } from '@xyflow/react/dist/esm/components/Handle'; +import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react'; function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { const onConnect = useCallback( @@ -12,7 +11,7 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI (connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections), [nodeId] ); - const status = useHandleConnectionStatus({ + const connections = useHandleConnections({ handleType: handleProps.type, handleId: handleProps.id, onConnect, @@ -20,8 +19,8 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI }); useEffect(() => { - console.log('useEffect, node id:', nodeId, handleProps.type, status); - }, [status]); + console.log('useEffect, node id:', nodeId, handleProps.type, connections); + }, [connections]); return ; } diff --git a/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx similarity index 81% rename from examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx rename to examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx index ebbe584c..444499fb 100644 --- a/examples/react/src/examples/UseHandleConnectionStatus/SingleHandleNode.tsx +++ b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx @@ -1,6 +1,5 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnectionStatus, Connection } from '@xyflow/react'; -import { HandleComponentProps } from '@xyflow/react/dist/esm/components/Handle'; +import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react'; function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { const onConnect = useCallback( @@ -15,7 +14,7 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI }, [nodeId] ); - const status = useHandleConnectionStatus({ + const connections = useHandleConnections({ handleType: handleProps.type, handleId: handleProps.id, onConnect, @@ -23,8 +22,8 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI }); useEffect(() => { - console.log('useEffect, node id:', nodeId, handleProps.type, status); - }, [status]); + console.log('useEffect, node id:', nodeId, handleProps.type, connections); + }, [connections]); return ; } diff --git a/examples/react/src/examples/UseHandleConnectionStatus/index.tsx b/examples/react/src/examples/UseHandleConnections/index.tsx similarity index 100% rename from examples/react/src/examples/UseHandleConnectionStatus/index.tsx rename to examples/react/src/examples/UseHandleConnections/index.tsx diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx new file mode 100644 index 00000000..09603eac --- /dev/null +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -0,0 +1,25 @@ +import { memo, useEffect } from 'react'; +import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; + +function ResultNode() { + const connections = useHandleConnections({ + handleType: 'target', + }); + const nodesData = useNodesData<{ text: string }>(connections.map((connection) => connection.source)); + + useEffect(() => { + console.log('incoming data changed', nodesData); + }, [nodesData]); + + return ( +
+ +
+ incoming texts:{' '} + {nodesData?.filter((nodeData) => nodeData.text).map(({ text }, i) =>
{text}
) || 'none'} +
+
+ ); +} + +export default memo(ResultNode); diff --git a/examples/react/src/examples/UseNodesData/TextNode.tsx b/examples/react/src/examples/UseNodesData/TextNode.tsx new file mode 100644 index 00000000..a7388cb0 --- /dev/null +++ b/examples/react/src/examples/UseNodesData/TextNode.tsx @@ -0,0 +1,20 @@ +import { memo, ChangeEventHandler } from 'react'; +import { Position, NodeProps, useUpdateNodeData, Handle } from '@xyflow/react'; + +function TextNode({ id, data }: NodeProps) { + const updateNodeData = useUpdateNodeData(); + + const onChange: ChangeEventHandler = (evt) => updateNodeData(id, { text: evt.target.value }); + + return ( +
+
node {id}
+
+ +
+ +
+ ); +} + +export default memo(TextNode); diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx new file mode 100644 index 00000000..67e68a2f --- /dev/null +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -0,0 +1,83 @@ +import { useCallback } from 'react'; +import { + ReactFlow, + Controls, + addEdge, + Connection, + useNodesState, + useEdgesState, + Background, + Node, + Edge, +} from '@xyflow/react'; + +import TextNode from './TextNode'; +import ResultNode from './ResultNode'; + +const nodeTypes = { + text: TextNode, + result: ResultNode, +}; + +const initNodes: Node[] = [ + { + id: '1', + type: 'text', + data: { + text: 'hello', + }, + position: { x: 0, y: 0 }, + }, + { + id: '2', + type: 'text', + data: { + text: 'world', + }, + position: { x: 0, y: 100 }, + }, + + { + id: '3', + type: 'result', + data: {}, + position: { x: 300, y: 50 }, + }, +]; + +const initEdges: Edge[] = [ + { + id: 'e1-3', + source: '1', + target: '3', + }, + { + id: 'e2-3', + source: '2', + target: '3', + }, +]; + +const CustomNodeFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState(initNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); + + const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]); + + return ( + + + + + ); +}; + +export default CustomNodeFlow; diff --git a/packages/react/src/hooks/useHandleConnectionStatus.ts b/packages/react/src/hooks/useHandleConnections.ts similarity index 86% rename from packages/react/src/hooks/useHandleConnectionStatus.ts rename to packages/react/src/hooks/useHandleConnections.ts index 470cc077..f692ba37 100644 --- a/packages/react/src/hooks/useHandleConnectionStatus.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -4,7 +4,7 @@ import { Connection, HandleType } from '@xyflow/system'; import { useStore } from './useStore'; import { useNodeId } from '../contexts/NodeIdContext'; -type useHandleConnectionStatusParams = { +type useHandleConnectionsParams = { handleType: HandleType; nodeId?: string; handleId?: string | null; @@ -12,6 +12,50 @@ type useHandleConnectionStatusParams = { onDisconnect?: (connections: Connection[]) => void; }; +/** + * Hook to check if a is connected to another and get the connections. + * + * @public + * @param param.handleType - 'source' or 'target' + * @param param.handleId - the handle id (this is only needed if the node has multiple handles of the same type) + * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used + * @param param.onConnect - gets called when a connection is established + * @param param.onDisconnect - gets called when a connection is removed + * @returns an array with connections + */ +export function useHandleConnections({ + handleType, + handleId = null, + nodeId, + onConnect, + onDisconnect, +}: useHandleConnectionsParams): Connection[] { + const _nodeId = useNodeId(); + const prevConnections = useRef | null>(null); + const currentNodeId = nodeId || _nodeId; + + const connections = useStore( + (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`), + areConnectionMapsEqual + ); + + useEffect(() => { + // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts + if (prevConnections.current && prevConnections.current !== connections) { + const _connections = connections ?? new Map(); + handleConnectionChange(prevConnections.current, _connections, onDisconnect); + handleConnectionChange(_connections, prevConnections.current, onConnect); + } + + prevConnections.current = connections ?? new Map(); + }, [connections, onConnect, onDisconnect]); + + return useMemo(() => Array.from(connections?.values() ?? []), [connections]); +} + +/** + * @internal + */ function areConnectionMapsEqual(a?: Map, b?: Map) { if (!a && !b) { return true; @@ -36,6 +80,7 @@ function areConnectionMapsEqual(a?: Map, b?: Map is connected to another and get the connections. - * - * @public - * @param param.handleType - 'source' or 'target' - * @param param.handleId - the handle id (this is only needed if the node has multiple handles of the same type) - * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used - * @param param.onConnect - gets called when a connection is established - * @param param.onDisconnect - gets called when a connection is removed - * @returns a `connected` boolean and a connections array - */ -export function useHandleConnectionStatus({ - handleType, - handleId = null, - nodeId, - onConnect, - onDisconnect, -}: useHandleConnectionStatusParams): { - connected: boolean; - connections: Connection[] | null; -} { - const _nodeId = useNodeId(); - const prevConnections = useRef | null>(null); - const currentNodeId = nodeId || _nodeId; - - const connections = useStore( - (state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`), - areConnectionMapsEqual - ); - - useEffect(() => { - // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts - if (prevConnections.current && prevConnections.current !== connections) { - const _connections = connections ?? new Map(); - handleConnectionChange(prevConnections.current, _connections, onDisconnect); - handleConnectionChange(_connections, prevConnections.current, onConnect); - } - - prevConnections.current = connections ?? new Map(); - }, [connections, onConnect, onDisconnect]); - - return useMemo( - () => ({ - connected: !!connections, - connections: Array.from(connections?.values() ?? []), - }), - [connections] - ); -} diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts new file mode 100644 index 00000000..69927642 --- /dev/null +++ b/packages/react/src/hooks/useNodesData.ts @@ -0,0 +1,33 @@ +import { useCallback } from 'react'; +import { shallow } from 'zustand/shallow'; + +import { useStore } from '../hooks/useStore'; + +export function useNodesData(nodeId: string): NodeData | null; +export function useNodesData(nodeIds: string[]): NodeData[]; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function useNodesData(nodeIds: any): any { + const nodesData = useStore( + useCallback( + (s) => { + if (!Array.isArray(nodeIds)) { + return s.nodeLookup.get(nodeIds)?.data || null; + } + + return nodeIds.reduce((res, id) => { + const node = s.nodeLookup.get(id); + + if (node) { + res.push(node.data); + } + + return res; + }, []); + }, + [nodeIds] + ), + shallow + ); + + return nodesData; +} diff --git a/packages/react/src/hooks/useUpdateNodeData.ts b/packages/react/src/hooks/useUpdateNodeData.ts new file mode 100644 index 00000000..10cd71e2 --- /dev/null +++ b/packages/react/src/hooks/useUpdateNodeData.ts @@ -0,0 +1,13 @@ +import { useCallback } from 'react'; + +import useReactFlow from './useReactFlow'; + +export function useUpdateNodeData() { + const { setNodes } = useReactFlow(); + + const updateNodeData = useCallback((id: string, data: unknown) => { + setNodes((prevNodes) => prevNodes.map((node) => (node.id === id ? { ...node, data } : node))); + }, []); + + return updateNodeData; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index d9013b7c..63b70c98 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,5 +1,5 @@ export { default as ReactFlow } from './container/ReactFlow'; -export { default as Handle } from './components/Handle'; +export { default as Handle, type HandleComponentProps } from './components/Handle'; export { default as EdgeText } from './components/Edges/EdgeText'; export { StraightEdge } from './components/Edges/StraightEdge'; export { StepEdge } from './components/Edges/StepEdge'; @@ -22,7 +22,9 @@ export { useStore, useStoreApi } from './hooks/useStore'; export { default as useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/useOnViewportChange'; export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange'; export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized'; -export { useHandleConnectionStatus } from './hooks/useHandleConnectionStatus'; +export { useHandleConnections } from './hooks/useHandleConnections'; +export { useUpdateNodeData } from './hooks/useUpdateNodeData'; +export { useNodesData } from './hooks/useNodesData'; export { useNodeId } from './contexts/NodeIdContext'; export { applyNodeChanges, applyEdgeChanges, handleParentExpand } from './utils/changes'; diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 22ad889e..e601ee08 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -22,8 +22,8 @@ export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => vo export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void; export type Connection = { - source: string | null; - target: string | null; + source: string; + target: string; sourceHandle: string | null; targetHandle: string | null; }; From 5d8c1bbff991e0d3acc6fd23d95008947c10fe1c Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 7 Dec 2023 14:09:56 +0100 Subject: [PATCH 20/32] chore(useNodesData): example --- .../examples/UseNodesData/UppercaseNode.tsx | 24 +++++++++++++++++++ .../react/src/examples/UseNodesData/index.tsx | 18 ++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 examples/react/src/examples/UseNodesData/UppercaseNode.tsx diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx new file mode 100644 index 00000000..7f0b964f --- /dev/null +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -0,0 +1,24 @@ +import { memo, useEffect } from 'react'; +import { Position, NodeProps, useUpdateNodeData, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; + +function UppercaseNode({ id }: NodeProps) { + const connections = useHandleConnections({ + handleType: 'target', + }); + const nodeData = useNodesData<{ text: string }>(connections[0]?.source); + const updateNodeData = useUpdateNodeData(); + + useEffect(() => { + updateNodeData(id, { text: nodeData?.text.toUpperCase() }); + }, [nodeData]); + + return ( +
+ +
uppercase transform
+ +
+ ); +} + +export default memo(UppercaseNode); diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index 67e68a2f..040ca35a 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -13,10 +13,12 @@ import { import TextNode from './TextNode'; import ResultNode from './ResultNode'; +import UppercaseNode from './UppercaseNode'; const nodeTypes = { text: TextNode, result: ResultNode, + uppercase: UppercaseNode, }; const initNodes: Node[] = [ @@ -26,8 +28,15 @@ const initNodes: Node[] = [ data: { text: 'hello', }, - position: { x: 0, y: 0 }, + position: { x: -100, y: -50 }, }, + { + id: '1a', + type: 'uppercase', + data: {}, + position: { x: 100, y: 0 }, + }, + { id: '2', type: 'text', @@ -47,8 +56,13 @@ const initNodes: Node[] = [ const initEdges: Edge[] = [ { - id: 'e1-3', + id: 'e1-1a', source: '1', + target: '1a', + }, + { + id: 'e1a-3', + source: '1a', target: '3', }, { From d4d773d9c62f49706bccd1d5435e75dd912d3643 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 11 Dec 2023 18:31:37 +0100 Subject: [PATCH 21/32] feat(svelte): add useHandleConnections, useNodesData and useUpdateNodeData --- .../src/examples/UseNodesData/ResultNode.tsx | 5 +- .../examples/UseNodesData/UppercaseNode.tsx | 6 +- .../react/src/examples/UseNodesData/index.tsx | 7 +- .../src/components/Header/Header.svelte | 2 + .../examples/customnode/CustomNode.svelte | 2 +- .../examples/handle-connect/+page.svelte | 94 +++++++++++++++ .../handle-connect/MultiHandleNode.svelte | 110 ++++++++++++++++++ .../handle-connect/SingleHandleNode.svelte | 77 ++++++++++++ .../routes/examples/usenodesdata/+page.svelte | 80 +++++++++++++ .../examples/usenodesdata/ResultNode.svelte | 38 ++++++ .../examples/usenodesdata/TextNode.svelte | 29 +++++ .../usenodesdata/UppercaseNode.svelte | 40 +++++++ .../react/src/components/Handle/index.tsx | 2 +- .../react/src/hooks/useHandleConnections.ts | 54 +-------- packages/react/src/hooks/useNodesData.ts | 23 ++-- packages/react/src/store/index.ts | 3 +- packages/react/src/store/initialState.ts | 7 +- packages/react/src/store/utils.ts | 21 ---- .../src/lib/components/Handle/Handle.svelte | 30 ++++- .../components/KeyHandler/KeyHandler.svelte | 2 +- .../src/lib/hooks/useHandleConnections.ts | 30 +++++ packages/svelte/src/lib/hooks/useNodesData.ts | 64 ++++++++++ .../svelte/src/lib/hooks/useUpdateNodeData.ts | 22 ++++ packages/svelte/src/lib/index.ts | 3 + .../svelte/src/lib/store/initial-store.ts | 10 +- packages/svelte/src/lib/store/utils.ts | 12 +- packages/svelte/src/lib/types/general.ts | 2 + packages/system/src/types/general.ts | 2 + packages/system/src/utils/connections.ts | 53 +++++++++ packages/system/src/utils/index.ts | 1 + packages/system/src/utils/store.ts | 26 ++++- packages/system/src/xyhandle/XYHandle.ts | 12 +- 32 files changed, 761 insertions(+), 108 deletions(-) create mode 100644 examples/svelte/src/routes/examples/handle-connect/+page.svelte create mode 100644 examples/svelte/src/routes/examples/handle-connect/MultiHandleNode.svelte create mode 100644 examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte create mode 100644 examples/svelte/src/routes/examples/usenodesdata/+page.svelte create mode 100644 examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte create mode 100644 examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte create mode 100644 examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte create mode 100644 packages/svelte/src/lib/hooks/useHandleConnections.ts create mode 100644 packages/svelte/src/lib/hooks/useNodesData.ts create mode 100644 packages/svelte/src/lib/hooks/useUpdateNodeData.ts create mode 100644 packages/system/src/utils/connections.ts diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx index 09603eac..324da1fe 100644 --- a/examples/react/src/examples/UseNodesData/ResultNode.tsx +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -5,7 +5,7 @@ function ResultNode() { const connections = useHandleConnections({ handleType: 'target', }); - const nodesData = useNodesData<{ text: string }>(connections.map((connection) => connection.source)); + const nodesData = useNodesData(connections.map((connection) => connection.source)); useEffect(() => { console.log('incoming data changed', nodesData); @@ -16,7 +16,8 @@ function ResultNode() {
incoming texts:{' '} - {nodesData?.filter((nodeData) => nodeData.text).map(({ text }, i) =>
{text}
) || 'none'} + {nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) =>
{text}
) || + 'none'}
); diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 7f0b964f..06cf84a8 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -5,7 +5,7 @@ function UppercaseNode({ id }: NodeProps) { const connections = useHandleConnections({ handleType: 'target', }); - const nodeData = useNodesData<{ text: string }>(connections[0]?.source); + const nodeData = useNodesData(connections[0]?.source); const updateNodeData = useUpdateNodeData(); useEffect(() => { @@ -14,9 +14,9 @@ function UppercaseNode({ id }: NodeProps) { return (
- -
uppercase transform
+
uppercase transform
+
); } diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index 040ca35a..7bb1a94e 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -15,13 +15,18 @@ import TextNode from './TextNode'; import ResultNode from './ResultNode'; import UppercaseNode from './UppercaseNode'; +export type TextNode = Node<{ text: string }, 'text'>; +export type ResultNode = Node<{}, 'result'>; +export type UppercaseNode = Node<{}, 'uppercase'>; +export type MyNode = Node<{ text: string }, 'text'> | Node<{}, 'result'> | Node<{}, 'uppercase'>; + const nodeTypes = { text: TextNode, result: ResultNode, uppercase: UppercaseNode, }; -const initNodes: Node[] = [ +const initNodes: MyNode[] = [ { id: '1', type: 'text', diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte index ad34d289..9d67f049 100644 --- a/examples/svelte/src/components/Header/Header.svelte +++ b/examples/svelte/src/components/Header/Header.svelte @@ -11,6 +11,7 @@ 'drag-n-drop', 'edges', 'figma', + 'handle-connect', 'interaction', 'intersections', 'node-toolbar', @@ -18,6 +19,7 @@ 'stress', 'subflows', 'two-way-viewport', + 'usenodesdata', 'usesvelteflow', 'useupdatenodeinternals', 'validation' diff --git a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte index 51ba33ea..25d37d32 100644 --- a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte +++ b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte @@ -1,6 +1,6 @@ + + + + + + diff --git a/examples/svelte/src/routes/examples/handle-connect/MultiHandleNode.svelte b/examples/svelte/src/routes/examples/handle-connect/MultiHandleNode.svelte new file mode 100644 index 00000000..ebc85c74 --- /dev/null +++ b/examples/svelte/src/routes/examples/handle-connect/MultiHandleNode.svelte @@ -0,0 +1,110 @@ + + +
+ +
node {id}
+ onConnectSource('a', connections)} + ondisconnect={(connections) => onDisconnectSource('a', connections)} + class="source-a" + /> + onConnectSource('b', connections)} + ondisconnect={(connections) => onDisconnectSource('b', connections)} + class="source-b" + /> +
+ + diff --git a/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte b/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte new file mode 100644 index 00000000..7c1e026d --- /dev/null +++ b/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte @@ -0,0 +1,77 @@ + + +
+ +
node {id}
+ +
+ + diff --git a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte new file mode 100644 index 00000000..7b2d2204 --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte @@ -0,0 +1,80 @@ + + + + + + + diff --git a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte new file mode 100644 index 00000000..c4150f42 --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte @@ -0,0 +1,38 @@ + + +
+ +
incoming texts:
+ + {#each $nodeData as data} +
{data.text}
+ {/each} +
+ + diff --git a/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte new file mode 100644 index 00000000..142a14bb --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte @@ -0,0 +1,29 @@ + + +
+
node {id}
+
+ updateNodeData(id, { text: evt.currentTarget.value })} + /> +
+ +
+ + diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte new file mode 100644 index 00000000..3e311f1f --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -0,0 +1,40 @@ + + +
+ +
uppercase transform
+ +
+ + diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 29231b71..a2c0101f 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -171,7 +171,7 @@ const Handle = forwardRef( lib, }); - if (isValid) { + if (isValid && connection) { onConnectExtended(connection); } diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index f692ba37..58987aec 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef } from 'react'; -import { Connection, HandleType } from '@xyflow/system'; +import { Connection, HandleType, areConnectionMapsEqual, handleConnectionChange } from '@xyflow/system'; import { useStore } from './useStore'; import { useNodeId } from '../contexts/NodeIdContext'; @@ -52,55 +52,3 @@ export function useHandleConnections({ return useMemo(() => Array.from(connections?.values() ?? []), [connections]); } - -/** - * @internal - */ -function areConnectionMapsEqual(a?: Map, b?: Map) { - if (!a && !b) { - return true; - } - - if (!a || !b || a.size !== b.size) { - return false; - } - - if (!a.size && !b.size) { - return true; - } - - for (const key of a.keys()) { - if (!b.has(key)) { - return false; - } - } - - return true; -} - -/** - * We call the callback for all connections in a that are not in b - * - * @internal - */ -function handleConnectionChange( - a: Map, - b: Map, - cb?: (diff: Connection[]) => void -) { - if (!cb) { - return; - } - - const diff: Connection[] = []; - - a.forEach((connection, key) => { - if (!b?.has(key)) { - diff.push(connection); - } - }); - - if (diff.length) { - cb(diff); - } -} diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 69927642..4c242ad3 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -2,9 +2,14 @@ import { useCallback } from 'react'; import { shallow } from 'zustand/shallow'; import { useStore } from '../hooks/useStore'; +import type { Node } from '../types'; -export function useNodesData(nodeId: string): NodeData | null; -export function useNodesData(nodeIds: string[]): NodeData[]; +export function useNodesData(nodeId: string): NodeType['data'] | null; +export function useNodesData(nodeIds: string[]): NodeType['data'][]; +export function useNodesData( + nodeIds: string[], + guard: (node: Node) => node is NodeType +): NodeType['data'][]; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const nodesData = useStore( @@ -14,15 +19,17 @@ export function useNodesData(nodeIds: any): any { return s.nodeLookup.get(nodeIds)?.data || null; } - return nodeIds.reduce((res, id) => { - const node = s.nodeLookup.get(id); + const data = []; - if (node) { - res.push(node.data); + for (const nodeId of nodeIds) { + const nodeData = s.nodeLookup.get(nodeId)?.data; + + if (nodeData) { + data.push(nodeData); } + } - return res; - }, []); + return data; }, [nodeIds] ), diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 0b308752..c5c21d7a 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -7,10 +7,11 @@ import { panBy as panBySystem, Dimensions, updateNodeDimensions as updateNodeDimensionsSystem, + updateConnectionLookup, } from '@xyflow/system'; import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; -import { updateConnectionLookup, updateNodesAndEdgesSelections } from './utils'; +import { updateNodesAndEdgesSelections } from './utils'; import getInitialState from './initialState'; import type { ReactFlowState, diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index f167a9aa..8d07617a 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -5,11 +5,10 @@ import { getNodesBounds, getViewportForBounds, Transform, - Connection, + updateConnectionLookup, } from '@xyflow/system'; import type { Edge, Node, ReactFlowStore } from '../types'; -import { updateConnectionLookup } from './utils'; const getInitialState = ({ nodes = [], @@ -24,8 +23,8 @@ const getInitialState = ({ height?: number; fitView?: boolean; } = {}): ReactFlowStore => { - const nodeLookup = new Map(); - const connectionLookup = updateConnectionLookup(new Map>(), edges); + const nodeLookup = new Map(); + const connectionLookup = updateConnectionLookup(new Map(), edges); const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); let transform: Transform = [0, 0, 1]; diff --git a/packages/react/src/store/utils.ts b/packages/react/src/store/utils.ts index 7ad5ad43..86cddde7 100644 --- a/packages/react/src/store/utils.ts +++ b/packages/react/src/store/utils.ts @@ -1,6 +1,5 @@ import type { StoreApi } from 'zustand'; import type { Edge, EdgeSelectionChange, Node, NodeSelectionChange, ReactFlowState } from '../types'; -import { Connection } from '@xyflow/system'; export function handleControlledSelectionChange( changes: NodeSelectionChange[] | EdgeSelectionChange[], @@ -43,23 +42,3 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, onEdgesChange?.(changedEdges); } } - -export function updateConnectionLookup(lookup: Map>, edges: Edge[]) { - lookup.clear(); - - edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => { - if (source && target) { - const sourceKey = `${source}-source-${sourceHandle}`; - const targetKey = `${target}-target-${targetHandle}`; - - const prevSource = lookup.get(sourceKey) || new Map(); - const prevTarget = lookup.get(targetKey) || new Map(); - const connection = { source, target, sourceHandle, targetHandle }; - - lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection)); - lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection)); - } - }); - - return lookup; -} diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index e4b6a823..06a6198e 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -6,7 +6,9 @@ XYHandle, isMouseEvent, type Connection, - type HandleType + type HandleType, + areConnectionMapsEqual, + handleConnectionChange } from '@xyflow/system'; import { useStore } from '$lib/store'; @@ -20,6 +22,8 @@ export let position: $$Props['position'] = Position.Top; export let style: $$Props['style'] = undefined; export let isConnectable: $$Props['isConnectable'] = undefined; + export let onconnect: $$Props['onconnect'] = undefined; + export let ondisconnect: $$Props['ondisconnect'] = undefined; // export let isConnectableStart: $$Props['isConnectableStart'] = undefined; // export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined; @@ -59,7 +63,9 @@ panBy, cancelConnection, updateConnection, - autoPanOnConnect + autoPanOnConnect, + edges, + connectionLookup } = store; function onPointerDown(event: MouseEvent | TouchEvent) { @@ -108,6 +114,26 @@ } } + let prevConnections: Map | null = null; + let connections: Map | undefined; + + $: if (onconnect || ondisconnect) { + // connectionLookup is not reactive, so we use edges to get notified about updates + $edges; + connections = $connectionLookup.get(`${nodeId}-${type}-${id || null}`); + } + + $: { + if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) { + const _connections = connections ?? new Map(); + + handleConnectionChange(prevConnections, _connections, ondisconnect); + handleConnectionChange(_connections, prevConnections, onconnect); + } + + prevConnections = connections ?? new Map(); + } + // @todo implement connectablestart, connectableend diff --git a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte index f059ff8e..f52d696a 100644 --- a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte +++ b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte @@ -107,7 +107,7 @@ trigger: [ { ...deleteKeyDefinition, - callback: () => deleteKeyDefinition.key && deleteKeyPressed.set(true) + callback: (event) => deleteKeyDefinition.key && deleteKeyPressed.set(true) } ], type: 'keydown' diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts new file mode 100644 index 00000000..c8df0dae --- /dev/null +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -0,0 +1,30 @@ +import { derived } from 'svelte/store'; +import { areConnectionMapsEqual, type Connection, type HandleType } from '@xyflow/system'; + +import { useStore } from '$lib/store'; + +export type useHandleConnectionsParams = { + nodeId: string; + type: HandleType; + id?: string | null; +}; + +const initialConnections: Connection[] = []; + +export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) { + const { edges, connectionLookup } = useStore(); + let prevConnections: Map | undefined = undefined; + + return derived( + [edges, connectionLookup], + ([, connectionLookup], set) => { + const nextConnections = connectionLookup.get(`${nodeId}-${type}-${id || null}`); + + if (!areConnectionMapsEqual(nextConnections, prevConnections)) { + prevConnections = nextConnections; + set(Array.from(prevConnections?.values() || [])); + } + }, + initialConnections + ); +} diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts new file mode 100644 index 00000000..f50bc8b8 --- /dev/null +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -0,0 +1,64 @@ +import { derived, type Readable } from 'svelte/store'; + +import type { Node } from '$lib/types'; +import { useStore } from '$lib/store'; + +function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) { + if ((!a && !b) || (!a?.length && !b?.length)) { + true; + } + + if (!a || !b || a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +} + +export function useNodesData( + nodeId: string +): Readable; +export function useNodesData( + nodeIds: string[] +): Readable; +export function useNodesData( + nodeIds: string[], + guard: (node: Node) => node is NodeType +): Readable; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function useNodesData(nodeIds: any): any { + const { nodes, nodeLookup } = useStore(); + let prevNodesData: (Node['data'] | null)[] | null = null; + + return derived([nodes, nodeLookup], ([, nodeLookup], set) => { + let nextNodesData: (Node['data'] | null)[] | null = null; + const nodeIdArray = Array.isArray(nodeIds); + + if (!nodeIdArray) { + nextNodesData = [nodeLookup.get(nodeIds)?.data || null]; + } else { + const data = []; + + for (const nodeId of nodeIds) { + const nodeData = nodeLookup.get(nodeId)?.data; + + if (nodeData) { + data.push(nodeData); + } + } + + nextNodesData = data; + } + + if (!areNodesDataEqual(nextNodesData, prevNodesData)) { + prevNodesData = nextNodesData; + set(nodeIdArray ? nextNodesData : nextNodesData[0]); + } + }); +} diff --git a/packages/svelte/src/lib/hooks/useUpdateNodeData.ts b/packages/svelte/src/lib/hooks/useUpdateNodeData.ts new file mode 100644 index 00000000..cb45dd10 --- /dev/null +++ b/packages/svelte/src/lib/hooks/useUpdateNodeData.ts @@ -0,0 +1,22 @@ +import { useStore } from '$lib/store'; + +export function useUpdateNodeData(): (id: string, data: unknown) => void { + const { nodes } = useStore(); + + const updateNodeData = (id: string, data: unknown) => { + nodes.update((nds) => + nds.map((node) => { + if (node.id === id) { + return { + ...node, + data + }; + } + + return node; + }) + ); + }; + + return updateNodeData; +} diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index a9107415..71841ec7 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -27,6 +27,9 @@ export * from '$lib/hooks/useSvelteFlow'; export * from '$lib/hooks/useUpdateNodeInternals'; export * from '$lib/hooks/useConnection'; export * from '$lib/hooks/useNodesEdges'; +export * from '$lib/hooks/useHandleConnections'; +export * from '$lib/hooks/useNodesData'; +export * from '$lib/hooks/useUpdateNodeData'; // types export type { diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 9f51abc3..ceec1c7b 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -17,7 +17,9 @@ import { type Viewport, updateNodes, getNodesBounds, - getViewportForBounds + getViewportForBounds, + updateConnectionLookup, + type ConnectionLookup } from '@xyflow/system'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; @@ -72,11 +74,12 @@ export const getInitialStore = ({ height?: number; fitView?: boolean; }) => { - const nodeLookup = new Map(); + const nodeLookup = new Map(); const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); + const connectionLookup = updateConnectionLookup(new Map(), edges); let viewport: Viewport = { x: 0, y: 0, zoom: 1 }; @@ -91,8 +94,9 @@ export const getInitialStore = ({ nodes: createNodesStore(nextNodes, nodeLookup), nodeLookup: readable>(nodeLookup), visibleNodes: readable([]), - edges: createEdgesStore(edges), + edges: createEdgesStore(edges, connectionLookup), edgeTree: readable[]>([]), + connectionLookup: readable(connectionLookup), height: writable(500), width: writable(500), minZoom: writable(0.5), diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts index a11bf1f6..b9292ca7 100644 --- a/packages/svelte/src/lib/store/utils.ts +++ b/packages/svelte/src/lib/store/utils.ts @@ -6,7 +6,13 @@ import { type Writable, get } from 'svelte/store'; -import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system'; +import { + updateNodes, + type Viewport, + type PanZoomInstance, + type ConnectionLookup, + updateConnectionLookup +} from '@xyflow/system'; import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types'; @@ -168,6 +174,7 @@ export const createNodesStore = ( export const createEdgesStore = ( edges: Edge[], + connectionLookup: ConnectionLookup, defaultOptions?: DefaultEdgeOptions ): Writable & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => { const { subscribe, set, update } = writable([]); @@ -176,6 +183,9 @@ export const createEdgesStore = ( const _set: typeof set = (eds: Edge[]) => { const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds; + + updateConnectionLookup(connectionLookup, nextEdges); + value = nextEdges; set(value); }; diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index 62666705..5d33a97c 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -31,6 +31,8 @@ export type HandleComponentProps = { isConnectable?: boolean; isConnectableStart?: boolean; isConnectableEnd?: boolean; + onconnect?: (connections: Connection[]) => void; + ondisconnect?: (connections: Connection[]) => void; }; export type FitViewOptions = FitViewOptionsBase; diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index e601ee08..1591cca4 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -139,3 +139,5 @@ export type UpdateConnection = (params: { export type ColorModeClass = 'light' | 'dark'; export type ColorMode = ColorModeClass | 'system'; + +export type ConnectionLookup = Map>; diff --git a/packages/system/src/utils/connections.ts b/packages/system/src/utils/connections.ts new file mode 100644 index 00000000..ded405c3 --- /dev/null +++ b/packages/system/src/utils/connections.ts @@ -0,0 +1,53 @@ +import { Connection } from '../types'; + +/** + * @internal + */ +export function areConnectionMapsEqual(a?: Map, b?: Map) { + if (!a && !b) { + return true; + } + + if (!a || !b || a.size !== b.size) { + return false; + } + + if (!a.size && !b.size) { + return true; + } + + for (const key of a.keys()) { + if (!b.has(key)) { + return false; + } + } + + return true; +} + +/** + * We call the callback for all connections in a that are not in b + * + * @internal + */ +export function handleConnectionChange( + a: Map, + b: Map, + cb?: (diff: Connection[]) => void +) { + if (!cb) { + return; + } + + const diff: Connection[] = []; + + a.forEach((connection, key) => { + if (!b?.has(key)) { + diff.push(connection); + } + }); + + if (diff.length) { + cb(diff); + } +} diff --git a/packages/system/src/utils/index.ts b/packages/system/src/utils/index.ts index 4937033c..cd66e951 100644 --- a/packages/system/src/utils/index.ts +++ b/packages/system/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './connections'; export * from './dom'; export * from './edges'; export * from './graph'; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index eca6b8de..86434a72 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -9,6 +9,8 @@ import { Transform, XYPosition, XYZPosition, + ConnectionLookup, + EdgeBase, } from '../types'; import { getDimensions, getHandleBounds } from './dom'; import { isNumeric } from './general'; @@ -71,11 +73,13 @@ export function updateNodes( defaults: {}, } ): NodeType[] { + const tmpLookup = new Map(nodeLookup); + nodeLookup.clear(); const parentNodes: ParentNodes = {}; const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; const nextNodes = nodes.map((n) => { - const currentStoreNode = nodeLookup.get(n.id); + const currentStoreNode = tmpLookup.get(n.id); const node: NodeType = { ...options.defaults, ...n, @@ -233,3 +237,23 @@ export function panBy({ return transformChanged; } + +export function updateConnectionLookup(lookup: ConnectionLookup, edges: EdgeBase[]) { + lookup.clear(); + + edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => { + if (source && target) { + const sourceKey = `${source}-source-${sourceHandle}`; + const targetKey = `${target}-target-${targetHandle}`; + + const prevSource = lookup.get(sourceKey) || new Map(); + const prevTarget = lookup.get(targetKey) || new Map(); + const connection = { source, target, sourceHandle, targetHandle }; + + lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection)); + lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection)); + } + }); + + return lookup; +} diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 6ec86528..826a1c80 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -58,12 +58,10 @@ export type XYHandleInstance = { type Result = { handleDomNode: Element | null; isValid: boolean; - connection: Connection; + connection: Connection | null; endHandle: ConnectingHandle | null; }; -const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null }; - const alwaysValid = () => true; let connectionStartHandle: ConnectingHandle | null = null; @@ -197,7 +195,7 @@ function onPointerDown( return resetRecentHandle(prevActiveHandle, lib); } - if (connection.source !== connection.target && handleDomNode) { + if (connection?.source !== connection?.target && handleDomNode) { resetRecentHandle(prevActiveHandle, lib); prevActiveHandle = handleDomNode; handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`); @@ -269,7 +267,7 @@ function isValidHandle( const result: Result = { handleDomNode: handleToCheck, isValid: false, - connection: nullConnection, + connection: null, endHandle: null, }; @@ -280,6 +278,10 @@ function isValidHandle( const connectable = handleToCheck.classList.contains('connectable'); const connectableEnd = handleToCheck.classList.contains('connectableend'); + if (!handleNodeId) { + return result; + } + const connection: Connection = { source: isTarget ? handleNodeId : fromNodeId, sourceHandle: isTarget ? handleId : fromHandleId, From fcf151f6cb543a248f9d49ce884d67460134c8e0 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 11 Dec 2023 19:35:31 +0100 Subject: [PATCH 22/32] fix(svelte): shortcut delete/input issue #3712 --- .../examples/usenodesdata/ResultNode.svelte | 2 +- .../svelte/src/lib/actions/shortcut/index.ts | 89 +++++++++++++++++++ .../components/KeyHandler/KeyHandler.svelte | 35 +++++--- 3 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 packages/svelte/src/lib/actions/shortcut/index.ts diff --git a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte index c4150f42..2664e8ef 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte @@ -17,7 +17,7 @@ type: 'target' }); - const nodeData = useNodesData($connections.map((connection) => connection.source)); + $: nodeData = useNodesData($connections.map((connection) => connection.source));
diff --git a/packages/svelte/src/lib/actions/shortcut/index.ts b/packages/svelte/src/lib/actions/shortcut/index.ts new file mode 100644 index 00000000..10c1e207 --- /dev/null +++ b/packages/svelte/src/lib/actions/shortcut/index.ts @@ -0,0 +1,89 @@ +// Shortcut action is copied and slightly adjusted from https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/shortcut +// If this discussion https://github.com/vnphanquang/svelte-put/discussions/256 gets resolved, we will use the original action again. + +export interface ShortcutEventDetail { + event: KeyboardEvent; + trigger: ShortcutTrigger; +} + +export type ShortcutModifier = 'alt' | 'ctrl' | 'meta' | 'shift'; + +export type ShortcutModifierDefinition = + | ShortcutModifier + | ShortcutModifier[] + | ShortcutModifier[][]; + +export type ShortcutTrigger = { + enabled?: boolean; + modifier?: ShortcutModifierDefinition; + id?: string; + key: string; + callback?: (detail: ShortcutEventDetail) => void; + preventDefault?: boolean; +}; + +export type ShortcutParameter = { + enabled?: boolean; + trigger: Array | ShortcutTrigger; + type?: 'keydown' | 'keyup'; +}; + +export function shortcut(node: Window, param: ShortcutParameter) { + let { enabled = true, trigger, type = 'keydown' } = param; + + function handler(event: KeyboardEvent) { + const normalizedTriggers = Array.isArray(trigger) ? trigger : [trigger]; + const modifiedMap = { + alt: event.altKey, + ctrl: event.ctrlKey, + shift: event.shiftKey, + meta: event.metaKey + }; + for (const trigger of normalizedTriggers) { + const mergedTrigger = { + modifier: [], + preventDefault: false, + enabled: true, + ...trigger + }; + const { modifier, key, callback, preventDefault, enabled: triggerEnabled } = mergedTrigger; + if (triggerEnabled) { + if (modifier.length) { + const modifierDefs = (Array.isArray(modifier) ? modifier : [modifier]).map((def) => + typeof def === 'string' ? [def] : def + ); + const modified = modifierDefs.some((def) => + def.every((modifier) => modifiedMap[modifier]) + ); + if (!modified) continue; + } + if (event.key === key) { + if (preventDefault) event.preventDefault(); + const detail = { event, trigger: mergedTrigger }; + callback?.(detail); + } + } + } + } + + if (enabled) node.addEventListener(type, handler); + + return { + update: (update: ShortcutParameter) => { + const { enabled: newEnabled = true, type: newType = 'keydown' } = update; + + if (enabled && (!newEnabled || type !== newType)) { + node.removeEventListener(type, handler); + } else if (!enabled && newEnabled) { + node.addEventListener(newType, handler); + } + + enabled = newEnabled; + type = newType; + trigger = update.trigger; + }, + destroy: () => { + node.removeEventListener(type, handler); + } + }; +} diff --git a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte index f52d696a..54f3a9c1 100644 --- a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte +++ b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte @@ -1,10 +1,10 @@
@@ -14,7 +14,7 @@
updateNodeData(id, { text: evt.currentTarget.value })} + on:input={(evt) => setNodeData(id, { text: evt.currentTarget.value })} />
diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index 3e311f1f..b23481d8 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -5,14 +5,14 @@ type NodeProps, useHandleConnections, useNodesData, - useUpdateNodeData + useSetNodeData } from '@xyflow/svelte'; type $$Props = NodeProps; export let id: $$Props['id']; - const updateNodeData = useUpdateNodeData(); + const setNodeData = useSetNodeData(); const connections = useHandleConnections({ nodeId: id, type: 'target' @@ -21,7 +21,7 @@ $: nodeData = useNodesData($connections[0]?.source); $: { - updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); + setNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); } diff --git a/packages/react/src/hooks/useSetNodeData.ts b/packages/react/src/hooks/useSetNodeData.ts new file mode 100644 index 00000000..16d33f66 --- /dev/null +++ b/packages/react/src/hooks/useSetNodeData.ts @@ -0,0 +1,30 @@ +import { useCallback } from 'react'; + +import useReactFlow from './useReactFlow'; +import { Node } from '../types'; + +export function useSetNodeData() { + const { setNodes } = useReactFlow(); + + const setNodeData = useCallback( + function setNodesData( + id: string, + dataUpdate: object | ((node: NodeType) => object), + options: { replace: boolean } = { replace: true } + ) { + setNodes((prevNodes) => + prevNodes.map((node) => { + if (node.id === id) { + const nextData = typeof dataUpdate === 'function' ? dataUpdate(node as NodeType) : dataUpdate; + return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } }; + } + + return node; + }) + ); + }, + [setNodes] + ); + + return setNodeData; +} diff --git a/packages/react/src/hooks/useUpdateNodeData.ts b/packages/react/src/hooks/useUpdateNodeData.ts deleted file mode 100644 index 10cd71e2..00000000 --- a/packages/react/src/hooks/useUpdateNodeData.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useCallback } from 'react'; - -import useReactFlow from './useReactFlow'; - -export function useUpdateNodeData() { - const { setNodes } = useReactFlow(); - - const updateNodeData = useCallback((id: string, data: unknown) => { - setNodes((prevNodes) => prevNodes.map((node) => (node.id === id ? { ...node, data } : node))); - }, []); - - return updateNodeData; -} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 63b70c98..ab450904 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -23,7 +23,7 @@ export { default as useOnViewportChange, type UseOnViewportChangeOptions } from export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange'; export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized'; export { useHandleConnections } from './hooks/useHandleConnections'; -export { useUpdateNodeData } from './hooks/useUpdateNodeData'; +export { useSetNodeData } from './hooks/useSetNodeData'; export { useNodesData } from './hooks/useNodesData'; export { useNodeId } from './contexts/NodeIdContext'; diff --git a/packages/svelte/src/lib/hooks/useSetNodeData.ts b/packages/svelte/src/lib/hooks/useSetNodeData.ts new file mode 100644 index 00000000..51aaaacb --- /dev/null +++ b/packages/svelte/src/lib/hooks/useSetNodeData.ts @@ -0,0 +1,28 @@ +import { useStore } from '$lib/store'; +import type { Node } from '$lib/types'; + +export function useSetNodeData() { + const { nodes } = useStore(); + + const setNodeData = ( + id: string, + dataUpdate: object | ((node: NodeType) => object), + options: { replace: boolean } = { replace: true } + ) => { + nodes.update((nds) => + nds.map((node) => { + if (node.id === id) { + const nextData = + typeof dataUpdate === 'function' ? dataUpdate(node as NodeType) : dataUpdate; + return options.replace + ? { ...node, data: nextData } + : { ...node, data: { ...node.data, ...nextData } }; + } + + return node; + }) + ); + }; + + return setNodeData; +} diff --git a/packages/svelte/src/lib/hooks/useUpdateNodeData.ts b/packages/svelte/src/lib/hooks/useUpdateNodeData.ts deleted file mode 100644 index cb45dd10..00000000 --- a/packages/svelte/src/lib/hooks/useUpdateNodeData.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useStore } from '$lib/store'; - -export function useUpdateNodeData(): (id: string, data: unknown) => void { - const { nodes } = useStore(); - - const updateNodeData = (id: string, data: unknown) => { - nodes.update((nds) => - nds.map((node) => { - if (node.id === id) { - return { - ...node, - data - }; - } - - return node; - }) - ); - }; - - return updateNodeData; -} diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 71841ec7..4675f2ca 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -29,7 +29,7 @@ export * from '$lib/hooks/useConnection'; export * from '$lib/hooks/useNodesEdges'; export * from '$lib/hooks/useHandleConnections'; export * from '$lib/hooks/useNodesData'; -export * from '$lib/hooks/useUpdateNodeData'; +export * from '$lib/hooks/useSetNodeData'; // types export type { From 8ceb1addc186726990f7ba2471abe410bf0aa6d7 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 12 Dec 2023 18:52:45 +0100 Subject: [PATCH 26/32] refactor(useSetNodeData): put function in useReactFlow/useSvelteFlow --- .../src/examples/UseNodesData/TextNode.tsx | 6 +- .../examples/UseNodesData/UppercaseNode.tsx | 6 +- .../examples/usenodesdata/TextNode.svelte | 6 +- .../usenodesdata/UppercaseNode.svelte | 6 +- packages/react/src/hooks/useReactFlow.ts | 35 +++- packages/react/src/hooks/useSetNodeData.ts | 30 --- packages/react/src/index.ts | 1 - packages/react/src/types/instance.ts | 13 ++ packages/react/src/utils/general.ts | 4 +- packages/svelte/package.json | 2 +- .../svelte/src/lib/actions/shortcut/index.ts | 89 --------- .../components/KeyHandler/KeyHandler.svelte | 4 +- .../svelte/src/lib/hooks/useSetNodeData.ts | 28 --- .../svelte/src/lib/hooks/useSvelteFlow.ts | 67 ++++--- packages/svelte/src/lib/index.ts | 1 - packages/svelte/src/lib/utils/index.ts | 4 +- packages/system/src/utils/graph.ts | 11 +- pnpm-lock.yaml | 188 ++++++++++++++---- 18 files changed, 254 insertions(+), 247 deletions(-) delete mode 100644 packages/react/src/hooks/useSetNodeData.ts delete mode 100644 packages/svelte/src/lib/actions/shortcut/index.ts delete mode 100644 packages/svelte/src/lib/hooks/useSetNodeData.ts diff --git a/examples/react/src/examples/UseNodesData/TextNode.tsx b/examples/react/src/examples/UseNodesData/TextNode.tsx index e787d824..6ca424e6 100644 --- a/examples/react/src/examples/UseNodesData/TextNode.tsx +++ b/examples/react/src/examples/UseNodesData/TextNode.tsx @@ -1,10 +1,10 @@ import { memo, ChangeEventHandler } from 'react'; -import { Position, NodeProps, useSetNodeData, Handle } from '@xyflow/react'; +import { Position, NodeProps, Handle, useReactFlow } from '@xyflow/react'; function TextNode({ id, data }: NodeProps) { - const setNodeData = useSetNodeData(); + const { updateNodeData } = useReactFlow(); - const onChange: ChangeEventHandler = (evt) => setNodeData(id, { text: evt.target.value }); + const onChange: ChangeEventHandler = (evt) => updateNodeData(id, { text: evt.target.value }); return (
diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 47ecd87c..699dc37e 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -1,15 +1,15 @@ import { memo, useEffect } from 'react'; -import { Position, NodeProps, useSetNodeData, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; +import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; function UppercaseNode({ id }: NodeProps) { + const { updateNodeData } = useReactFlow(); const connections = useHandleConnections({ type: 'target', }); const nodeData = useNodesData(connections[0]?.source); - const setNodeData = useSetNodeData(); useEffect(() => { - setNodeData(id, { text: nodeData?.text.toUpperCase() }); + updateNodeData(id, { text: nodeData?.text.toUpperCase() }); }, [nodeData]); return ( diff --git a/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte index 63f2d83b..c1642da2 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte @@ -1,12 +1,12 @@
@@ -14,7 +14,7 @@
setNodeData(id, { text: evt.currentTarget.value })} + on:input={(evt) => updateNodeData(id, { text: evt.currentTarget.value })} />
diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index b23481d8..88c8a918 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -5,14 +5,14 @@ type NodeProps, useHandleConnections, useNodesData, - useSetNodeData + useSvelteFlow } from '@xyflow/svelte'; type $$Props = NodeProps; export let id: $$Props['id']; - const setNodeData = useSetNodeData(); + const { updateNodeData } = useSvelteFlow(); const connections = useHandleConnections({ nodeId: id, type: 'target' @@ -21,7 +21,7 @@ $: nodeData = useNodesData($connections[0]?.source); $: { - setNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); + updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); } diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 44e29c14..9bb17eb9 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -10,7 +10,7 @@ import { } from '@xyflow/system'; import useViewportHelper from './useViewportHelper'; -import { useStoreApi } from '../hooks/useStore'; +import { useStoreApi } from './useStore'; import type { ReactFlowInstance, Instance, @@ -24,6 +24,7 @@ import type { Node, Edge, } from '../types'; +import { isNode } from '../utils'; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export default function useReactFlow(): ReactFlowInstance { @@ -271,6 +272,36 @@ export default function useReactFlow(): ReactFlo return getOutgoersBase(node, nodes, edges); }, []); + const updateNode = useCallback( + (id, nodeUpdate, options = { replace: true }) => { + setNodes((prevNodes) => + prevNodes.map((node) => { + if (node.id === id) { + const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate; + return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode }; + } + + return node; + }) + ); + }, + [setNodes] + ); + + const updateNodeData = useCallback( + (id, dataUpdate, options = { replace: false }) => { + updateNode( + id, + (node) => { + const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate; + return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } }; + }, + options + ); + }, + [updateNode] + ); + return useMemo(() => { return { ...viewportHelper, @@ -289,6 +320,8 @@ export default function useReactFlow(): ReactFlo getConnectedEdges, getIncomers, getOutgoers, + updateNode, + updateNodeData, }; }, [ viewportHelper, diff --git a/packages/react/src/hooks/useSetNodeData.ts b/packages/react/src/hooks/useSetNodeData.ts deleted file mode 100644 index 16d33f66..00000000 --- a/packages/react/src/hooks/useSetNodeData.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback } from 'react'; - -import useReactFlow from './useReactFlow'; -import { Node } from '../types'; - -export function useSetNodeData() { - const { setNodes } = useReactFlow(); - - const setNodeData = useCallback( - function setNodesData( - id: string, - dataUpdate: object | ((node: NodeType) => object), - options: { replace: boolean } = { replace: true } - ) { - setNodes((prevNodes) => - prevNodes.map((node) => { - if (node.id === id) { - const nextData = typeof dataUpdate === 'function' ? dataUpdate(node as NodeType) : dataUpdate; - return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } }; - } - - return node; - }) - ); - }, - [setNodes] - ); - - return setNodeData; -} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index ab450904..ae82e78b 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -23,7 +23,6 @@ export { default as useOnViewportChange, type UseOnViewportChangeOptions } from export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange'; export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized'; export { useHandleConnections } from './hooks/useHandleConnections'; -export { useSetNodeData } from './hooks/useSetNodeData'; export { useNodesData } from './hooks/useNodesData'; export { useNodeId } from './contexts/NodeIdContext'; diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index f229ec87..a1c822bb 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -45,6 +45,17 @@ export namespace Instance { export type getConnectedEdges = (id: string | (Node | { id: Node['id'] })[]) => Edge[]; export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[]; export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[]; + + export type UpdateNode = ( + id: string, + dataUpdate: Partial | ((node: Node) => Partial), + options?: { replace: boolean } + ) => void; + export type UpdateNodeData = ( + id: string, + dataUpdate: object | ((node: Node) => object), + options?: { replace: boolean } + ) => void; } export type ReactFlowInstance = { @@ -60,5 +71,7 @@ export type ReactFlowInstance = { deleteElements: Instance.DeleteElements; getIntersectingNodes: Instance.GetIntersectingNodes; isNodeIntersecting: Instance.IsNodeIntersecting; + updateNode: Instance.UpdateNode; + updateNodeData: Instance.UpdateNodeData; viewportInitialized: boolean; } & Omit; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index e13247c1..a106e182 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -10,8 +10,8 @@ import { import type { Edge, Node } from '../types'; -export const isNode = isNodeBase; -export const isEdge = isEdgeBase; +export const isNode = isNodeBase; +export const isEdge = isEdgeBase; export const getOutgoers = getOutgoersBase; export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 5ed9f8e9..60f4b8ed 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -41,7 +41,7 @@ "access": "public" }, "dependencies": { - "@svelte-put/shortcut": "^3.0.0", + "@svelte-put/shortcut": "^3.1.0", "@xyflow/system": "workspace:*", "classcat": "^5.0.4" }, diff --git a/packages/svelte/src/lib/actions/shortcut/index.ts b/packages/svelte/src/lib/actions/shortcut/index.ts deleted file mode 100644 index 10c1e207..00000000 --- a/packages/svelte/src/lib/actions/shortcut/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Shortcut action is copied and slightly adjusted from https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/shortcut -// If this discussion https://github.com/vnphanquang/svelte-put/discussions/256 gets resolved, we will use the original action again. - -export interface ShortcutEventDetail { - event: KeyboardEvent; - trigger: ShortcutTrigger; -} - -export type ShortcutModifier = 'alt' | 'ctrl' | 'meta' | 'shift'; - -export type ShortcutModifierDefinition = - | ShortcutModifier - | ShortcutModifier[] - | ShortcutModifier[][]; - -export type ShortcutTrigger = { - enabled?: boolean; - modifier?: ShortcutModifierDefinition; - id?: string; - key: string; - callback?: (detail: ShortcutEventDetail) => void; - preventDefault?: boolean; -}; - -export type ShortcutParameter = { - enabled?: boolean; - trigger: Array | ShortcutTrigger; - type?: 'keydown' | 'keyup'; -}; - -export function shortcut(node: Window, param: ShortcutParameter) { - let { enabled = true, trigger, type = 'keydown' } = param; - - function handler(event: KeyboardEvent) { - const normalizedTriggers = Array.isArray(trigger) ? trigger : [trigger]; - const modifiedMap = { - alt: event.altKey, - ctrl: event.ctrlKey, - shift: event.shiftKey, - meta: event.metaKey - }; - for (const trigger of normalizedTriggers) { - const mergedTrigger = { - modifier: [], - preventDefault: false, - enabled: true, - ...trigger - }; - const { modifier, key, callback, preventDefault, enabled: triggerEnabled } = mergedTrigger; - if (triggerEnabled) { - if (modifier.length) { - const modifierDefs = (Array.isArray(modifier) ? modifier : [modifier]).map((def) => - typeof def === 'string' ? [def] : def - ); - const modified = modifierDefs.some((def) => - def.every((modifier) => modifiedMap[modifier]) - ); - if (!modified) continue; - } - if (event.key === key) { - if (preventDefault) event.preventDefault(); - const detail = { event, trigger: mergedTrigger }; - callback?.(detail); - } - } - } - } - - if (enabled) node.addEventListener(type, handler); - - return { - update: (update: ShortcutParameter) => { - const { enabled: newEnabled = true, type: newType = 'keydown' } = update; - - if (enabled && (!newEnabled || type !== newType)) { - node.removeEventListener(type, handler); - } else if (!enabled && newEnabled) { - node.addEventListener(newType, handler); - } - - enabled = newEnabled; - type = newType; - trigger = update.trigger; - }, - destroy: () => { - node.removeEventListener(type, handler); - } - }; -} diff --git a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte index 54f3a9c1..36206982 100644 --- a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte +++ b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte @@ -1,10 +1,10 @@ @@ -69,11 +77,11 @@ {edges} fitView fitViewOptions={{ padding: 2 }} - on:connectstart={({ detail: { nodeId } }) => { + onconnectstart={(_, { nodeId }) => { // Memorize the nodeId you start draggin a connection line from a node connectingNodeId = nodeId; }} - on:connectend={handleConnectEnd} + onconnectend={handleConnectEnd} />
diff --git a/examples/svelte/src/routes/examples/customnode/+page.svelte b/examples/svelte/src/routes/examples/customnode/+page.svelte index 123932f9..525b0706 100644 --- a/examples/svelte/src/routes/examples/customnode/+page.svelte +++ b/examples/svelte/src/routes/examples/customnode/+page.svelte @@ -87,7 +87,7 @@ {nodeTypes} style="--xy-background-color: {$bgColor}" fitView - on:connect={onConnect} + onconnect={onConnect} > diff --git a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte index 25d37d32..ca01be5e 100644 --- a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte +++ b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte @@ -10,7 +10,7 @@
- +
Custom Color Picker Node: {$colorStore}
@@ -20,14 +20,8 @@ on:input={(evt) => colorStore.set(evt.currentTarget.value)} value={$colorStore} /> - - + +