@stonecrop/node-editor 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/assets/index.css +430 -0
  2. package/dist/node-editor.js +821 -642
  3. package/dist/node-editor.js.map +1 -1
  4. package/dist/src/components/EditableEdge.vue.d.ts +11 -0
  5. package/dist/src/components/EditableEdge.vue.d.ts.map +1 -0
  6. package/dist/src/components/EditableNode.vue.d.ts +10 -0
  7. package/dist/src/components/EditableNode.vue.d.ts.map +1 -0
  8. package/dist/src/components/NodeEditor.vue.d.ts +14 -0
  9. package/dist/src/components/NodeEditor.vue.d.ts.map +1 -0
  10. package/dist/src/components/SelfLoopEdge.vue.d.ts +11 -0
  11. package/dist/src/components/SelfLoopEdge.vue.d.ts.map +1 -0
  12. package/dist/src/components/StateEditor.vue.d.ts +69 -0
  13. package/dist/src/components/StateEditor.vue.d.ts.map +1 -0
  14. package/dist/src/index.d.ts +1 -0
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  17. package/package.json +35 -28
  18. package/dist/node-editor.css +0 -1
  19. package/dist/node-editor.d.ts +0 -58
  20. package/dist/node_editor.tsbuildinfo +0 -1
  21. package/dist/src/index.js +0 -12
  22. package/dist/src/types/index.js +0 -0
  23. package/dist/src/utils/autoLayout.js +0 -50
  24. package/dist/src/utils/stateTransforms.js +0 -148
  25. package/src/components/EditableEdge.vue +0 -92
  26. package/src/components/EditableNode.vue +0 -65
  27. package/src/components/NodeEditor.vue +0 -397
  28. package/src/components/SelfLoopEdge.vue +0 -88
  29. package/src/components/StateEditor.vue +0 -46
  30. package/src/index.ts +0 -17
  31. package/src/shims-vue.d.ts +0 -5
  32. package/src/types/index.ts +0 -33
  33. package/src/utils/autoLayout.ts +0 -67
  34. package/src/utils/stateTransforms.ts +0 -164
@@ -1,649 +1,828 @@
1
- import { defineComponent as T, useTemplateRef as Y, ref as E, computed as M, openBlock as S, createElementBlock as P, Fragment as Q, createElementVNode as c, normalizeStyle as K, createVNode as O, unref as _, withCtx as R, withModifiers as D, toDisplayString as F, withDirectives as X, withKeys as W, vModelText as U, createCommentVNode as B, nextTick as L, createBlock as H, onMounted as Z, onBeforeUnmount as ie, normalizeClass as ue, createTextVNode as de, mergeProps as A, useModel as J, mergeModels as re } from "vue";
2
- import { getBezierPath as ce, EdgeLabelRenderer as ee, Handle as q, useVueFlow as ve, VueFlow as fe, Position as C } from "@vue-flow/core";
3
- import { Graph as pe, layout as ge } from "@dagrejs/dagre";
4
- const me = ["d"], be = ["id", "d", "marker-end"], ye = { class: "vue-flow__edge-label" }, he = {
5
- key: 0,
6
- class: "label-input-wrapper"
7
- }, we = {
8
- inheritAttrs: !1
9
- }, ke = /* @__PURE__ */ T({
10
- ...we,
11
- __name: "EditableEdge",
12
- props: {
13
- id: {},
14
- sourceNode: {},
15
- targetNode: {},
16
- source: {},
17
- target: {},
18
- type: {},
19
- label: { type: [String, Object, Function] },
20
- style: {},
21
- selected: { type: Boolean },
22
- sourcePosition: {},
23
- targetPosition: {},
24
- sourceHandleId: {},
25
- targetHandleId: {},
26
- animated: { type: Boolean },
27
- updatable: { type: Boolean },
28
- markerStart: {},
29
- markerEnd: {},
30
- curvature: {},
31
- interactionWidth: {},
32
- data: {},
33
- events: {},
34
- labelStyle: {},
35
- labelShowBg: { type: Boolean },
36
- labelBgStyle: {},
37
- labelBgPadding: {},
38
- labelBgBorderRadius: {},
39
- sourceX: {},
40
- sourceY: {},
41
- targetX: {},
42
- targetY: {}
43
- },
44
- emits: ["change", "remove"],
45
- setup(l, { emit: v }) {
46
- const r = l, p = v, n = Y("labelInput"), u = E(""), t = E(!1);
47
- let e = 0;
48
- const a = async () => {
49
- let f = Date.now();
50
- f - e < 500 && !t.value && await s(), e = f;
51
- }, s = async () => {
52
- u.value = r.label, t.value = !0, await L(), n.value?.focus();
53
- }, g = () => {
54
- t.value = !1, p("change", u.value);
55
- }, m = M(() => ce(r));
56
- return (f, y) => (S(), P(Q, null, [
57
- c("path", {
58
- d: m.value[0],
59
- fill: "none",
60
- stroke: "transparent",
61
- "stroke-width": "20",
62
- class: "vue-flow__edge-interaction"
63
- }, null, 8, me),
64
- c("path", {
65
- id: l.id,
66
- style: K(l.style),
67
- class: "vue-flow__edge-path",
68
- d: m.value[0],
69
- "marker-end": l.markerEnd
70
- }, null, 12, be),
71
- O(_(ee), null, {
72
- default: R(() => [
73
- c("div", {
74
- style: K({
75
- pointerEvents: "all",
76
- position: "absolute",
77
- transform: `translate(-50%, -50%) translate(${m.value[1]}px,${m.value[2]}px)`
78
- }),
79
- class: "nodrag nopan editable-edge-label",
80
- onClick: y[2] || (y[2] = (h) => a()),
81
- onContextmenu: y[3] || (y[3] = D((h) => f.$emit("remove", l.id), ["prevent"]))
82
- }, [
83
- c("div", ye, F(l.label), 1),
84
- t.value ? (S(), P("div", he, [
85
- X(c("input", {
86
- ref: "labelInput",
87
- "onUpdate:modelValue": y[0] || (y[0] = (h) => u.value = h),
88
- class: "label-input",
89
- onBlur: y[1] || (y[1] = (h) => t.value = !1),
90
- onKeypress: W(g, ["enter"])
91
- }, null, 544), [
92
- [U, u.value]
93
- ])
94
- ])) : B("", !0)
95
- ], 36)
96
- ]),
97
- _: 1
98
- })
99
- ], 64));
100
- }
101
- }), Se = {
102
- key: 0,
103
- class: "label-input-wrapper"
104
- }, $e = /* @__PURE__ */ T({
105
- __name: "EditableNode",
106
- props: {
107
- id: {},
108
- type: {},
109
- selected: { type: Boolean },
110
- connectable: { type: [Boolean, Number, String, Function] },
111
- position: {},
112
- dimensions: {},
113
- label: {},
114
- isValidTargetPos: { type: Function },
115
- isValidSourcePos: { type: Function },
116
- parent: {},
117
- parentNodeId: {},
118
- dragging: { type: Boolean },
119
- resizing: { type: Boolean },
120
- zIndex: {},
121
- targetPosition: {},
122
- sourcePosition: {},
123
- dragHandle: {},
124
- data: {},
125
- events: {}
126
- },
127
- emits: ["change"],
128
- setup(l, { emit: v }) {
129
- const r = l, p = v, n = Y("labelInput"), u = E(""), t = E(!1);
130
- let e = 0;
131
- const a = async () => {
132
- let m = Date.now();
133
- m - e < 500 && !t.value && await s(), e = m;
134
- }, s = async () => {
135
- u.value = r.label, t.value = !0, await L(), n.value?.focus();
136
- }, g = () => {
137
- t.value = !1, p("change", u.value);
138
- };
139
- return (m, f) => (S(), P("div", {
140
- onClick: f[2] || (f[2] = (y) => a())
141
- }, [
142
- c("div", null, F(l.label), 1),
143
- t.value ? (S(), P("div", Se, [
144
- X(c("input", {
145
- ref: "labelInput",
146
- "onUpdate:modelValue": f[0] || (f[0] = (y) => u.value = y),
147
- class: "label-input",
148
- onBlur: f[1] || (f[1] = (y) => t.value = !1),
149
- onKeypress: W(g, ["enter"])
150
- }, null, 544), [
151
- [U, u.value]
152
- ])
153
- ])) : B("", !0),
154
- l.data.hasInput ? (S(), H(_(q), {
155
- key: 1,
156
- id: "a",
157
- type: "target",
158
- position: l.targetPosition
159
- }, null, 8, ["position"])) : B("", !0),
160
- l.data.hasOutput ? (S(), H(_(q), {
161
- key: 2,
162
- id: "b",
163
- type: "source",
164
- position: l.sourcePosition
165
- }, null, 8, ["position"])) : B("", !0)
166
- ]));
167
- }
168
- }), Ce = ["d"], Ee = ["id", "d", "marker-end"], Pe = { class: "vue-flow__edge-label" }, Ne = {
169
- key: 0,
170
- class: "label-input-wrapper"
171
- }, Be = {
172
- inheritAttrs: !1
173
- }, Ie = /* @__PURE__ */ T({
174
- ...Be,
175
- __name: "SelfLoopEdge",
176
- props: {
177
- id: {},
178
- sourceNode: {},
179
- targetNode: {},
180
- source: {},
181
- target: {},
182
- type: {},
183
- label: { type: [String, Object, Function] },
184
- style: {},
185
- selected: { type: Boolean },
186
- sourcePosition: {},
187
- targetPosition: {},
188
- sourceHandleId: {},
189
- targetHandleId: {},
190
- animated: { type: Boolean },
191
- updatable: { type: Boolean },
192
- markerStart: {},
193
- markerEnd: {},
194
- curvature: {},
195
- interactionWidth: {},
196
- data: {},
197
- events: {},
198
- labelStyle: {},
199
- labelShowBg: { type: Boolean },
200
- labelBgStyle: {},
201
- labelBgPadding: {},
202
- labelBgBorderRadius: {},
203
- sourceX: {},
204
- sourceY: {},
205
- targetX: {},
206
- targetY: {}
207
- },
208
- emits: ["change", "remove"],
209
- setup(l, { emit: v }) {
210
- const r = l, p = v, n = 90, u = 45, t = M(() => {
211
- const { sourceX: h, sourceY: k, targetX: $, targetY: x } = r, j = `M ${h},${k} C ${h + u},${k - n} ${$ - u},${x - n} ${$},${x}`, z = (h + $) / 2, V = Math.min(k, x) - n * 0.72;
212
- return { d: j, labelX: z, labelY: V };
213
- }), e = Y("labelInput"), a = E(""), s = E(!1);
214
- let g = 0;
215
- const m = async () => {
216
- let h = Date.now();
217
- h - g < 500 && !s.value && await f(), g = h;
218
- }, f = async () => {
219
- a.value = r.label, s.value = !0, await L(), e.value?.focus();
220
- }, y = () => {
221
- s.value = !1, p("change", a.value);
222
- };
223
- return (h, k) => (S(), P(Q, null, [
224
- c("path", {
225
- d: t.value.d,
226
- fill: "none",
227
- stroke: "transparent",
228
- "stroke-width": "20",
229
- class: "vue-flow__edge-interaction"
230
- }, null, 8, Ce),
231
- c("path", {
232
- id: l.id,
233
- style: K(l.style),
234
- class: "vue-flow__edge-path",
235
- d: t.value.d,
236
- "marker-end": l.markerEnd
237
- }, null, 12, Ee),
238
- O(_(ee), null, {
239
- default: R(() => [
240
- c("div", {
241
- style: K({
242
- pointerEvents: "all",
243
- position: "absolute",
244
- transform: `translate(-50%, -50%) translate(${t.value.labelX}px,${t.value.labelY}px)`
245
- }),
246
- class: "nodrag nopan editable-edge-label",
247
- onClick: k[2] || (k[2] = ($) => m()),
248
- onContextmenu: k[3] || (k[3] = D(($) => h.$emit("remove", l.id), ["prevent"]))
249
- }, [
250
- c("div", Pe, F(l.label), 1),
251
- s.value ? (S(), P("div", Ne, [
252
- X(c("input", {
253
- ref: "labelInput",
254
- "onUpdate:modelValue": k[0] || (k[0] = ($) => a.value = $),
255
- class: "label-input",
256
- onBlur: k[1] || (k[1] = ($) => s.value = !1),
257
- onKeypress: W(y, ["enter"])
258
- }, null, 544), [
259
- [U, a.value]
260
- ])
261
- ])) : B("", !0)
262
- ], 36)
263
- ]),
264
- _: 1
265
- })
266
- ], 64));
267
- }
1
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, toDisplayString, unref, useModel, useTemplateRef, vModelText, withCtx, withDirectives, withKeys, withModifiers } from "vue";
2
+ import { EdgeLabelRenderer, Handle, Position, Position as Position$1, VueFlow, getBezierPath, useVueFlow } from "@vue-flow/core";
3
+ import { Graph, layout } from "@dagrejs/dagre";
4
+ import './assets/index.css';//#region src/components/EditableEdge.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1$3 = ["d"];
6
+ var _hoisted_2$2 = [
7
+ "id",
8
+ "d",
9
+ "marker-end"
10
+ ];
11
+ var _hoisted_3$2 = { class: "vue-flow__edge-label" };
12
+ var _hoisted_4$2 = {
13
+ key: 0,
14
+ class: "label-input-wrapper"
15
+ };
16
+ //#endregion
17
+ //#region src/components/EditableEdge.vue
18
+ var EditableEdge_default = /* @__PURE__ */ defineComponent({
19
+ inheritAttrs: false,
20
+ __name: "EditableEdge",
21
+ props: {
22
+ id: {},
23
+ sourceNode: {},
24
+ targetNode: {},
25
+ source: {},
26
+ target: {},
27
+ type: {},
28
+ label: { type: [
29
+ String,
30
+ Object,
31
+ Function
32
+ ] },
33
+ style: {},
34
+ selected: { type: Boolean },
35
+ sourcePosition: {},
36
+ targetPosition: {},
37
+ sourceHandleId: {},
38
+ targetHandleId: {},
39
+ animated: { type: Boolean },
40
+ updatable: { type: Boolean },
41
+ markerStart: {},
42
+ markerEnd: {},
43
+ curvature: {},
44
+ interactionWidth: {},
45
+ data: {},
46
+ events: {},
47
+ labelStyle: {},
48
+ labelShowBg: { type: Boolean },
49
+ labelBgStyle: {},
50
+ labelBgPadding: {},
51
+ labelBgBorderRadius: {},
52
+ sourceX: {},
53
+ sourceY: {},
54
+ targetX: {},
55
+ targetY: {}
56
+ },
57
+ emits: ["change", "remove"],
58
+ setup(__props, { emit: __emit }) {
59
+ const props = __props;
60
+ const emit = __emit;
61
+ const inputRef = useTemplateRef("labelInput");
62
+ const newLabel = ref("");
63
+ const showInput = ref(false);
64
+ let lastClick = 0;
65
+ const labelOnClick = async () => {
66
+ let now = Date.now();
67
+ if (now - lastClick < 500 && !showInput.value) await showLabelInput();
68
+ lastClick = now;
69
+ };
70
+ const showLabelInput = async () => {
71
+ newLabel.value = props.label;
72
+ showInput.value = true;
73
+ await nextTick();
74
+ inputRef.value?.focus();
75
+ };
76
+ const submitNewLabel = () => {
77
+ showInput.value = false;
78
+ emit("change", newLabel.value);
79
+ };
80
+ const path = computed(() => getBezierPath(props));
81
+ return (_ctx, _cache) => {
82
+ return openBlock(), createElementBlock(Fragment, null, [
83
+ createElementVNode("path", {
84
+ d: path.value[0],
85
+ fill: "none",
86
+ stroke: "transparent",
87
+ "stroke-width": "20",
88
+ class: "vue-flow__edge-interaction"
89
+ }, null, 8, _hoisted_1$3),
90
+ createElementVNode("path", {
91
+ id: __props.id,
92
+ style: normalizeStyle(__props.style),
93
+ class: "vue-flow__edge-path",
94
+ d: path.value[0],
95
+ "marker-end": __props.markerEnd
96
+ }, null, 12, _hoisted_2$2),
97
+ createVNode(unref(EdgeLabelRenderer), null, {
98
+ default: withCtx(() => [createElementVNode("div", {
99
+ style: normalizeStyle({
100
+ pointerEvents: "all",
101
+ position: "absolute",
102
+ transform: `translate(-50%, -50%) translate(${path.value[1]}px,${path.value[2]}px)`
103
+ }),
104
+ class: "nodrag nopan editable-edge-label",
105
+ onClick: _cache[2] || (_cache[2] = ($event) => labelOnClick()),
106
+ onContextmenu: _cache[3] || (_cache[3] = withModifiers(($event) => _ctx.$emit("remove", __props.id), ["prevent"]))
107
+ }, [createElementVNode("div", _hoisted_3$2, toDisplayString(__props.label), 1), showInput.value ? (openBlock(), createElementBlock("div", _hoisted_4$2, [withDirectives(createElementVNode("input", {
108
+ ref: "labelInput",
109
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => newLabel.value = $event),
110
+ class: "label-input",
111
+ onBlur: _cache[1] || (_cache[1] = ($event) => showInput.value = false),
112
+ onKeypress: withKeys(submitNewLabel, ["enter"])
113
+ }, null, 544), [[vModelText, newLabel.value]])])) : createCommentVNode("", true)], 36)]),
114
+ _: 1
115
+ })
116
+ ], 64);
117
+ };
118
+ }
119
+ });
120
+ //#endregion
121
+ //#region src/components/EditableNode.vue?vue&type=script&setup=true&lang.ts
122
+ var _hoisted_1$2 = {
123
+ key: 0,
124
+ class: "label-input-wrapper"
125
+ };
126
+ //#endregion
127
+ //#region src/components/EditableNode.vue
128
+ var EditableNode_default = /* @__PURE__ */ defineComponent({
129
+ __name: "EditableNode",
130
+ props: {
131
+ id: {},
132
+ type: {},
133
+ selected: { type: Boolean },
134
+ connectable: { type: [
135
+ Boolean,
136
+ Number,
137
+ String,
138
+ Function
139
+ ] },
140
+ position: {},
141
+ dimensions: {},
142
+ label: {},
143
+ isValidTargetPos: { type: Function },
144
+ isValidSourcePos: { type: Function },
145
+ parent: {},
146
+ parentNodeId: {},
147
+ dragging: { type: Boolean },
148
+ resizing: { type: Boolean },
149
+ zIndex: {},
150
+ targetPosition: {},
151
+ sourcePosition: {},
152
+ dragHandle: {},
153
+ data: {},
154
+ events: {}
155
+ },
156
+ emits: ["change"],
157
+ setup(__props, { emit: __emit }) {
158
+ const props = __props;
159
+ const emit = __emit;
160
+ const inputRef = useTemplateRef("labelInput");
161
+ const newLabel = ref("");
162
+ const showInput = ref(false);
163
+ let lastClick = 0;
164
+ const nodeOnClick = async () => {
165
+ let now = Date.now();
166
+ if (now - lastClick < 500 && !showInput.value) await showLabelInput();
167
+ lastClick = now;
168
+ };
169
+ const showLabelInput = async () => {
170
+ newLabel.value = props.label;
171
+ showInput.value = true;
172
+ await nextTick();
173
+ inputRef.value?.focus();
174
+ };
175
+ const submitNewLabel = () => {
176
+ showInput.value = false;
177
+ emit("change", newLabel.value);
178
+ };
179
+ return (_ctx, _cache) => {
180
+ return openBlock(), createElementBlock("div", { onClick: _cache[2] || (_cache[2] = ($event) => nodeOnClick()) }, [
181
+ createElementVNode("div", null, toDisplayString(__props.label), 1),
182
+ showInput.value ? (openBlock(), createElementBlock("div", _hoisted_1$2, [withDirectives(createElementVNode("input", {
183
+ ref: "labelInput",
184
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => newLabel.value = $event),
185
+ class: "label-input",
186
+ onBlur: _cache[1] || (_cache[1] = ($event) => showInput.value = false),
187
+ onKeypress: withKeys(submitNewLabel, ["enter"])
188
+ }, null, 544), [[vModelText, newLabel.value]])])) : createCommentVNode("", true),
189
+ __props.data.hasInput ? (openBlock(), createBlock(unref(Handle), {
190
+ key: 1,
191
+ id: "a",
192
+ type: "target",
193
+ position: __props.targetPosition
194
+ }, null, 8, ["position"])) : createCommentVNode("", true),
195
+ __props.data.hasOutput ? (openBlock(), createBlock(unref(Handle), {
196
+ key: 2,
197
+ id: "b",
198
+ type: "source",
199
+ position: __props.sourcePosition
200
+ }, null, 8, ["position"])) : createCommentVNode("", true)
201
+ ]);
202
+ };
203
+ }
268
204
  });
269
- function te(l, v = {}) {
270
- const { rankdir: r = "LR", nodeWidth: p = 160, nodeHeight: n = 40, dimensions: u } = v, t = (s) => u?.[s] ?? { width: p, height: n }, e = [];
271
- for (const s of l)
272
- "source" in s || e.push(s.id);
273
- if (e.length === 0) return [...l];
274
- const a = new pe();
275
- a.setGraph({ rankdir: r, nodesep: 40, ranksep: 80, edgesep: 20 }), a.setDefaultEdgeLabel(() => ({}));
276
- for (const s of e) {
277
- const { width: g, height: m } = t(s);
278
- a.setNode(s, { width: g, height: m });
279
- }
280
- for (const s of l)
281
- "source" in s && s.source !== s.target && a.setEdge(s.source, s.target);
282
- return ge(a), l.map((s) => {
283
- if ("source" in s) return s;
284
- const g = a.node(s.id);
285
- if (!g) return s;
286
- const { width: m, height: f } = t(s.id);
287
- return { ...s, position: { x: g.x - m / 2, y: g.y - f / 2 } };
288
- });
205
+ //#endregion
206
+ //#region src/components/SelfLoopEdge.vue?vue&type=script&setup=true&lang.ts
207
+ var _hoisted_1$1 = ["d"];
208
+ var _hoisted_2$1 = [
209
+ "id",
210
+ "d",
211
+ "marker-end"
212
+ ];
213
+ var _hoisted_3$1 = { class: "vue-flow__edge-label" };
214
+ var _hoisted_4$1 = {
215
+ key: 0,
216
+ class: "label-input-wrapper"
217
+ };
218
+ //#endregion
219
+ //#region src/components/SelfLoopEdge.vue
220
+ var SelfLoopEdge_default = /* @__PURE__ */ defineComponent({
221
+ inheritAttrs: false,
222
+ __name: "SelfLoopEdge",
223
+ props: {
224
+ id: {},
225
+ sourceNode: {},
226
+ targetNode: {},
227
+ source: {},
228
+ target: {},
229
+ type: {},
230
+ label: { type: [
231
+ String,
232
+ Object,
233
+ Function
234
+ ] },
235
+ style: {},
236
+ selected: { type: Boolean },
237
+ sourcePosition: {},
238
+ targetPosition: {},
239
+ sourceHandleId: {},
240
+ targetHandleId: {},
241
+ animated: { type: Boolean },
242
+ updatable: { type: Boolean },
243
+ markerStart: {},
244
+ markerEnd: {},
245
+ curvature: {},
246
+ interactionWidth: {},
247
+ data: {},
248
+ events: {},
249
+ labelStyle: {},
250
+ labelShowBg: { type: Boolean },
251
+ labelBgStyle: {},
252
+ labelBgPadding: {},
253
+ labelBgBorderRadius: {},
254
+ sourceX: {},
255
+ sourceY: {},
256
+ targetX: {},
257
+ targetY: {}
258
+ },
259
+ emits: ["change", "remove"],
260
+ setup(__props, { emit: __emit }) {
261
+ const props = __props;
262
+ const emit = __emit;
263
+ const LOOP_HEIGHT = 90;
264
+ const LOOP_SPREAD = 45;
265
+ const path = computed(() => {
266
+ const { sourceX, sourceY, targetX, targetY } = props;
267
+ return {
268
+ d: `M ${sourceX},${sourceY} C ${sourceX + LOOP_SPREAD},${sourceY - LOOP_HEIGHT} ${targetX - LOOP_SPREAD},${targetY - LOOP_HEIGHT} ${targetX},${targetY}`,
269
+ labelX: (sourceX + targetX) / 2,
270
+ labelY: Math.min(sourceY, targetY) - LOOP_HEIGHT * .72
271
+ };
272
+ });
273
+ const inputRef = useTemplateRef("labelInput");
274
+ const newLabel = ref("");
275
+ const showInput = ref(false);
276
+ let lastClick = 0;
277
+ const labelOnClick = async () => {
278
+ let now = Date.now();
279
+ if (now - lastClick < 500 && !showInput.value) await showLabelInput();
280
+ lastClick = now;
281
+ };
282
+ const showLabelInput = async () => {
283
+ newLabel.value = props.label;
284
+ showInput.value = true;
285
+ await nextTick();
286
+ inputRef.value?.focus();
287
+ };
288
+ const submitNewLabel = () => {
289
+ showInput.value = false;
290
+ emit("change", newLabel.value);
291
+ };
292
+ return (_ctx, _cache) => {
293
+ return openBlock(), createElementBlock(Fragment, null, [
294
+ createElementVNode("path", {
295
+ d: path.value.d,
296
+ fill: "none",
297
+ stroke: "transparent",
298
+ "stroke-width": "20",
299
+ class: "vue-flow__edge-interaction"
300
+ }, null, 8, _hoisted_1$1),
301
+ createElementVNode("path", {
302
+ id: __props.id,
303
+ style: normalizeStyle(__props.style),
304
+ class: "vue-flow__edge-path",
305
+ d: path.value.d,
306
+ "marker-end": __props.markerEnd
307
+ }, null, 12, _hoisted_2$1),
308
+ createVNode(unref(EdgeLabelRenderer), null, {
309
+ default: withCtx(() => [createElementVNode("div", {
310
+ style: normalizeStyle({
311
+ pointerEvents: "all",
312
+ position: "absolute",
313
+ transform: `translate(-50%, -50%) translate(${path.value.labelX}px,${path.value.labelY}px)`
314
+ }),
315
+ class: "nodrag nopan editable-edge-label",
316
+ onClick: _cache[2] || (_cache[2] = ($event) => labelOnClick()),
317
+ onContextmenu: _cache[3] || (_cache[3] = withModifiers(($event) => _ctx.$emit("remove", __props.id), ["prevent"]))
318
+ }, [createElementVNode("div", _hoisted_3$1, toDisplayString(__props.label), 1), showInput.value ? (openBlock(), createElementBlock("div", _hoisted_4$1, [withDirectives(createElementVNode("input", {
319
+ ref: "labelInput",
320
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => newLabel.value = $event),
321
+ class: "label-input",
322
+ onBlur: _cache[1] || (_cache[1] = ($event) => showInput.value = false),
323
+ onKeypress: withKeys(submitNewLabel, ["enter"])
324
+ }, null, 544), [[vModelText, newLabel.value]])])) : createCommentVNode("", true)], 36)]),
325
+ _: 1
326
+ })
327
+ ], 64);
328
+ };
329
+ }
330
+ });
331
+ //#endregion
332
+ //#region src/utils/autoLayout.ts
333
+ /**
334
+ * Compute node positions for a workflow graph with dagre's layered (Sugiyama) algorithm.
335
+ *
336
+ * Pure and synchronous: takes VueFlow elements (nodes + edges) and returns a new elements array with
337
+ * each node's `position` replaced by a laid-out coordinate; edges and every other node field are
338
+ * passed through untouched. Used two ways — as the seed for an un-arranged workflow (fixed default
339
+ * dimensions, no DOM) and behind the toolbar button (measured `dimensions`).
340
+ *
341
+ * Self-loop edges (`source === target`) are skipped: dagre doesn't rank them, and they're reserved
342
+ * for the separate mutate-in-place self-transition feature.
343
+ * @public
344
+ */
345
+ function autoLayout(elements, options = {}) {
346
+ const { rankdir = "LR", nodeWidth = 160, nodeHeight = 40, dimensions } = options;
347
+ const sizeOf = (id) => dimensions?.[id] ?? {
348
+ width: nodeWidth,
349
+ height: nodeHeight
350
+ };
351
+ const nodeIds = [];
352
+ for (const el of elements) if (!("source" in el)) nodeIds.push(el.id);
353
+ if (nodeIds.length === 0) return [...elements];
354
+ const graph = new Graph();
355
+ graph.setGraph({
356
+ rankdir,
357
+ nodesep: 40,
358
+ ranksep: 80,
359
+ edgesep: 20
360
+ });
361
+ graph.setDefaultEdgeLabel(() => ({}));
362
+ for (const id of nodeIds) {
363
+ const { width, height } = sizeOf(id);
364
+ graph.setNode(id, {
365
+ width,
366
+ height
367
+ });
368
+ }
369
+ for (const el of elements) {
370
+ if (!("source" in el)) continue;
371
+ if (el.source === el.target) continue;
372
+ graph.setEdge(el.source, el.target);
373
+ }
374
+ layout(graph);
375
+ return elements.map((el) => {
376
+ if ("source" in el) return el;
377
+ const center = graph.node(el.id);
378
+ if (!center) return el;
379
+ const { width, height } = sizeOf(el.id);
380
+ return {
381
+ ...el,
382
+ position: {
383
+ x: center.x - width / 2,
384
+ y: center.y - height / 2
385
+ }
386
+ };
387
+ });
289
388
  }
290
- const Le = { class: "chart-controls" }, xe = { class: "chart-controls-left" }, Oe = { class: "chart-controls-right" }, Ve = { key: 0 }, Re = { key: 1 }, oe = /* @__PURE__ */ T({
291
- __name: "NodeEditor",
292
- props: {
293
- modelValue: {},
294
- nodeContainerClass: { type: [Boolean, null, String, Object, Array], default: "" }
295
- },
296
- emits: ["update:modelValue"],
297
- setup(l, { emit: v }) {
298
- const r = v, p = E(!1), n = E([]), u = E(), t = E(""), e = M(
299
- () => n.value.findIndex((o) => o.id === t.value)
300
- ), a = M({
301
- get: () => {
302
- const o = l.modelValue;
303
- for (const i of o)
304
- i.data = { ...i.data }, i.type === "input" ? (i.data.hasInput = !1, i.data.hasOutput = !0) : i.type === "output" ? (i.data.hasInput = !0, i.data.hasOutput = !1) : (i.data.hasInput = !0, i.data.hasOutput = !0), i.class = "vue-flow__node-default", i.type = "source" in i && i.source === i.target ? "selfloop" : "editable";
305
- return o;
306
- },
307
- set: (o) => {
308
- r("update:modelValue", JSON.parse(JSON.stringify(o)));
309
- }
310
- }), { addEdges: s, removeEdges: g } = ve();
311
- Z(() => {
312
- document.removeEventListener("keypress", $), document.addEventListener("keypress", $);
313
- }), ie(() => {
314
- document.removeEventListener("keypress", $);
315
- });
316
- const m = (o) => {
317
- u.value = o;
318
- };
319
- n.value = a.value;
320
- const f = (o) => ({
321
- [C.Top]: C.Right,
322
- [C.Right]: C.Bottom,
323
- [C.Bottom]: C.Left,
324
- [C.Left]: C.Top
325
- })[o], y = () => {
326
- if (e.value > -1) {
327
- const o = n.value[e.value];
328
- if (!o.sourcePosition) return;
329
- o.sourcePosition = f(o.sourcePosition), N();
330
- }
331
- }, h = () => {
332
- if (e.value > -1) {
333
- const o = n.value[e.value];
334
- if (!o.targetPosition) return;
335
- o.targetPosition = f(o.targetPosition), N();
336
- }
337
- }, k = (o) => {
338
- window.scrollBy(0, o.deltaY);
339
- }, $ = (o) => {
340
- p.value && o.ctrlKey == !0 && ((o.key == "+" || o.key == "=") && u.value?.zoomIn(), o.key == "-" && u.value?.zoomOut());
341
- }, x = async () => {
342
- await u.value?.fitView();
343
- }, j = async () => {
344
- const o = u.value, i = {};
345
- for (const b of n.value) {
346
- if ("source" in b) continue;
347
- const I = o?.findNode?.(b.id)?.dimensions;
348
- I?.width && I?.height && (i[b.id] = { width: I.width, height: I.height });
349
- }
350
- const d = te(n.value, { dimensions: i }), w = /* @__PURE__ */ new Map();
351
- for (const b of d)
352
- "source" in b || w.set(b.id, b.position);
353
- for (const b of n.value) {
354
- if ("source" in b) continue;
355
- const I = w.get(b.id);
356
- I && (b.position = I);
357
- }
358
- N(), await L(), await x();
359
- }, z = () => {
360
- let o = !1, i = { x: Math.random() * 200, y: Math.random() * 200 };
361
- if (e.value > -1) {
362
- const b = n.value[e.value];
363
- b.data?.hasOutput && (i = { x: b.position.x + 200, y: b.position.y + 50 }, o = !0);
364
- }
365
- const d = n.value.length, w = `node-${d}`;
366
- if (n.value.push({
367
- id: w,
368
- label: "Node " + d,
369
- sourcePosition: C.Right,
370
- targetPosition: C.Left,
371
- class: "vue-flow__node-default",
372
- type: "editable",
373
- data: {
374
- hasInput: !0,
375
- hasOutput: !0
376
- },
377
- position: i
378
- }), o) {
379
- const b = `edge-${d + 1}`;
380
- n.value.push({
381
- id: b,
382
- source: t.value,
383
- target: w,
384
- type: "editable",
385
- label: `EDGE ${d + 1}`,
386
- animated: !0
387
- });
388
- }
389
- N();
390
- }, V = (o, i) => {
391
- for (let d = 0; d < n.value.length; d++)
392
- if (n.value[d].id == i) {
393
- n.value[d].label = o;
394
- break;
395
- }
396
- N();
397
- }, ne = ({ node: o }) => {
398
- t.value = o.id;
399
- }, ae = ({ edge: o }) => {
400
- t.value = o.id;
401
- }, le = async (o) => {
402
- const i = n.value.length, d = o.source === o.target, w = {
403
- id: `edge-${i}`,
404
- source: o.source,
405
- target: o.target,
406
- type: d ? "selfloop" : "editable",
407
- label: "New Edge",
408
- interactionWidth: 400,
409
- animated: !0
410
- };
411
- s([w]), await L(), N();
412
- }, se = async (o) => {
413
- g([o.edge.id]), await L(), N();
414
- }, G = async (o) => {
415
- g([o]), await L(), N();
416
- }, N = () => {
417
- r("update:modelValue", JSON.parse(JSON.stringify(n.value)));
418
- };
419
- return (o, i) => (S(), P("div", {
420
- class: ue(["node-editor-wrapper", l.nodeContainerClass]),
421
- onContextmenu: i[1] || (i[1] = D(() => {
422
- }, ["prevent"])),
423
- onMouseover: i[2] || (i[2] = (d) => p.value = !0),
424
- onMouseleave: i[3] || (i[3] = (d) => p.value = !1)
425
- }, [
426
- c("div", Le, [
427
- c("div", xe, [
428
- c("div", null, [
429
- i[4] || (i[4] = c("b", null, "Selected Node:", -1)),
430
- de(" " + F(t.value ? t.value : "none"), 1)
431
- ])
432
- ]),
433
- c("div", Oe, [
434
- c("div", null, [
435
- c("button", {
436
- class: "button-default",
437
- onClick: z
438
- }, "Add Node")
439
- ]),
440
- c("div", null, [
441
- c("button", {
442
- class: "button-default",
443
- onClick: x
444
- }, "Center")
445
- ]),
446
- c("div", null, [
447
- c("button", {
448
- class: "button-default",
449
- onClick: j
450
- }, "Auto-arrange")
451
- ]),
452
- e.value > -1 ? (S(), P("div", Ve, [
453
- c("button", {
454
- class: "button-default",
455
- onClick: h
456
- }, "Shift Input Position")
457
- ])) : B("", !0),
458
- e.value > -1 ? (S(), P("div", Re, [
459
- c("button", {
460
- class: "button-default",
461
- onClick: y
462
- }, "Shift Output Position")
463
- ])) : B("", !0)
464
- ])
465
- ]),
466
- n.value && n.value.length ? (S(), H(_(fe), {
467
- key: 0,
468
- modelValue: n.value,
469
- "onUpdate:modelValue": i[0] || (i[0] = (d) => n.value = d),
470
- class: "nowheel",
471
- "prevent-scrolling": !0,
472
- "zoom-on-scroll": !1,
473
- "fit-view-on-init": !0,
474
- "pan-activation-key-code": null,
475
- onConnect: le,
476
- onPaneReady: m,
477
- onNodeClick: ne,
478
- onEdgeClick: ae,
479
- onEdgeContextMenu: se,
480
- onNodeDragStop: N,
481
- onWheel: D(k, ["prevent"])
482
- }, {
483
- "node-editable": R((d) => [
484
- O($e, A(d, {
485
- onChange: (w) => V(w, d.id)
486
- }), null, 16, ["onChange"])
487
- ]),
488
- "edge-editable": R((d) => [
489
- O(ke, A(d, {
490
- onChange: (w) => V(w, d.id),
491
- onRemove: (w) => G(d.id)
492
- }), null, 16, ["onChange", "onRemove"])
493
- ]),
494
- "edge-selfloop": R((d) => [
495
- O(Ie, A(d, {
496
- onChange: (w) => V(w, d.id),
497
- onRemove: (w) => G(d.id)
498
- }), null, 16, ["onChange", "onRemove"])
499
- ]),
500
- _: 1
501
- }, 8, ["modelValue"])) : B("", !0)
502
- ], 34));
503
- }
389
+ //#endregion
390
+ //#region src/components/NodeEditor.vue?vue&type=script&setup=true&lang.ts
391
+ var _hoisted_1 = { class: "chart-controls" };
392
+ var _hoisted_2 = { class: "chart-controls-left" };
393
+ var _hoisted_3 = { class: "chart-controls-right" };
394
+ var _hoisted_4 = { key: 0 };
395
+ var _hoisted_5 = { key: 1 };
396
+ //#endregion
397
+ //#region src/components/NodeEditor.vue
398
+ var NodeEditor_default = /* @__PURE__ */ defineComponent({
399
+ __name: "NodeEditor",
400
+ props: {
401
+ modelValue: {},
402
+ nodeContainerClass: {
403
+ type: [
404
+ Boolean,
405
+ null,
406
+ String,
407
+ Object,
408
+ Array
409
+ ],
410
+ default: ""
411
+ }
412
+ },
413
+ emits: ["update:modelValue"],
414
+ setup(__props, { emit: __emit }) {
415
+ const emit = __emit;
416
+ const hover = ref(false);
417
+ const vueFlowElements = ref([]);
418
+ const vueFlowInstance = ref();
419
+ const activeElementKey = ref("");
420
+ const activeElementIndex = computed(() => vueFlowElements.value.findIndex((element) => element.id === activeElementKey.value));
421
+ const elements = computed({
422
+ get: () => {
423
+ const items = __props.modelValue;
424
+ for (const element of items) {
425
+ element.data = { ...element.data };
426
+ if (element.type === "input") {
427
+ element.data.hasInput = false;
428
+ element.data.hasOutput = true;
429
+ } else if (element.type === "output") {
430
+ element.data.hasInput = true;
431
+ element.data.hasOutput = false;
432
+ } else {
433
+ element.data.hasInput = true;
434
+ element.data.hasOutput = true;
435
+ }
436
+ element.class = "vue-flow__node-default";
437
+ element.type = "source" in element && element.source === element.target ? "selfloop" : "editable";
438
+ }
439
+ return items;
440
+ },
441
+ set: (newValue) => {
442
+ emit("update:modelValue", JSON.parse(JSON.stringify(newValue)));
443
+ }
444
+ });
445
+ const { addEdges, removeEdges } = useVueFlow();
446
+ onMounted(() => {
447
+ document.removeEventListener("keypress", handleKeypress);
448
+ document.addEventListener("keypress", handleKeypress);
449
+ });
450
+ onBeforeUnmount(() => {
451
+ document.removeEventListener("keypress", handleKeypress);
452
+ });
453
+ const setInstance = (instance) => {
454
+ vueFlowInstance.value = instance;
455
+ };
456
+ vueFlowElements.value = elements.value;
457
+ const shiftTerminal = (currentTerminal) => {
458
+ return {
459
+ [Position$1.Top]: Position$1.Right,
460
+ [Position$1.Right]: Position$1.Bottom,
461
+ [Position$1.Bottom]: Position$1.Left,
462
+ [Position$1.Left]: Position$1.Top
463
+ }[currentTerminal];
464
+ };
465
+ const shiftOutput = () => {
466
+ if (activeElementIndex.value > -1) {
467
+ const activeNode = vueFlowElements.value[activeElementIndex.value];
468
+ if (!activeNode.sourcePosition) return;
469
+ activeNode.sourcePosition = shiftTerminal(activeNode.sourcePosition);
470
+ emitElements();
471
+ }
472
+ };
473
+ const shiftInput = () => {
474
+ if (activeElementIndex.value > -1) {
475
+ const activeNode = vueFlowElements.value[activeElementIndex.value];
476
+ if (!activeNode.targetPosition) return;
477
+ activeNode.targetPosition = shiftTerminal(activeNode.targetPosition);
478
+ emitElements();
479
+ }
480
+ };
481
+ const onWheel = (event) => {
482
+ window.scrollBy(0, event.deltaY);
483
+ };
484
+ const handleKeypress = (event) => {
485
+ if (hover.value && event.ctrlKey == true) {
486
+ if (event.key == "+" || event.key == "=") vueFlowInstance.value?.zoomIn();
487
+ if (event.key == "-") vueFlowInstance.value?.zoomOut();
488
+ }
489
+ };
490
+ const fitView = async () => {
491
+ await vueFlowInstance.value?.fitView();
492
+ };
493
+ const autoArrange = async () => {
494
+ const store = vueFlowInstance.value;
495
+ const dimensions = {};
496
+ for (const el of vueFlowElements.value) {
497
+ if ("source" in el) continue;
498
+ const dims = store?.findNode?.(el.id)?.dimensions;
499
+ if (dims?.width && dims?.height) dimensions[el.id] = {
500
+ width: dims.width,
501
+ height: dims.height
502
+ };
503
+ }
504
+ const laid = autoLayout(vueFlowElements.value, { dimensions });
505
+ const positionById = /* @__PURE__ */ new Map();
506
+ for (const el of laid) {
507
+ if ("source" in el) continue;
508
+ positionById.set(el.id, el.position);
509
+ }
510
+ for (const el of vueFlowElements.value) {
511
+ if ("source" in el) continue;
512
+ const position = positionById.get(el.id);
513
+ if (position) el.position = position;
514
+ }
515
+ emitElements();
516
+ await nextTick();
517
+ await fitView();
518
+ };
519
+ const addNode = () => {
520
+ let makeEdge = false;
521
+ let newNodePosition = {
522
+ x: Math.random() * 200,
523
+ y: Math.random() * 200
524
+ };
525
+ if (activeElementIndex.value > -1) {
526
+ const activeNode = vueFlowElements.value[activeElementIndex.value];
527
+ if (activeNode.data?.hasOutput) {
528
+ newNodePosition = {
529
+ x: activeNode.position.x + 200,
530
+ y: activeNode.position.y + 50
531
+ };
532
+ makeEdge = true;
533
+ }
534
+ }
535
+ const id = vueFlowElements.value.length;
536
+ const nodeId = `node-${id}`;
537
+ vueFlowElements.value.push({
538
+ id: nodeId,
539
+ label: "Node " + id,
540
+ sourcePosition: Position$1.Right,
541
+ targetPosition: Position$1.Left,
542
+ class: "vue-flow__node-default",
543
+ type: "editable",
544
+ data: {
545
+ hasInput: true,
546
+ hasOutput: true
547
+ },
548
+ position: newNodePosition
549
+ });
550
+ if (makeEdge) {
551
+ const edgeId = `edge-${id + 1}`;
552
+ vueFlowElements.value.push({
553
+ id: edgeId,
554
+ source: activeElementKey.value,
555
+ target: nodeId,
556
+ type: "editable",
557
+ label: `EDGE ${id + 1}`,
558
+ animated: true
559
+ });
560
+ }
561
+ emitElements();
562
+ };
563
+ const labelChanged = (event, id) => {
564
+ for (let j = 0; j < vueFlowElements.value.length; j++) if (vueFlowElements.value[j].id == id) {
565
+ vueFlowElements.value[j].label = event;
566
+ break;
567
+ }
568
+ emitElements();
569
+ };
570
+ const handleNodeClick = ({ node }) => {
571
+ activeElementKey.value = node.id;
572
+ };
573
+ const handleEdgeClick = ({ edge }) => {
574
+ activeElementKey.value = edge.id;
575
+ };
576
+ const handleConnect = async (event) => {
577
+ const id = vueFlowElements.value.length;
578
+ const isSelfLoop = event.source === event.target;
579
+ const newEdge = {
580
+ id: `edge-${id}`,
581
+ source: event.source,
582
+ target: event.target,
583
+ type: isSelfLoop ? "selfloop" : "editable",
584
+ label: `New Edge`,
585
+ interactionWidth: 400,
586
+ animated: true
587
+ };
588
+ addEdges([newEdge]);
589
+ await nextTick();
590
+ emitElements();
591
+ };
592
+ const handleEdgeContextMenu = async (event) => {
593
+ removeEdges([event.edge.id]);
594
+ await nextTick();
595
+ emitElements();
596
+ };
597
+ const removeEdge = async (id) => {
598
+ removeEdges([id]);
599
+ await nextTick();
600
+ emitElements();
601
+ };
602
+ const emitElements = () => {
603
+ emit("update:modelValue", JSON.parse(JSON.stringify(vueFlowElements.value)));
604
+ };
605
+ return (_ctx, _cache) => {
606
+ return openBlock(), createElementBlock("div", {
607
+ class: normalizeClass(["node-editor-wrapper", __props.nodeContainerClass]),
608
+ onContextmenu: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"])),
609
+ onMouseover: _cache[2] || (_cache[2] = ($event) => hover.value = true),
610
+ onMouseleave: _cache[3] || (_cache[3] = ($event) => hover.value = false)
611
+ }, [createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [createElementVNode("div", null, [_cache[4] || (_cache[4] = createElementVNode("b", null, "Selected Node:", -1)), createTextVNode(" " + toDisplayString(activeElementKey.value ? activeElementKey.value : "none"), 1)])]), createElementVNode("div", _hoisted_3, [
612
+ createElementVNode("div", null, [createElementVNode("button", {
613
+ class: "button-default",
614
+ onClick: addNode
615
+ }, "Add Node")]),
616
+ createElementVNode("div", null, [createElementVNode("button", {
617
+ class: "button-default",
618
+ onClick: fitView
619
+ }, "Center")]),
620
+ createElementVNode("div", null, [createElementVNode("button", {
621
+ class: "button-default",
622
+ onClick: autoArrange
623
+ }, "Auto-arrange")]),
624
+ activeElementIndex.value > -1 ? (openBlock(), createElementBlock("div", _hoisted_4, [createElementVNode("button", {
625
+ class: "button-default",
626
+ onClick: shiftInput
627
+ }, "Shift Input Position")])) : createCommentVNode("", true),
628
+ activeElementIndex.value > -1 ? (openBlock(), createElementBlock("div", _hoisted_5, [createElementVNode("button", {
629
+ class: "button-default",
630
+ onClick: shiftOutput
631
+ }, "Shift Output Position")])) : createCommentVNode("", true)
632
+ ])]), vueFlowElements.value && vueFlowElements.value.length ? (openBlock(), createBlock(unref(VueFlow), {
633
+ key: 0,
634
+ modelValue: vueFlowElements.value,
635
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vueFlowElements.value = $event),
636
+ class: "nowheel",
637
+ "prevent-scrolling": true,
638
+ "zoom-on-scroll": false,
639
+ "fit-view-on-init": true,
640
+ "pan-activation-key-code": null,
641
+ onConnect: handleConnect,
642
+ onPaneReady: setInstance,
643
+ onNodeClick: handleNodeClick,
644
+ onEdgeClick: handleEdgeClick,
645
+ onEdgeContextMenu: handleEdgeContextMenu,
646
+ onNodeDragStop: emitElements,
647
+ onWheel: withModifiers(onWheel, ["prevent"])
648
+ }, {
649
+ "node-editable": withCtx((props) => [createVNode(EditableNode_default, mergeProps(props, { onChange: ($event) => labelChanged($event, props.id) }), null, 16, ["onChange"])]),
650
+ "edge-editable": withCtx((props) => [createVNode(EditableEdge_default, mergeProps(props, {
651
+ onChange: ($event) => labelChanged($event, props.id),
652
+ onRemove: ($event) => removeEdge(props.id)
653
+ }), null, 16, ["onChange", "onRemove"])]),
654
+ "edge-selfloop": withCtx((props) => [createVNode(SelfLoopEdge_default, mergeProps(props, {
655
+ onChange: ($event) => labelChanged($event, props.id),
656
+ onRemove: ($event) => removeEdge(props.id)
657
+ }), null, 16, ["onChange", "onRemove"])]),
658
+ _: 1
659
+ }, 8, ["modelValue"])) : createCommentVNode("", true)], 34);
660
+ };
661
+ }
504
662
  });
505
- function Me(l, v) {
506
- const { states: r = [], actions: p = {} } = l, n = [], u = [];
507
- for (const [t, e] of Object.entries(p))
508
- if (!(e.stateless || !e.allowedStates?.length)) {
509
- if (e.selfTransition) {
510
- for (const a of e.allowedStates)
511
- n.push({
512
- id: `${t}-${a}`,
513
- source: a,
514
- target: a,
515
- label: e.label ?? t,
516
- data: { actionKey: t },
517
- animated: !0,
518
- type: "selfloop",
519
- interactionWidth: 40
520
- });
521
- continue;
522
- }
523
- if (e.nextState)
524
- for (const a of e.allowedStates)
525
- n.push({
526
- id: `${t}-${a}`,
527
- source: a,
528
- target: e.nextState,
529
- // The edge paints the human display label; its identity (the action key) rides in
530
- // `data.actionKey`, so relabeling the edge renames the action without re-keying it.
531
- label: e.label ?? t,
532
- data: { actionKey: t },
533
- animated: !0,
534
- type: "smoothstep",
535
- interactionWidth: 40
536
- });
537
- }
538
- for (let t = 0; t < r.length; t++) {
539
- const e = r[t], a = {
540
- id: e,
541
- label: e,
542
- position: v?.[e]?.position ?? { x: 200 * t, y: 100 },
543
- targetPosition: v?.[e]?.targetPosition ?? C.Left,
544
- sourcePosition: v?.[e]?.sourcePosition ?? C.Right
545
- };
546
- u.push(a);
547
- }
548
- return [...n, ...u];
663
+ //#endregion
664
+ //#region src/utils/stateTransforms.ts
665
+ function statesToFlowElements(workflow, layout) {
666
+ const { states = [], actions = {} } = workflow;
667
+ const edges = [];
668
+ const nodes = [];
669
+ for (const [actionKey, actionDef] of Object.entries(actions)) {
670
+ if (actionDef.stateless || !actionDef.allowedStates?.length) continue;
671
+ if (actionDef.selfTransition) {
672
+ for (const source of actionDef.allowedStates) edges.push({
673
+ id: `${actionKey}-${source}`,
674
+ source,
675
+ target: source,
676
+ label: actionDef.label ?? actionKey,
677
+ data: { actionKey },
678
+ animated: true,
679
+ type: "selfloop",
680
+ interactionWidth: 40
681
+ });
682
+ continue;
683
+ }
684
+ if (!actionDef.nextState) continue;
685
+ for (const source of actionDef.allowedStates) edges.push({
686
+ id: `${actionKey}-${source}`,
687
+ source,
688
+ target: actionDef.nextState,
689
+ label: actionDef.label ?? actionKey,
690
+ data: { actionKey },
691
+ animated: true,
692
+ type: "smoothstep",
693
+ interactionWidth: 40
694
+ });
695
+ }
696
+ for (let index = 0; index < states.length; index++) {
697
+ const state = states[index];
698
+ const node = {
699
+ id: state,
700
+ label: state,
701
+ position: layout?.[state]?.position ?? {
702
+ x: 200 * index,
703
+ y: 100
704
+ },
705
+ targetPosition: layout?.[state]?.targetPosition ?? Position$1.Left,
706
+ sourcePosition: layout?.[state]?.sourcePosition ?? Position$1.Right
707
+ };
708
+ nodes.push(node);
709
+ }
710
+ return [...edges, ...nodes];
549
711
  }
550
- function _e(l, v) {
551
- const r = {}, p = {}, n = [];
552
- for (const e of l) {
553
- if ("source" in e) continue;
554
- const a = typeof e.label == "string" ? e.label : e.id;
555
- r[e.id] = a, n.push(a), e.position && (p[a] = {
556
- position: e.position,
557
- ...e.targetPosition !== void 0 && { targetPosition: e.targetPosition },
558
- ...e.sourcePosition !== void 0 && { sourcePosition: e.sourcePosition }
559
- });
560
- }
561
- const u = {};
562
- for (const e of l) {
563
- if (!("source" in e)) continue;
564
- const a = typeof e.label == "string" ? e.label : e.id, s = e.data?.actionKey ?? a, g = r[e.source] || e.source, m = r[e.target] || e.target, f = e.source === e.target;
565
- u[s] ? u[s].allowedStates.push(g) : u[s] = {
566
- allowedStates: [g],
567
- label: a,
568
- selfLoop: f,
569
- ...f ? {} : { nextState: m }
570
- };
571
- }
572
- const t = {};
573
- for (const [e, a] of Object.entries(u)) {
574
- const s = v?.actions?.[e];
575
- t[e] = {
576
- // Spread existing first so every field the graph does NOT own — clientHandler,
577
- // requiredFields and any field added to ActionDefinition later — survives the
578
- // round-trip. The graph owns topology (allowedStates/nextState/selfTransition) and the
579
- // display label. (Enumerating named fields here previously dropped clientHandler.)
580
- ...s,
581
- // The edge's label is the display name (decoupled from the key). Fall back to the
582
- // existing label, then the key, if an edge ever arrives label-less.
583
- label: a.label || s?.label || e,
584
- // The graph owns topology only. A self-transition (self-loop) stays in place: mark
585
- // `selfTransition`, carry NO `nextState`. A cross-state transition carries `nextState`
586
- // and clears any stale self flag (a self-loop redrawn as a normal edge). Explicit
587
- // `undefined` on the unused field is dropped by JSON.stringify, so no format churn.
588
- allowedStates: a.allowedStates,
589
- nextState: a.selfLoop ? void 0 : a.nextState,
590
- selfTransition: a.selfLoop ? !0 : void 0
591
- };
592
- }
593
- for (const [e, a] of Object.entries(v?.actions ?? {}))
594
- e in u || (a.stateless || !a.allowedStates?.length) && (t[e] = a);
595
- return {
596
- // Spread existingWorkflow first so every top-level key the graph does NOT own — the
597
- // sibling `triggers` map (field-validation triggers), and any WorkflowMeta key added
598
- // later — survives the round-trip. The graph owns topology only: states + actions,
599
- // overridden below. (Same principle the per-action spread applies above; enumerating
600
- // only states+actions here previously dropped `triggers` on every graph edit.)
601
- workflow: { ...v, states: n, actions: t },
602
- layout: p
603
- };
712
+ function flowElementsToStates(nextElements, existingWorkflow) {
713
+ const idToLabel = {};
714
+ const nextLayout = {};
715
+ const stateNames = [];
716
+ for (const el of nextElements) {
717
+ if ("source" in el) continue;
718
+ const label = typeof el.label === "string" ? el.label : el.id;
719
+ idToLabel[el.id] = label;
720
+ stateNames.push(label);
721
+ if (el.position) nextLayout[label] = {
722
+ position: el.position,
723
+ ...el.targetPosition !== void 0 && { targetPosition: el.targetPosition },
724
+ ...el.sourcePosition !== void 0 && { sourcePosition: el.sourcePosition }
725
+ };
726
+ }
727
+ const transitionGroups = {};
728
+ for (const el of nextElements) {
729
+ if (!("source" in el)) continue;
730
+ const edgeLabel = typeof el.label === "string" ? el.label : el.id;
731
+ const actionKey = el.data?.actionKey ?? edgeLabel;
732
+ const sourceLabel = idToLabel[el.source] || el.source;
733
+ const targetLabel = idToLabel[el.target] || el.target;
734
+ const isSelf = el.source === el.target;
735
+ if (!transitionGroups[actionKey]) transitionGroups[actionKey] = {
736
+ allowedStates: [sourceLabel],
737
+ label: edgeLabel,
738
+ selfLoop: isSelf,
739
+ ...isSelf ? {} : { nextState: targetLabel }
740
+ };
741
+ else transitionGroups[actionKey].allowedStates.push(sourceLabel);
742
+ }
743
+ const nextActions = {};
744
+ for (const [actionKey, group] of Object.entries(transitionGroups)) {
745
+ const existing = existingWorkflow?.actions?.[actionKey];
746
+ nextActions[actionKey] = {
747
+ ...existing,
748
+ label: group.label || existing?.label || actionKey,
749
+ allowedStates: group.allowedStates,
750
+ nextState: group.selfLoop ? void 0 : group.nextState,
751
+ selfTransition: group.selfLoop ? true : void 0
752
+ };
753
+ }
754
+ for (const [actionKey, actionDef] of Object.entries(existingWorkflow?.actions ?? {})) {
755
+ if (actionKey in transitionGroups) continue;
756
+ if (actionDef.stateless || !actionDef.allowedStates?.length) nextActions[actionKey] = actionDef;
757
+ }
758
+ return {
759
+ workflow: {
760
+ ...existingWorkflow,
761
+ states: stateNames,
762
+ actions: nextActions
763
+ },
764
+ layout: nextLayout
765
+ };
604
766
  }
605
- const Te = /* @__PURE__ */ T({
606
- __name: "StateEditor",
607
- props: /* @__PURE__ */ re({
608
- nodeContainerClass: { type: [Boolean, null, String, Object, Array], default: "" }
609
- }, {
610
- modelValue: {},
611
- modelModifiers: {},
612
- layout: {},
613
- layoutModifiers: {}
614
- }),
615
- emits: ["update:modelValue", "update:layout"],
616
- setup(l) {
617
- const v = J(l, "modelValue"), r = J(l, "layout");
618
- Z(() => {
619
- r.value === void 0 && console.warn("[StateEditor] v-model:layout is not bound. Node position changes will not be persisted.");
620
- });
621
- const p = M({
622
- get: () => {
623
- if (!v.value) return [];
624
- const n = Me(v.value, r.value);
625
- return !r.value || Object.keys(r.value).length === 0 ? te(n) : n;
626
- },
627
- set: (n) => {
628
- const { workflow: u, layout: t } = _e(n, v.value);
629
- v.value = u, r.value !== void 0 && (r.value = t);
630
- }
631
- });
632
- return (n, u) => (S(), P("div", null, [
633
- O(oe, {
634
- modelValue: p.value,
635
- "onUpdate:modelValue": u[0] || (u[0] = (t) => p.value = t),
636
- "node-container-class": l.nodeContainerClass
637
- }, null, 8, ["modelValue", "node-container-class"])
638
- ]));
639
- }
767
+ //#endregion
768
+ //#region src/components/StateEditor.vue
769
+ var StateEditor_default = /* @__PURE__ */ defineComponent({
770
+ __name: "StateEditor",
771
+ props: /*@__PURE__*/ mergeModels({ nodeContainerClass: {
772
+ type: [
773
+ Boolean,
774
+ null,
775
+ String,
776
+ Object,
777
+ Array
778
+ ],
779
+ default: ""
780
+ } }, {
781
+ "modelValue": {},
782
+ "modelModifiers": {},
783
+ "layout": {},
784
+ "layoutModifiers": {}
785
+ }),
786
+ emits: ["update:modelValue", "update:layout"],
787
+ setup(__props) {
788
+ const workflow = useModel(__props, "modelValue");
789
+ const layout = useModel(__props, "layout");
790
+ onMounted(() => {
791
+ if (layout.value === void 0) console.warn("[StateEditor] v-model:layout is not bound. Node position changes will not be persisted.");
792
+ });
793
+ const elements = computed({
794
+ get: () => {
795
+ if (!workflow.value) return [];
796
+ const els = statesToFlowElements(workflow.value, layout.value);
797
+ return !layout.value || Object.keys(layout.value).length === 0 ? autoLayout(els) : els;
798
+ },
799
+ set: (newValue) => {
800
+ const { workflow: nextWorkflow, layout: nextLayout } = flowElementsToStates(newValue, workflow.value);
801
+ workflow.value = nextWorkflow;
802
+ if (layout.value !== void 0) layout.value = nextLayout;
803
+ }
804
+ });
805
+ return (_ctx, _cache) => {
806
+ return openBlock(), createElementBlock("div", null, [createVNode(NodeEditor_default, {
807
+ modelValue: elements.value,
808
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => elements.value = $event),
809
+ "node-container-class": __props.nodeContainerClass
810
+ }, null, 8, ["modelValue", "node-container-class"])]);
811
+ };
812
+ }
640
813
  });
641
- function je(l) {
642
- l.component("NodeEditor", oe), l.component("StateEditor", Te);
814
+ //#endregion
815
+ //#region src/index.ts
816
+ /**
817
+ * Install all Node Editor components
818
+ * @param app - Vue app instance
819
+ * @public
820
+ */
821
+ function install(app) {
822
+ app.component("NodeEditor", NodeEditor_default);
823
+ app.component("StateEditor", StateEditor_default);
643
824
  }
644
- export {
645
- oe as NodeEditor,
646
- Te as StateEditor,
647
- je as install
648
- };
649
- //# sourceMappingURL=node-editor.js.map
825
+ //#endregion
826
+ export { NodeEditor_default as NodeEditor, Position, StateEditor_default as StateEditor, install };
827
+
828
+ //# sourceMappingURL=node-editor.js.map