@particle-academy/fancy-flow 0.7.0 → 0.9.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.
- package/README.md +93 -0
- package/dist/{chunk-BCXECQUC.js → chunk-6GGFEZH7.js} +3 -3
- package/dist/{chunk-BCXECQUC.js.map → chunk-6GGFEZH7.js.map} +1 -1
- package/dist/{chunk-M2XKGQQL.js → chunk-6RHAQ2LP.js} +228 -17
- package/dist/chunk-6RHAQ2LP.js.map +1 -0
- package/dist/{chunk-WNVBXXOL.js → chunk-CPSOC27D.js} +43 -2
- package/dist/chunk-CPSOC27D.js.map +1 -0
- package/dist/{chunk-NVULCEDX.js → chunk-HNBO4HP3.js} +8 -4
- package/dist/chunk-HNBO4HP3.js.map +1 -0
- package/dist/chunk-TITD5W4Y.js +26 -0
- package/dist/chunk-TITD5W4Y.js.map +1 -0
- package/dist/{chunk-QSSQRQN4.js → chunk-VEI743ZX.js} +3 -3
- package/dist/{chunk-QSSQRQN4.js.map → chunk-VEI743ZX.js.map} +1 -1
- package/dist/engine.cjs +32 -2
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.js +3 -1
- package/dist/index.cjs +717 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -6
- package/dist/index.d.ts +45 -6
- package/dist/index.js +415 -23
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.cts +111 -4
- package/dist/registry/index.d.ts +111 -4
- package/dist/registry.cjs +313 -31
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +3 -2
- package/dist/runtime.cjs +32 -2
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +4 -2
- package/dist/schema.cjs +41 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/styles.css +191 -0
- package/dist/styles.css.map +1 -1
- package/dist/types-BocBFh6l.d.ts +221 -0
- package/dist/types-DKqaUjF_.d.cts +221 -0
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.d.cts +1 -1
- package/dist/ux.d.ts +1 -1
- package/dist/ux.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-M2XKGQQL.js.map +0 -1
- package/dist/chunk-NVULCEDX.js.map +0 -1
- package/dist/chunk-WNVBXXOL.js.map +0 -1
- package/dist/types-C0wdN6QX.d.cts +0 -121
- package/dist/types-DnMe9Vsf.d.ts +0 -121
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,8 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
|
|
9
9
|
var ReactExports__default = /*#__PURE__*/_interopDefault(ReactExports);
|
|
10
10
|
|
|
11
|
+
// src/registry/builtin.ts
|
|
12
|
+
|
|
11
13
|
// src/registry/registry.ts
|
|
12
14
|
var kinds = /* @__PURE__ */ new Map();
|
|
13
15
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -81,6 +83,47 @@ function validateField(field, value) {
|
|
|
81
83
|
case "json":
|
|
82
84
|
return null;
|
|
83
85
|
// permissive — just JSON-shaped
|
|
86
|
+
case "repeater": {
|
|
87
|
+
if (!Array.isArray(value)) return `${field.label} must be a list`;
|
|
88
|
+
if (field.minItems !== void 0 && value.length < field.minItems) {
|
|
89
|
+
return `${field.label} needs at least ${field.minItems}`;
|
|
90
|
+
}
|
|
91
|
+
if (field.maxItems !== void 0 && value.length > field.maxItems) {
|
|
92
|
+
return `${field.label} allows at most ${field.maxItems}`;
|
|
93
|
+
}
|
|
94
|
+
for (let i = 0; i < value.length; i++) {
|
|
95
|
+
const row = value[i];
|
|
96
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
97
|
+
return `${field.label} item ${i + 1} must be an object`;
|
|
98
|
+
}
|
|
99
|
+
for (const sub of field.fields) {
|
|
100
|
+
const cell = row[sub.key];
|
|
101
|
+
if (sub.required && (cell === void 0 || cell === null || cell === "")) {
|
|
102
|
+
return `${field.label} item ${i + 1}: ${sub.label} is required`;
|
|
103
|
+
}
|
|
104
|
+
if (cell === void 0 || cell === null) continue;
|
|
105
|
+
const issue = validateField(sub, cell);
|
|
106
|
+
if (issue) return `${field.label} item ${i + 1}: ${issue}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
case "keyvalue": {
|
|
112
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
113
|
+
return `${field.label} must be a key/value map`;
|
|
114
|
+
}
|
|
115
|
+
const allowed = field.valueOptions?.map((o) => o.value);
|
|
116
|
+
for (const [k, v] of Object.entries(value)) {
|
|
117
|
+
if (typeof v !== "string") return `${field.label}: "${k}" must be a string`;
|
|
118
|
+
if (allowed && !allowed.includes(v)) {
|
|
119
|
+
return `${field.label}: "${k}" must be one of ${allowed.join(", ")}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
case "document":
|
|
125
|
+
return null;
|
|
126
|
+
// opaque to fancy-flow — the host's editor owns its shape
|
|
84
127
|
default:
|
|
85
128
|
return null;
|
|
86
129
|
}
|
|
@@ -105,8 +148,91 @@ function categoryAccent(category) {
|
|
|
105
148
|
return "#71717a";
|
|
106
149
|
}
|
|
107
150
|
}
|
|
151
|
+
var adapter = null;
|
|
152
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
153
|
+
function registerRichInputAdapter(next) {
|
|
154
|
+
adapter = next;
|
|
155
|
+
for (const l of listeners2) l();
|
|
156
|
+
return () => {
|
|
157
|
+
if (adapter === next) {
|
|
158
|
+
adapter = null;
|
|
159
|
+
for (const l of listeners2) l();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function getRichInputAdapter() {
|
|
164
|
+
return adapter;
|
|
165
|
+
}
|
|
166
|
+
function isRichInputEnabled() {
|
|
167
|
+
return adapter !== null && (adapter.renderDocument !== void 0 || adapter.renderEditor !== void 0);
|
|
168
|
+
}
|
|
169
|
+
function onRichInputAdapterChanged(fn) {
|
|
170
|
+
listeners2.add(fn);
|
|
171
|
+
return () => listeners2.delete(fn);
|
|
172
|
+
}
|
|
173
|
+
function RichInputPreview({ config }) {
|
|
174
|
+
const a = getRichInputAdapter();
|
|
175
|
+
const title = typeof config.title === "string" && config.title.trim() !== "" ? config.title : "Untitled step";
|
|
176
|
+
const doc = config.document;
|
|
177
|
+
if (!a || !a.renderDocument && !a.renderEditor) {
|
|
178
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview__unavailable", children: [
|
|
181
|
+
"Needs a document adapter. Install",
|
|
182
|
+
" ",
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/fancy-cms-ui" }),
|
|
184
|
+
" +",
|
|
185
|
+
" ",
|
|
186
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/react-fancy" }),
|
|
187
|
+
", then call",
|
|
188
|
+
" ",
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "registerRichInputAdapter()" }),
|
|
190
|
+
"."
|
|
191
|
+
] })
|
|
192
|
+
] });
|
|
193
|
+
}
|
|
194
|
+
const body = doc === void 0 || doc === null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ff-rich-preview__unavailable", children: "Nothing authored yet." }) : a.renderDocument?.(doc) ?? null;
|
|
195
|
+
const Frame = a.FauxClient;
|
|
196
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-rich-preview__frame", children: Frame ? /* @__PURE__ */ jsxRuntime.jsx(Frame, { variant: "browser", children: body }) : body })
|
|
199
|
+
] });
|
|
200
|
+
}
|
|
108
201
|
|
|
109
202
|
// src/registry/builtin.ts
|
|
203
|
+
function casePorts(cases) {
|
|
204
|
+
const byPort = /* @__PURE__ */ new Map();
|
|
205
|
+
if (cases && typeof cases === "object" && !Array.isArray(cases)) {
|
|
206
|
+
for (const [match, port] of Object.entries(cases)) {
|
|
207
|
+
if (typeof port !== "string" || port === "" || port === "default") continue;
|
|
208
|
+
const matches = byPort.get(port) ?? [];
|
|
209
|
+
matches.push(match);
|
|
210
|
+
byPort.set(port, matches);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const ports = [...byPort].map(([id2, matches]) => ({
|
|
214
|
+
id: id2,
|
|
215
|
+
label: matches.join("|")
|
|
216
|
+
}));
|
|
217
|
+
return [...ports, { id: "default", label: "default" }];
|
|
218
|
+
}
|
|
219
|
+
function routePorts(routes, fallback) {
|
|
220
|
+
const ports = [];
|
|
221
|
+
const seen = /* @__PURE__ */ new Set();
|
|
222
|
+
if (Array.isArray(routes)) {
|
|
223
|
+
for (const route of routes) {
|
|
224
|
+
const id2 = route?.port;
|
|
225
|
+
if (typeof id2 !== "string" || id2.trim() === "" || seen.has(id2)) continue;
|
|
226
|
+
seen.add(id2);
|
|
227
|
+
ports.push({ id: id2, label: id2 });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (fallback !== false && !seen.has("fallback")) {
|
|
231
|
+
ports.push({ id: "fallback", label: "fallback" });
|
|
232
|
+
}
|
|
233
|
+
if (ports.length === 0) ports.push({ id: "out" });
|
|
234
|
+
return ports;
|
|
235
|
+
}
|
|
110
236
|
var HTTP_METHODS = [
|
|
111
237
|
{ type: "select", key: "method", label: "Method", options: [
|
|
112
238
|
{ value: "GET", label: "GET" },
|
|
@@ -175,15 +301,59 @@ var KINDS = [
|
|
|
175
301
|
configSchema: [
|
|
176
302
|
{ type: "text", key: "title", label: "Form title", default: "Need your input" },
|
|
177
303
|
{
|
|
178
|
-
type: "
|
|
304
|
+
type: "repeater",
|
|
179
305
|
key: "fields",
|
|
180
|
-
label: "Fields
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
306
|
+
label: "Fields",
|
|
307
|
+
description: "The form the run pauses on.",
|
|
308
|
+
titleKey: "label",
|
|
309
|
+
addLabel: "Add field",
|
|
310
|
+
minItems: 1,
|
|
311
|
+
fields: [
|
|
312
|
+
{ type: "text", key: "key", label: "Key", required: true, placeholder: "answer" },
|
|
313
|
+
{ type: "text", key: "label", label: "Label", required: true, placeholder: "Your answer" },
|
|
314
|
+
{
|
|
315
|
+
type: "select",
|
|
316
|
+
key: "type",
|
|
317
|
+
label: "Type",
|
|
318
|
+
default: "text",
|
|
319
|
+
options: [
|
|
320
|
+
{ value: "text", label: "Text" },
|
|
321
|
+
{ value: "textarea", label: "Long text" },
|
|
322
|
+
{ value: "number", label: "Number" },
|
|
323
|
+
{ value: "select", label: "Select" },
|
|
324
|
+
{ value: "switch", label: "Switch" }
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
{ type: "switch", key: "required", label: "Required", default: false }
|
|
328
|
+
],
|
|
329
|
+
default: [{ key: "answer", label: "Your answer", type: "textarea", required: true }]
|
|
184
330
|
}
|
|
185
331
|
]
|
|
186
332
|
},
|
|
333
|
+
{
|
|
334
|
+
name: "rich_user_input",
|
|
335
|
+
category: "human",
|
|
336
|
+
label: "Rich User Input",
|
|
337
|
+
description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
|
|
338
|
+
icon: "\u25A4",
|
|
339
|
+
inputs: [{ id: "in" }],
|
|
340
|
+
outputs: [{ id: "out", label: "values" }],
|
|
341
|
+
configSchema: [
|
|
342
|
+
{ type: "text", key: "title", label: "Step title", default: "Please review" },
|
|
343
|
+
{
|
|
344
|
+
type: "document",
|
|
345
|
+
key: "document",
|
|
346
|
+
label: "Page content",
|
|
347
|
+
documentType: "stages",
|
|
348
|
+
description: "Authored with the host's document editor (fancy-cms Stages)."
|
|
349
|
+
},
|
|
350
|
+
{ type: "switch", key: "requireConfirm", label: "Require explicit confirmation", default: true },
|
|
351
|
+
{ type: "text", key: "submitLabel", label: "Submit button", default: "Continue" }
|
|
352
|
+
],
|
|
353
|
+
// Preview the authored page inside a FauxClient frame, so the canvas shows
|
|
354
|
+
// what the person hitting this step will actually see.
|
|
355
|
+
renderBody: (ctx) => ReactExports.createElement(RichInputPreview, { config: ctx.config ?? {} })
|
|
356
|
+
},
|
|
187
357
|
// ───────────── Logic ─────────────
|
|
188
358
|
{
|
|
189
359
|
name: "branch",
|
|
@@ -204,10 +374,24 @@ var KINDS = [
|
|
|
204
374
|
description: "Route to one of N labelled outputs based on a key.",
|
|
205
375
|
icon: "\u2933",
|
|
206
376
|
inputs: [{ id: "in" }],
|
|
207
|
-
|
|
377
|
+
// Ports ARE the config: every distinct port a case routes to becomes an
|
|
378
|
+
// output handle, plus the always-present `default`. Editing the cases map
|
|
379
|
+
// moves the ports on the canvas and the ports the runtime activates.
|
|
380
|
+
outputs: (config) => casePorts(config?.cases),
|
|
208
381
|
configSchema: [
|
|
209
382
|
{ type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
|
|
210
|
-
{
|
|
383
|
+
{
|
|
384
|
+
type: "keyvalue",
|
|
385
|
+
key: "cases",
|
|
386
|
+
label: "Cases",
|
|
387
|
+
description: "Match value \u2192 output port. Unmatched input takes `default`.",
|
|
388
|
+
keyLabel: "When value is",
|
|
389
|
+
valueLabel: "Route to port",
|
|
390
|
+
keyPlaceholder: "a",
|
|
391
|
+
valuePlaceholder: "case_a",
|
|
392
|
+
addLabel: "Add case",
|
|
393
|
+
default: { a: "case_a", b: "case_b" }
|
|
394
|
+
}
|
|
211
395
|
]
|
|
212
396
|
},
|
|
213
397
|
{
|
|
@@ -362,6 +546,76 @@ var KINDS = [
|
|
|
362
546
|
{ type: "credential", key: "credential", label: "API credential", credentialType: "llm_credential" }
|
|
363
547
|
]
|
|
364
548
|
},
|
|
549
|
+
{
|
|
550
|
+
name: "llm_branch",
|
|
551
|
+
category: "ai",
|
|
552
|
+
label: "LLM Router",
|
|
553
|
+
description: "Let a model choose which route the flow takes.",
|
|
554
|
+
icon: "\u2727",
|
|
555
|
+
inputs: [{ id: "in" }],
|
|
556
|
+
// Each declared route is a port. The executor returns `{ __port: id }`
|
|
557
|
+
// (or `Port.only(id)` on the PHP runtime) to pick one.
|
|
558
|
+
outputs: (config) => routePorts(config?.routes, config?.fallback),
|
|
559
|
+
configSchema: [
|
|
560
|
+
{
|
|
561
|
+
type: "textarea",
|
|
562
|
+
key: "system",
|
|
563
|
+
label: "System prompt",
|
|
564
|
+
rows: 3,
|
|
565
|
+
description: "Optional framing for the routing decision."
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
type: "expression",
|
|
569
|
+
key: "prompt",
|
|
570
|
+
label: "What to route on",
|
|
571
|
+
example: "{{ $json.message }}",
|
|
572
|
+
required: true
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
type: "repeater",
|
|
576
|
+
key: "routes",
|
|
577
|
+
label: "Routes",
|
|
578
|
+
description: "The model picks exactly one. Descriptions are what it chooses between \u2014 make them distinct.",
|
|
579
|
+
titleKey: "port",
|
|
580
|
+
addLabel: "Add route",
|
|
581
|
+
minItems: 2,
|
|
582
|
+
fields: [
|
|
583
|
+
{ type: "text", key: "port", label: "Port", required: true, placeholder: "billing" },
|
|
584
|
+
{
|
|
585
|
+
type: "text",
|
|
586
|
+
key: "description",
|
|
587
|
+
label: "When to choose it",
|
|
588
|
+
required: true,
|
|
589
|
+
placeholder: "The user is asking about an invoice, refund, or payment."
|
|
590
|
+
}
|
|
591
|
+
],
|
|
592
|
+
default: [
|
|
593
|
+
{ port: "a", description: "Describe when the model should pick this route." },
|
|
594
|
+
{ port: "b", description: "Describe when the model should pick this route." }
|
|
595
|
+
]
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
type: "select",
|
|
599
|
+
key: "provider",
|
|
600
|
+
label: "Provider",
|
|
601
|
+
default: "anthropic",
|
|
602
|
+
options: [
|
|
603
|
+
{ value: "anthropic", label: "Anthropic" },
|
|
604
|
+
{ value: "openai", label: "OpenAI" },
|
|
605
|
+
{ value: "custom", label: "Custom" }
|
|
606
|
+
]
|
|
607
|
+
},
|
|
608
|
+
{ type: "text", key: "model", label: "Model", placeholder: "claude-sonnet-4-5" },
|
|
609
|
+
{
|
|
610
|
+
type: "switch",
|
|
611
|
+
key: "fallback",
|
|
612
|
+
label: "Add a `fallback` port",
|
|
613
|
+
default: true,
|
|
614
|
+
description: "Where the flow goes if the model returns no usable route."
|
|
615
|
+
},
|
|
616
|
+
{ type: "credential", key: "credential", label: "API credential", credentialType: "llm_credential" }
|
|
617
|
+
]
|
|
618
|
+
},
|
|
365
619
|
{
|
|
366
620
|
name: "tool_use",
|
|
367
621
|
category: "ai",
|
|
@@ -1474,7 +1728,7 @@ function defaultTouchable() {
|
|
|
1474
1728
|
return navigator.maxTouchPoints || "ontouchstart" in this;
|
|
1475
1729
|
}
|
|
1476
1730
|
function drag_default() {
|
|
1477
|
-
var filter2 = defaultFilter, container = defaultContainer, subject = defaultSubject, touchable = defaultTouchable, gestures = {},
|
|
1731
|
+
var filter2 = defaultFilter, container = defaultContainer, subject = defaultSubject, touchable = defaultTouchable, gestures = {}, listeners3 = dispatch_default("start", "drag", "end"), active = 0, mousedownx, mousedowny, mousemoving, touchending, clickDistance2 = 0;
|
|
1478
1732
|
function drag(selection2) {
|
|
1479
1733
|
selection2.on("mousedown.drag", mousedowned).filter(touchable).on("touchstart.drag", touchstarted).on("touchmove.drag", touchmoved, nonpassive).on("touchend.drag touchcancel.drag", touchended).style("touch-action", "none").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
|
|
1480
1734
|
}
|
|
@@ -1537,7 +1791,7 @@ function drag_default() {
|
|
|
1537
1791
|
}
|
|
1538
1792
|
}
|
|
1539
1793
|
function beforestart(that, container2, event, d, identifier, touch) {
|
|
1540
|
-
var dispatch2 =
|
|
1794
|
+
var dispatch2 = listeners3.copy(), p = pointer_default(touch || event, container2), dx, dy, s;
|
|
1541
1795
|
if ((s = subject.call(that, new DragEvent("beforestart", {
|
|
1542
1796
|
sourceEvent: event,
|
|
1543
1797
|
target: drag,
|
|
@@ -1596,8 +1850,8 @@ function drag_default() {
|
|
|
1596
1850
|
return arguments.length ? (touchable = typeof _ === "function" ? _ : constant_default2(!!_), drag) : touchable;
|
|
1597
1851
|
};
|
|
1598
1852
|
drag.on = function() {
|
|
1599
|
-
var value =
|
|
1600
|
-
return value ===
|
|
1853
|
+
var value = listeners3.on.apply(listeners3, arguments);
|
|
1854
|
+
return value === listeners3 ? drag : value;
|
|
1601
1855
|
};
|
|
1602
1856
|
drag.clickDistance = function(_) {
|
|
1603
1857
|
return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2);
|
|
@@ -3151,7 +3405,7 @@ function defaultConstrain(transform2, extent, translateExtent) {
|
|
|
3151
3405
|
);
|
|
3152
3406
|
}
|
|
3153
3407
|
function zoom_default2() {
|
|
3154
|
-
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate = zoom_default,
|
|
3408
|
+
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate = zoom_default, listeners3 = dispatch_default("start", "zoom", "end"), touchstarting, touchfirst, touchending, touchDelay = 500, wheelDelay = 150, clickDistance2 = 0, tapDistance = 10;
|
|
3155
3409
|
function zoom(selection2) {
|
|
3156
3410
|
selection2.property("__zoom", defaultTransform).on("wheel.zoom", wheeled, { passive: false }).on("mousedown.zoom", mousedowned).on("dblclick.zoom", dblclicked).filter(touchable).on("touchstart.zoom", touchstarted).on("touchmove.zoom", touchmoved).on("touchend.zoom touchcancel.zoom", touchended).style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
|
|
3157
3411
|
}
|
|
@@ -3263,14 +3517,14 @@ function zoom_default2() {
|
|
|
3263
3517
|
},
|
|
3264
3518
|
emit: function(type) {
|
|
3265
3519
|
var d = select_default2(this.that).datum();
|
|
3266
|
-
|
|
3520
|
+
listeners3.call(
|
|
3267
3521
|
type,
|
|
3268
3522
|
this.that,
|
|
3269
3523
|
new ZoomEvent(type, {
|
|
3270
3524
|
sourceEvent: this.sourceEvent,
|
|
3271
3525
|
target: zoom,
|
|
3272
3526
|
transform: this.that.__zoom,
|
|
3273
|
-
dispatch:
|
|
3527
|
+
dispatch: listeners3
|
|
3274
3528
|
}),
|
|
3275
3529
|
d
|
|
3276
3530
|
);
|
|
@@ -3420,8 +3674,8 @@ function zoom_default2() {
|
|
|
3420
3674
|
return arguments.length ? (interpolate = _, zoom) : interpolate;
|
|
3421
3675
|
};
|
|
3422
3676
|
zoom.on = function() {
|
|
3423
|
-
var value =
|
|
3424
|
-
return value ===
|
|
3677
|
+
var value = listeners3.on.apply(listeners3, arguments);
|
|
3678
|
+
return value === listeners3 ? zoom : value;
|
|
3425
3679
|
};
|
|
3426
3680
|
zoom.clickDistance = function(_) {
|
|
3427
3681
|
return arguments.length ? (clickDistance2 = (_ = +_) * _, zoom) : Math.sqrt(clickDistance2);
|
|
@@ -6094,20 +6348,20 @@ var use_sync_external_store_with_selector_default = { useSyncExternalStoreWithSe
|
|
|
6094
6348
|
// node_modules/zustand/esm/vanilla.mjs
|
|
6095
6349
|
var createStoreImpl = (createState) => {
|
|
6096
6350
|
let state;
|
|
6097
|
-
const
|
|
6351
|
+
const listeners3 = /* @__PURE__ */ new Set();
|
|
6098
6352
|
const setState = (partial, replace) => {
|
|
6099
6353
|
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
6100
6354
|
if (!Object.is(nextState, state)) {
|
|
6101
6355
|
const previousState = state;
|
|
6102
6356
|
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
6103
|
-
|
|
6357
|
+
listeners3.forEach((listener) => listener(state, previousState));
|
|
6104
6358
|
}
|
|
6105
6359
|
};
|
|
6106
6360
|
const getState = () => state;
|
|
6107
6361
|
const getInitialState2 = () => initialState;
|
|
6108
6362
|
const subscribe = (listener) => {
|
|
6109
|
-
|
|
6110
|
-
return () =>
|
|
6363
|
+
listeners3.add(listener);
|
|
6364
|
+
return () => listeners3.delete(listener);
|
|
6111
6365
|
};
|
|
6112
6366
|
const destroy = () => {
|
|
6113
6367
|
if ((undefined ? undefined.MODE : void 0) !== "production") {
|
|
@@ -6115,7 +6369,7 @@ var createStoreImpl = (createState) => {
|
|
|
6115
6369
|
"[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
|
|
6116
6370
|
);
|
|
6117
6371
|
}
|
|
6118
|
-
|
|
6372
|
+
listeners3.clear();
|
|
6119
6373
|
};
|
|
6120
6374
|
const api = { setState, getState, getInitialState: getInitialState2, subscribe, destroy };
|
|
6121
6375
|
const initialState = state = createState(setState, getState, api);
|
|
@@ -9647,9 +9901,18 @@ function paletteDropHandlers(onDrop) {
|
|
|
9647
9901
|
}
|
|
9648
9902
|
};
|
|
9649
9903
|
}
|
|
9650
|
-
function ConfigFieldRenderer({
|
|
9904
|
+
function ConfigFieldRenderer({
|
|
9905
|
+
field,
|
|
9906
|
+
value,
|
|
9907
|
+
onChange,
|
|
9908
|
+
renderCredentialField,
|
|
9909
|
+
renderDocumentField
|
|
9910
|
+
}) {
|
|
9651
9911
|
switch (field.type) {
|
|
9652
|
-
case "text":
|
|
9912
|
+
case "text": {
|
|
9913
|
+
if (field.choices?.length) {
|
|
9914
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChoiceField, { field, value, onChange });
|
|
9915
|
+
}
|
|
9653
9916
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9654
9917
|
"input",
|
|
9655
9918
|
{
|
|
@@ -9660,6 +9923,7 @@ function ConfigFieldRenderer({ field, value, onChange, renderCredentialField })
|
|
|
9660
9923
|
onChange: (e) => onChange(e.target.value)
|
|
9661
9924
|
}
|
|
9662
9925
|
);
|
|
9926
|
+
}
|
|
9663
9927
|
case "textarea":
|
|
9664
9928
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9665
9929
|
"textarea",
|
|
@@ -9737,10 +10001,244 @@ function ConfigFieldRenderer({ field, value, onChange, renderCredentialField })
|
|
|
9737
10001
|
onChange: (e) => onChange(e.target.value)
|
|
9738
10002
|
}
|
|
9739
10003
|
);
|
|
10004
|
+
case "repeater":
|
|
10005
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
10006
|
+
RepeaterField,
|
|
10007
|
+
{
|
|
10008
|
+
field,
|
|
10009
|
+
value,
|
|
10010
|
+
onChange,
|
|
10011
|
+
renderCredentialField,
|
|
10012
|
+
renderDocumentField
|
|
10013
|
+
}
|
|
10014
|
+
);
|
|
10015
|
+
case "keyvalue":
|
|
10016
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KeyValueField, { field, value, onChange });
|
|
10017
|
+
case "document":
|
|
10018
|
+
if (renderDocumentField) {
|
|
10019
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderDocumentField({ documentType: field.documentType, value, onChange }) });
|
|
10020
|
+
}
|
|
10021
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "ff-panel__hint ff-panel__hint--missing", children: [
|
|
10022
|
+
"No document editor supplied. Pass ",
|
|
10023
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "renderDocumentField" }),
|
|
10024
|
+
" to NodeConfigPanel to author this field."
|
|
10025
|
+
] });
|
|
9740
10026
|
default:
|
|
9741
10027
|
return null;
|
|
9742
10028
|
}
|
|
9743
10029
|
}
|
|
10030
|
+
function normalizeChoices(choices) {
|
|
10031
|
+
return choices.map(
|
|
10032
|
+
(c) => typeof c === "string" ? { value: c, label: c } : { value: c.value, label: c.label ?? c.value }
|
|
10033
|
+
);
|
|
10034
|
+
}
|
|
10035
|
+
function ChoiceField({
|
|
10036
|
+
field,
|
|
10037
|
+
value,
|
|
10038
|
+
onChange
|
|
10039
|
+
}) {
|
|
10040
|
+
const options = normalizeChoices(field.choices ?? []);
|
|
10041
|
+
const current = typeof value === "string" ? value : "";
|
|
10042
|
+
const known = options.some((o) => o.value === current);
|
|
10043
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10044
|
+
"select",
|
|
10045
|
+
{
|
|
10046
|
+
className: "ff-panel__input",
|
|
10047
|
+
value: current,
|
|
10048
|
+
onChange: (e) => onChange(e.target.value),
|
|
10049
|
+
children: [
|
|
10050
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: field.placeholder ?? "\u2014" }),
|
|
10051
|
+
options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value)),
|
|
10052
|
+
current !== "" && !known && /* @__PURE__ */ jsxRuntime.jsxs("option", { value: current, children: [
|
|
10053
|
+
current,
|
|
10054
|
+
" (not in list)"
|
|
10055
|
+
] })
|
|
10056
|
+
]
|
|
10057
|
+
}
|
|
10058
|
+
);
|
|
10059
|
+
}
|
|
10060
|
+
function RepeaterField({
|
|
10061
|
+
field,
|
|
10062
|
+
value,
|
|
10063
|
+
onChange,
|
|
10064
|
+
renderCredentialField,
|
|
10065
|
+
renderDocumentField
|
|
10066
|
+
}) {
|
|
10067
|
+
const rows = Array.isArray(value) ? value : [];
|
|
10068
|
+
const max = field.maxItems ?? Infinity;
|
|
10069
|
+
const min = field.minItems ?? 0;
|
|
10070
|
+
const replace = (next) => onChange(next);
|
|
10071
|
+
const addRow = () => {
|
|
10072
|
+
const blank = {};
|
|
10073
|
+
for (const f of field.fields) {
|
|
10074
|
+
if ("default" in f && f.default !== void 0) blank[f.key] = f.default;
|
|
10075
|
+
}
|
|
10076
|
+
replace([...rows, blank]);
|
|
10077
|
+
};
|
|
10078
|
+
const removeRow = (i) => replace(rows.filter((_, idx) => idx !== i));
|
|
10079
|
+
const moveRow = (i, delta) => {
|
|
10080
|
+
const target = i + delta;
|
|
10081
|
+
if (target < 0 || target >= rows.length) return;
|
|
10082
|
+
const next = [...rows];
|
|
10083
|
+
[next[i], next[target]] = [next[target], next[i]];
|
|
10084
|
+
replace(next);
|
|
10085
|
+
};
|
|
10086
|
+
const setCell = (i, key, cell) => replace(rows.map((row, idx) => idx === i ? { ...row, [key]: cell } : row));
|
|
10087
|
+
const rowTitle = (row, i) => {
|
|
10088
|
+
const key = field.titleKey ?? field.fields[0]?.key;
|
|
10089
|
+
const raw = key ? row[key] : void 0;
|
|
10090
|
+
if (typeof raw === "string" && raw.trim() !== "") return raw;
|
|
10091
|
+
if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
|
|
10092
|
+
return `Item ${i + 1}`;
|
|
10093
|
+
};
|
|
10094
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater", "data-ff-repeater": field.key, children: [
|
|
10095
|
+
rows.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ff-repeater__empty", children: "None yet." }),
|
|
10096
|
+
rows.map((row, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__row", "data-ff-repeater-row": i, children: [
|
|
10097
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__row-head", children: [
|
|
10098
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-repeater__row-title", children: rowTitle(row, i) }),
|
|
10099
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__row-actions", children: [
|
|
10100
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10101
|
+
"button",
|
|
10102
|
+
{
|
|
10103
|
+
type: "button",
|
|
10104
|
+
className: "ff-repeater__btn",
|
|
10105
|
+
onClick: () => moveRow(i, -1),
|
|
10106
|
+
disabled: i === 0,
|
|
10107
|
+
"aria-label": `Move ${rowTitle(row, i)} up`,
|
|
10108
|
+
children: "\u2191"
|
|
10109
|
+
}
|
|
10110
|
+
),
|
|
10111
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10112
|
+
"button",
|
|
10113
|
+
{
|
|
10114
|
+
type: "button",
|
|
10115
|
+
className: "ff-repeater__btn",
|
|
10116
|
+
onClick: () => moveRow(i, 1),
|
|
10117
|
+
disabled: i === rows.length - 1,
|
|
10118
|
+
"aria-label": `Move ${rowTitle(row, i)} down`,
|
|
10119
|
+
children: "\u2193"
|
|
10120
|
+
}
|
|
10121
|
+
),
|
|
10122
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10123
|
+
"button",
|
|
10124
|
+
{
|
|
10125
|
+
type: "button",
|
|
10126
|
+
className: "ff-repeater__btn ff-repeater__btn--danger",
|
|
10127
|
+
onClick: () => removeRow(i),
|
|
10128
|
+
disabled: rows.length <= min,
|
|
10129
|
+
"aria-label": `Remove ${rowTitle(row, i)}`,
|
|
10130
|
+
children: "\u2715"
|
|
10131
|
+
}
|
|
10132
|
+
)
|
|
10133
|
+
] })
|
|
10134
|
+
] }),
|
|
10135
|
+
field.fields.map((sub) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__cell", children: [
|
|
10136
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-panel__label ff-panel__label--sub", children: [
|
|
10137
|
+
sub.label,
|
|
10138
|
+
sub.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-panel__required", "aria-hidden": true, children: " *" })
|
|
10139
|
+
] }),
|
|
10140
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10141
|
+
ConfigFieldRenderer,
|
|
10142
|
+
{
|
|
10143
|
+
field: sub,
|
|
10144
|
+
value: row[sub.key],
|
|
10145
|
+
onChange: (cell) => setCell(i, sub.key, cell),
|
|
10146
|
+
renderCredentialField,
|
|
10147
|
+
renderDocumentField
|
|
10148
|
+
}
|
|
10149
|
+
)
|
|
10150
|
+
] }, sub.key))
|
|
10151
|
+
] }, i)),
|
|
10152
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10153
|
+
"button",
|
|
10154
|
+
{
|
|
10155
|
+
type: "button",
|
|
10156
|
+
className: "ff-repeater__add",
|
|
10157
|
+
onClick: addRow,
|
|
10158
|
+
disabled: rows.length >= max,
|
|
10159
|
+
children: [
|
|
10160
|
+
"+ ",
|
|
10161
|
+
field.addLabel ?? "Add"
|
|
10162
|
+
]
|
|
10163
|
+
}
|
|
10164
|
+
)
|
|
10165
|
+
] });
|
|
10166
|
+
}
|
|
10167
|
+
function KeyValueField({
|
|
10168
|
+
field,
|
|
10169
|
+
value,
|
|
10170
|
+
onChange
|
|
10171
|
+
}) {
|
|
10172
|
+
const map = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10173
|
+
const entries = Object.entries(map);
|
|
10174
|
+
const commit = (next) => {
|
|
10175
|
+
const obj = {};
|
|
10176
|
+
for (const [k, v] of next) {
|
|
10177
|
+
if (k === "") continue;
|
|
10178
|
+
obj[k] = v;
|
|
10179
|
+
}
|
|
10180
|
+
onChange(obj);
|
|
10181
|
+
};
|
|
10182
|
+
const setKey = (i, key) => commit(entries.map(([k, v], idx) => idx === i ? [key, v] : [k, v]));
|
|
10183
|
+
const setVal = (i, val) => commit(entries.map(([k, v], idx) => idx === i ? [k, val] : [k, v]));
|
|
10184
|
+
const remove2 = (i) => commit(entries.filter((_, idx) => idx !== i));
|
|
10185
|
+
const add = () => commit([...entries, ["", ""]]);
|
|
10186
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-keyvalue", "data-ff-keyvalue": field.key, children: [
|
|
10187
|
+
entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-keyvalue__head", children: [
|
|
10188
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: field.keyLabel ?? "Key" }),
|
|
10189
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: field.valueLabel ?? "Value" }),
|
|
10190
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", {})
|
|
10191
|
+
] }),
|
|
10192
|
+
entries.map(([k, v], i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-keyvalue__row", "data-ff-keyvalue-row": i, children: [
|
|
10193
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10194
|
+
"input",
|
|
10195
|
+
{
|
|
10196
|
+
className: "ff-panel__input",
|
|
10197
|
+
value: k,
|
|
10198
|
+
placeholder: field.keyPlaceholder,
|
|
10199
|
+
"aria-label": `${field.keyLabel ?? "Key"} ${i + 1}`,
|
|
10200
|
+
onChange: (e) => setKey(i, e.target.value)
|
|
10201
|
+
}
|
|
10202
|
+
),
|
|
10203
|
+
field.valueOptions ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10204
|
+
"select",
|
|
10205
|
+
{
|
|
10206
|
+
className: "ff-panel__input",
|
|
10207
|
+
value: v ?? "",
|
|
10208
|
+
"aria-label": `${field.valueLabel ?? "Value"} ${i + 1}`,
|
|
10209
|
+
onChange: (e) => setVal(i, e.target.value),
|
|
10210
|
+
children: [
|
|
10211
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "\u2014" }),
|
|
10212
|
+
field.valueOptions.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
|
|
10213
|
+
]
|
|
10214
|
+
}
|
|
10215
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
10216
|
+
"input",
|
|
10217
|
+
{
|
|
10218
|
+
className: "ff-panel__input",
|
|
10219
|
+
value: v ?? "",
|
|
10220
|
+
placeholder: field.valuePlaceholder,
|
|
10221
|
+
"aria-label": `${field.valueLabel ?? "Value"} ${i + 1}`,
|
|
10222
|
+
onChange: (e) => setVal(i, e.target.value)
|
|
10223
|
+
}
|
|
10224
|
+
),
|
|
10225
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10226
|
+
"button",
|
|
10227
|
+
{
|
|
10228
|
+
type: "button",
|
|
10229
|
+
className: "ff-repeater__btn ff-repeater__btn--danger",
|
|
10230
|
+
onClick: () => remove2(i),
|
|
10231
|
+
"aria-label": `Remove ${k || `entry ${i + 1}`}`,
|
|
10232
|
+
children: "\u2715"
|
|
10233
|
+
}
|
|
10234
|
+
)
|
|
10235
|
+
] }, i)),
|
|
10236
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "ff-repeater__add", onClick: add, children: [
|
|
10237
|
+
"+ ",
|
|
10238
|
+
field.addLabel ?? "Add"
|
|
10239
|
+
] })
|
|
10240
|
+
] });
|
|
10241
|
+
}
|
|
9744
10242
|
function JsonField({ value, onChange, rows }) {
|
|
9745
10243
|
const initial = ReactExports.useMemo(() => {
|
|
9746
10244
|
try {
|
|
@@ -9775,6 +10273,7 @@ function NodeConfigPanel({
|
|
|
9775
10273
|
onChange,
|
|
9776
10274
|
header,
|
|
9777
10275
|
renderCredentialField,
|
|
10276
|
+
renderDocumentField,
|
|
9778
10277
|
className,
|
|
9779
10278
|
style: style2
|
|
9780
10279
|
}) {
|
|
@@ -9800,6 +10299,8 @@ function NodeConfigPanel({
|
|
|
9800
10299
|
const setDescription = (description) => onChange({ ...node, data: { ...node.data, description } });
|
|
9801
10300
|
const setConfigValue = (key, value) => onChange({ ...node, data: { ...node.data, config: { ...config, [key]: value } } });
|
|
9802
10301
|
const issues = validateConfig(kind, config);
|
|
10302
|
+
const adapter2 = getRichInputAdapter();
|
|
10303
|
+
const documentField = renderDocumentField ?? (adapter2?.renderEditor ? ({ value, onChange: set3 }) => adapter2.renderEditor({ value, onChange: set3 }) : void 0);
|
|
9803
10304
|
return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: ["ff-panel", className ?? ""].filter(Boolean).join(" "), style: style2, children: [
|
|
9804
10305
|
header,
|
|
9805
10306
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "ff-panel__header", children: [
|
|
@@ -9848,7 +10349,8 @@ function NodeConfigPanel({
|
|
|
9848
10349
|
field,
|
|
9849
10350
|
value: config[field.key],
|
|
9850
10351
|
onChange: (v) => setConfigValue(field.key, v),
|
|
9851
|
-
renderCredentialField
|
|
10352
|
+
renderCredentialField,
|
|
10353
|
+
renderDocumentField: documentField
|
|
9852
10354
|
}
|
|
9853
10355
|
)
|
|
9854
10356
|
] }, field.key))
|
|
@@ -9897,6 +10399,29 @@ function useFlowState(initial) {
|
|
|
9897
10399
|
return { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, onConnect, toGraph };
|
|
9898
10400
|
}
|
|
9899
10401
|
|
|
10402
|
+
// src/registry/ports.ts
|
|
10403
|
+
function resolvePortSpec(spec, config) {
|
|
10404
|
+
if (spec === void 0) return void 0;
|
|
10405
|
+
if (typeof spec !== "function") return spec;
|
|
10406
|
+
try {
|
|
10407
|
+
const resolved = spec(config);
|
|
10408
|
+
return Array.isArray(resolved) ? resolved : void 0;
|
|
10409
|
+
} catch {
|
|
10410
|
+
return void 0;
|
|
10411
|
+
}
|
|
10412
|
+
}
|
|
10413
|
+
function nodeConfig(node) {
|
|
10414
|
+
return node.data?.config ?? {};
|
|
10415
|
+
}
|
|
10416
|
+
function resolveNodePorts(node, kind) {
|
|
10417
|
+
const config = nodeConfig(node);
|
|
10418
|
+
const data = node.data;
|
|
10419
|
+
return {
|
|
10420
|
+
inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),
|
|
10421
|
+
outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config)
|
|
10422
|
+
};
|
|
10423
|
+
}
|
|
10424
|
+
|
|
9900
10425
|
// src/runtime/run-flow.ts
|
|
9901
10426
|
async function runFlow(graph, executors, onEvent = () => {
|
|
9902
10427
|
}, options = {}) {
|
|
@@ -10028,8 +10553,9 @@ function activatedPorts(node, result) {
|
|
|
10028
10553
|
return { ports: [r.branch], value: r.value ?? r };
|
|
10029
10554
|
}
|
|
10030
10555
|
}
|
|
10031
|
-
const
|
|
10032
|
-
|
|
10556
|
+
const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
|
|
10557
|
+
const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);
|
|
10558
|
+
return { ports: declared?.length ? declared : ["out"], value: result };
|
|
10033
10559
|
}
|
|
10034
10560
|
|
|
10035
10561
|
// src/runtime/use-flow-run.ts
|
|
@@ -10248,9 +10774,10 @@ function RegistryNodeInner(props) {
|
|
|
10248
10774
|
const data = props.data;
|
|
10249
10775
|
const status = data.status ?? "idle";
|
|
10250
10776
|
const accent = kind.accent ?? categoryAccent(kind.category);
|
|
10251
|
-
const
|
|
10252
|
-
const
|
|
10253
|
-
const
|
|
10777
|
+
const resolved = resolveNodePorts(props, kind);
|
|
10778
|
+
const inputs = resolved.inputs ?? defaultInputs2(kind.category);
|
|
10779
|
+
const outputs = resolved.outputs ?? defaultOutputs2(kind.category);
|
|
10780
|
+
const config = nodeConfig(props);
|
|
10254
10781
|
const label = data.label ?? kind.label;
|
|
10255
10782
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10256
10783
|
"div",
|
|
@@ -10363,6 +10890,17 @@ function removeEdges(edges, ids) {
|
|
|
10363
10890
|
const doomed = new Set(ids);
|
|
10364
10891
|
return edges.filter((e) => !doomed.has(e.id));
|
|
10365
10892
|
}
|
|
10893
|
+
function setEdgeLabel(edges, id2, label) {
|
|
10894
|
+
const next = label?.trim();
|
|
10895
|
+
return edges.map((e) => {
|
|
10896
|
+
if (e.id !== id2) return e;
|
|
10897
|
+
if (!next) {
|
|
10898
|
+
const { label: _drop, ...rest } = e;
|
|
10899
|
+
return rest;
|
|
10900
|
+
}
|
|
10901
|
+
return { ...e, label: next };
|
|
10902
|
+
});
|
|
10903
|
+
}
|
|
10366
10904
|
function duplicateNode(node, id2, offset = 40) {
|
|
10367
10905
|
return {
|
|
10368
10906
|
...node,
|
|
@@ -10410,6 +10948,7 @@ function FlowEditorInner({
|
|
|
10410
10948
|
onChange,
|
|
10411
10949
|
onSelectionChange,
|
|
10412
10950
|
onDelete,
|
|
10951
|
+
onEdgeDelete,
|
|
10413
10952
|
apiRef
|
|
10414
10953
|
}) {
|
|
10415
10954
|
const internal = useFlowState(initial);
|
|
@@ -10427,18 +10966,39 @@ function FlowEditorInner({
|
|
|
10427
10966
|
const [selectedId, setSelectedId] = ReactExports.useState(null);
|
|
10428
10967
|
const selected2 = ReactExports.useMemo(() => flow.nodes.find((n) => n.id === selectedId) ?? null, [flow.nodes, selectedId]);
|
|
10429
10968
|
ReactExports.useEffect(() => onSelectionChange?.(selected2), [selected2, onSelectionChange]);
|
|
10430
|
-
const
|
|
10969
|
+
const [selectedEdgeId, setSelectedEdgeId] = ReactExports.useState(null);
|
|
10970
|
+
const selectedEdge = ReactExports.useMemo(
|
|
10971
|
+
() => flow.edges.find((e) => e.id === selectedEdgeId) ?? null,
|
|
10972
|
+
[flow.edges, selectedEdgeId]
|
|
10973
|
+
);
|
|
10974
|
+
const handleNodeClick2 = (_e, node) => {
|
|
10975
|
+
setSelectedId(node.id);
|
|
10976
|
+
setSelectedEdgeId(null);
|
|
10977
|
+
};
|
|
10431
10978
|
const [menu, setMenu] = ReactExports.useState(null);
|
|
10979
|
+
const [labelEdit, setLabelEdit] = ReactExports.useState(null);
|
|
10432
10980
|
const closeMenu = ReactExports.useCallback(() => setMenu(null), []);
|
|
10433
10981
|
const handleNodeContextMenu = (event, node) => {
|
|
10434
10982
|
event.preventDefault();
|
|
10435
10983
|
setSelectedId(node.id);
|
|
10436
|
-
|
|
10984
|
+
setSelectedEdgeId(null);
|
|
10985
|
+
setMenu({ x: event.clientX, y: event.clientY, target: { type: "node", id: node.id } });
|
|
10986
|
+
};
|
|
10987
|
+
const handleEdgeClick = (_e, edge) => {
|
|
10988
|
+
setSelectedEdgeId(edge.id);
|
|
10989
|
+
};
|
|
10990
|
+
const handleEdgeContextMenu = (event, edge) => {
|
|
10991
|
+
event.preventDefault();
|
|
10992
|
+
setSelectedEdgeId(edge.id);
|
|
10993
|
+
setMenu({ x: event.clientX, y: event.clientY, target: { type: "edge", id: edge.id } });
|
|
10437
10994
|
};
|
|
10438
10995
|
ReactExports.useEffect(() => {
|
|
10439
|
-
if (menu === null) return;
|
|
10440
|
-
const close = () =>
|
|
10441
|
-
|
|
10996
|
+
if (menu === null && labelEdit === null) return;
|
|
10997
|
+
const close = () => {
|
|
10998
|
+
setMenu(null);
|
|
10999
|
+
setLabelEdit(null);
|
|
11000
|
+
};
|
|
11001
|
+
const onKey = (e) => e.key === "Escape" && close();
|
|
10442
11002
|
window.addEventListener("click", close);
|
|
10443
11003
|
window.addEventListener("scroll", close, true);
|
|
10444
11004
|
window.addEventListener("keydown", onKey);
|
|
@@ -10447,7 +11007,7 @@ function FlowEditorInner({
|
|
|
10447
11007
|
window.removeEventListener("scroll", close, true);
|
|
10448
11008
|
window.removeEventListener("keydown", onKey);
|
|
10449
11009
|
};
|
|
10450
|
-
}, [menu]);
|
|
11010
|
+
}, [menu, labelEdit]);
|
|
10451
11011
|
ReactExports.useEffect(() => {
|
|
10452
11012
|
if (!controlled) onChange?.({ nodes: flow.nodes, edges: flow.edges });
|
|
10453
11013
|
}, [flow.nodes, flow.edges, onChange, controlled]);
|
|
@@ -10490,14 +11050,29 @@ function FlowEditorInner({
|
|
|
10490
11050
|
edges: flow.edges,
|
|
10491
11051
|
selectedId,
|
|
10492
11052
|
selected: selected2,
|
|
11053
|
+
selectedEdgeId,
|
|
11054
|
+
selectedEdge,
|
|
10493
11055
|
running: runner.running,
|
|
10494
11056
|
statuses: runner.statuses,
|
|
10495
11057
|
select: setSelectedId,
|
|
11058
|
+
selectEdge: setSelectedEdgeId,
|
|
10496
11059
|
addNode,
|
|
10497
11060
|
updateNode: (next) => flow.setNodes((all) => all.map((x) => x.id === next.id ? next : x)),
|
|
10498
11061
|
deleteNodes,
|
|
10499
11062
|
deleteSelected: () => deleteNodes(selectedId ? [selectedId] : []),
|
|
10500
|
-
deleteEdges: (ids) =>
|
|
11063
|
+
deleteEdges: (ids) => {
|
|
11064
|
+
if (ids.length === 0) return;
|
|
11065
|
+
flow.setEdges((all) => removeEdges(all, ids));
|
|
11066
|
+
setSelectedEdgeId((cur) => cur !== null && ids.includes(cur) ? null : cur);
|
|
11067
|
+
onEdgeDelete?.(ids);
|
|
11068
|
+
},
|
|
11069
|
+
deleteSelectedEdge: () => {
|
|
11070
|
+
if (!selectedEdgeId) return;
|
|
11071
|
+
flow.setEdges((all) => removeEdges(all, [selectedEdgeId]));
|
|
11072
|
+
setSelectedEdgeId(null);
|
|
11073
|
+
onEdgeDelete?.([selectedEdgeId]);
|
|
11074
|
+
},
|
|
11075
|
+
setEdgeLabel: (id2, label) => flow.setEdges((all) => setEdgeLabel(all, id2, label)),
|
|
10501
11076
|
duplicateNode: (id2) => {
|
|
10502
11077
|
const src = flow.nodes.find((n) => n.id === id2);
|
|
10503
11078
|
if (!src) return null;
|
|
@@ -10569,6 +11144,9 @@ function FlowEditorInner({
|
|
|
10569
11144
|
onConnect: flow.onConnect,
|
|
10570
11145
|
onNodeClick: handleNodeClick2,
|
|
10571
11146
|
onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
|
|
11147
|
+
onEdgeClick: handleEdgeClick,
|
|
11148
|
+
onEdgeContextMenu: builtins.edgeContextMenu === false ? void 0 : handleEdgeContextMenu,
|
|
11149
|
+
onEdgesDelete: (deleted) => onEdgeDelete?.(deleted.map((e) => e.id)),
|
|
10572
11150
|
onNodesDelete: (deleted) => onDelete?.(deleted.map((n) => n.id)),
|
|
10573
11151
|
deleteKeyCode: ["Delete", "Backspace"],
|
|
10574
11152
|
height: "100%",
|
|
@@ -10577,14 +11155,14 @@ function FlowEditorInner({
|
|
|
10577
11155
|
}
|
|
10578
11156
|
),
|
|
10579
11157
|
slots.empty && api.nodes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-editor__empty", children: slots.empty(api) }),
|
|
10580
|
-
menu
|
|
11158
|
+
menu?.target.type === "node" && builtins.contextMenu !== false && /* @__PURE__ */ jsxRuntime.jsx(
|
|
10581
11159
|
"div",
|
|
10582
11160
|
{
|
|
10583
11161
|
className: "ff-editor__ctx",
|
|
10584
11162
|
style: { top: menu.y, left: menu.x },
|
|
10585
11163
|
role: "menu",
|
|
10586
11164
|
onClick: (e) => e.stopPropagation(),
|
|
10587
|
-
children: slots.contextMenu ? slots.contextMenu(api, menu.
|
|
11165
|
+
children: slots.contextMenu ? slots.contextMenu(api, menu.target.id, closeMenu) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10588
11166
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10589
11167
|
"button",
|
|
10590
11168
|
{
|
|
@@ -10593,7 +11171,7 @@ function FlowEditorInner({
|
|
|
10593
11171
|
className: "ff-editor__ctx-item",
|
|
10594
11172
|
"data-action": "ctx-duplicate",
|
|
10595
11173
|
onClick: () => {
|
|
10596
|
-
api.duplicateNode(menu.
|
|
11174
|
+
api.duplicateNode(menu.target.id);
|
|
10597
11175
|
closeMenu();
|
|
10598
11176
|
},
|
|
10599
11177
|
children: "Duplicate"
|
|
@@ -10607,7 +11185,7 @@ function FlowEditorInner({
|
|
|
10607
11185
|
className: "ff-editor__ctx-item ff-editor__ctx-item--danger",
|
|
10608
11186
|
"data-action": "ctx-delete",
|
|
10609
11187
|
onClick: () => {
|
|
10610
|
-
api.deleteNodes([menu.
|
|
11188
|
+
api.deleteNodes([menu.target.id]);
|
|
10611
11189
|
closeMenu();
|
|
10612
11190
|
},
|
|
10613
11191
|
children: "Delete"
|
|
@@ -10616,6 +11194,58 @@ function FlowEditorInner({
|
|
|
10616
11194
|
] })
|
|
10617
11195
|
}
|
|
10618
11196
|
),
|
|
11197
|
+
menu?.target.type === "edge" && builtins.edgeContextMenu !== false && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11198
|
+
"div",
|
|
11199
|
+
{
|
|
11200
|
+
className: "ff-editor__ctx",
|
|
11201
|
+
style: { top: menu.y, left: menu.x },
|
|
11202
|
+
role: "menu",
|
|
11203
|
+
onClick: (e) => e.stopPropagation(),
|
|
11204
|
+
children: slots.edgeContextMenu ? slots.edgeContextMenu(api, menu.target.id, closeMenu) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11205
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11206
|
+
"button",
|
|
11207
|
+
{
|
|
11208
|
+
type: "button",
|
|
11209
|
+
role: "menuitem",
|
|
11210
|
+
className: "ff-editor__ctx-item",
|
|
11211
|
+
"data-action": "ctx-edge-label",
|
|
11212
|
+
onClick: () => {
|
|
11213
|
+
setLabelEdit({ x: menu.x, y: menu.y, edgeId: menu.target.id });
|
|
11214
|
+
setMenu(null);
|
|
11215
|
+
},
|
|
11216
|
+
children: "Label\u2026"
|
|
11217
|
+
}
|
|
11218
|
+
),
|
|
11219
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11220
|
+
"button",
|
|
11221
|
+
{
|
|
11222
|
+
type: "button",
|
|
11223
|
+
role: "menuitem",
|
|
11224
|
+
className: "ff-editor__ctx-item ff-editor__ctx-item--danger",
|
|
11225
|
+
"data-action": "ctx-edge-delete",
|
|
11226
|
+
onClick: () => {
|
|
11227
|
+
api.deleteEdges([menu.target.id]);
|
|
11228
|
+
closeMenu();
|
|
11229
|
+
},
|
|
11230
|
+
children: "Delete connection"
|
|
11231
|
+
}
|
|
11232
|
+
)
|
|
11233
|
+
] })
|
|
11234
|
+
}
|
|
11235
|
+
),
|
|
11236
|
+
labelEdit !== null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11237
|
+
EdgeLabelEditor,
|
|
11238
|
+
{
|
|
11239
|
+
x: labelEdit.x,
|
|
11240
|
+
y: labelEdit.y,
|
|
11241
|
+
initial: flow.edges.find((e) => e.id === labelEdit.edgeId)?.label ?? "",
|
|
11242
|
+
onCommit: (text) => {
|
|
11243
|
+
api.setEdgeLabel(labelEdit.edgeId, text);
|
|
11244
|
+
setLabelEdit(null);
|
|
11245
|
+
},
|
|
11246
|
+
onCancel: () => setLabelEdit(null)
|
|
11247
|
+
}
|
|
11248
|
+
),
|
|
10619
11249
|
showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsxRuntime.jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" }))
|
|
10620
11250
|
] }),
|
|
10621
11251
|
showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-editor__panel-wrap", children: [
|
|
@@ -10692,6 +11322,46 @@ function makeControlledFlowAdapter(value, onChange) {
|
|
|
10692
11322
|
toGraph: () => value
|
|
10693
11323
|
};
|
|
10694
11324
|
}
|
|
11325
|
+
function EdgeLabelEditor({
|
|
11326
|
+
x,
|
|
11327
|
+
y,
|
|
11328
|
+
initial,
|
|
11329
|
+
onCommit,
|
|
11330
|
+
onCancel
|
|
11331
|
+
}) {
|
|
11332
|
+
const [text, setText] = ReactExports.useState(initial);
|
|
11333
|
+
const ref = ReactExports.useRef(null);
|
|
11334
|
+
ReactExports.useEffect(() => {
|
|
11335
|
+
ref.current?.focus();
|
|
11336
|
+
ref.current?.select();
|
|
11337
|
+
}, []);
|
|
11338
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11339
|
+
"div",
|
|
11340
|
+
{
|
|
11341
|
+
className: "ff-editor__ctx ff-editor__edge-label",
|
|
11342
|
+
style: { top: y, left: x },
|
|
11343
|
+
onClick: (e) => e.stopPropagation(),
|
|
11344
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
11345
|
+
"input",
|
|
11346
|
+
{
|
|
11347
|
+
ref,
|
|
11348
|
+
className: "ff-panel__input",
|
|
11349
|
+
value: text,
|
|
11350
|
+
placeholder: "Label this connection",
|
|
11351
|
+
"aria-label": "Connection label",
|
|
11352
|
+
"data-action": "edge-label-input",
|
|
11353
|
+
onChange: (e) => setText(e.target.value),
|
|
11354
|
+
onKeyDown: (e) => {
|
|
11355
|
+
e.stopPropagation();
|
|
11356
|
+
if (e.key === "Enter") onCommit(text);
|
|
11357
|
+
if (e.key === "Escape") onCancel();
|
|
11358
|
+
},
|
|
11359
|
+
onBlur: () => onCommit(text)
|
|
11360
|
+
}
|
|
11361
|
+
)
|
|
11362
|
+
}
|
|
11363
|
+
);
|
|
11364
|
+
}
|
|
10695
11365
|
function defineNode(render) {
|
|
10696
11366
|
function Wrapped(props) {
|
|
10697
11367
|
return render({
|
|
@@ -10741,6 +11411,7 @@ exports.NodeShell = NodeShell;
|
|
|
10741
11411
|
exports.NoteNode = NoteNode;
|
|
10742
11412
|
exports.OutputNode = OutputNode2;
|
|
10743
11413
|
exports.RegistryNode = RegistryNode;
|
|
11414
|
+
exports.RichInputPreview = RichInputPreview;
|
|
10744
11415
|
exports.SubgraphNode = SubgraphNode;
|
|
10745
11416
|
exports.TriggerNode = TriggerNode;
|
|
10746
11417
|
exports.WORKFLOW_SCHEMA_URL = WORKFLOW_SCHEMA_URL;
|
|
@@ -10753,12 +11424,18 @@ exports.defaultNodeTypes = defaultNodeTypes;
|
|
|
10753
11424
|
exports.defineNode = defineNode;
|
|
10754
11425
|
exports.exportWorkflow = exportWorkflow;
|
|
10755
11426
|
exports.getNodeKind = getNodeKind;
|
|
11427
|
+
exports.getRichInputAdapter = getRichInputAdapter;
|
|
10756
11428
|
exports.importWorkflow = importWorkflow;
|
|
11429
|
+
exports.isRichInputEnabled = isRichInputEnabled;
|
|
10757
11430
|
exports.listNodeKinds = listNodeKinds;
|
|
10758
11431
|
exports.onNodeKindsChanged = onNodeKindsChanged;
|
|
11432
|
+
exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
|
|
10759
11433
|
exports.paletteDropHandlers = paletteDropHandlers;
|
|
10760
11434
|
exports.registerBuiltinKinds = registerBuiltinKinds;
|
|
10761
11435
|
exports.registerNodeKind = registerNodeKind;
|
|
11436
|
+
exports.registerRichInputAdapter = registerRichInputAdapter;
|
|
11437
|
+
exports.resolveNodePorts = resolveNodePorts;
|
|
11438
|
+
exports.resolvePortSpec = resolvePortSpec;
|
|
10762
11439
|
exports.runFlow = runFlow;
|
|
10763
11440
|
exports.useFlowEditor = useFlowEditor;
|
|
10764
11441
|
exports.useFlowEditorOptional = useFlowEditorOptional;
|