@particle-academy/fancy-flow 0.8.0 → 0.10.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 +104 -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-WNVBXXOL.js → chunk-CPSOC27D.js} +43 -2
- package/dist/chunk-CPSOC27D.js.map +1 -0
- package/dist/chunk-F5RPRB7A.js +56 -0
- package/dist/chunk-F5RPRB7A.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-M2XKGQQL.js → chunk-NCPQDVUE.js} +178 -16
- package/dist/chunk-NCPQDVUE.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 +716 -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 +417 -23
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.cts +112 -4
- package/dist/registry/index.d.ts +112 -4
- package/dist/registry.cjs +312 -31
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +4 -2
- package/dist/rich-input.cjs +56 -0
- package/dist/rich-input.cjs.map +1 -0
- package/dist/rich-input.d.cts +27 -0
- package/dist/rich-input.d.ts +27 -0
- package/dist/rich-input.js +37 -0
- package/dist/rich-input.js.map +1 -0
- 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 +24 -2
- 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.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { useFlowState, useFlowRun, applyStatusesToNodes } from './chunk-
|
|
2
|
-
export { applyStatusesToNodes, useFlowRun, useFlowState } from './chunk-
|
|
3
|
-
export { runFlow } from './chunk-
|
|
4
|
-
import { buildNodeTypes, registerBuiltinKinds } from './chunk-
|
|
5
|
-
export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds } from './chunk-
|
|
1
|
+
import { useFlowState, useFlowRun, applyStatusesToNodes } from './chunk-VEI743ZX.js';
|
|
2
|
+
export { applyStatusesToNodes, useFlowRun, useFlowState } from './chunk-VEI743ZX.js';
|
|
3
|
+
export { runFlow } from './chunk-HNBO4HP3.js';
|
|
4
|
+
import { buildNodeTypes, registerBuiltinKinds } from './chunk-NCPQDVUE.js';
|
|
5
|
+
export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds } from './chunk-NCPQDVUE.js';
|
|
6
6
|
import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, index, BackgroundVariant, Background, Controls, MiniMap } from './chunk-NF6NPY5N.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export { resolveNodePorts, resolvePortSpec } from './chunk-TITD5W4Y.js';
|
|
8
|
+
import { workflowToBlob, importWorkflow, exportWorkflow } from './chunk-6GGFEZH7.js';
|
|
9
|
+
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, workflowToBlob } from './chunk-6GGFEZH7.js';
|
|
10
|
+
import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, validateConfig, categoryAccent } from './chunk-CPSOC27D.js';
|
|
11
|
+
export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-CPSOC27D.js';
|
|
12
|
+
import { getRichInputAdapter } from './chunk-F5RPRB7A.js';
|
|
13
|
+
export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
|
|
11
14
|
import { memo, createContext, forwardRef, useState, useEffect, useMemo, useCallback, useImperativeHandle, useRef, useContext } from 'react';
|
|
12
15
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
16
|
|
|
@@ -274,9 +277,18 @@ function paletteDropHandlers(onDrop) {
|
|
|
274
277
|
}
|
|
275
278
|
};
|
|
276
279
|
}
|
|
277
|
-
function ConfigFieldRenderer({
|
|
280
|
+
function ConfigFieldRenderer({
|
|
281
|
+
field,
|
|
282
|
+
value,
|
|
283
|
+
onChange,
|
|
284
|
+
renderCredentialField,
|
|
285
|
+
renderDocumentField
|
|
286
|
+
}) {
|
|
278
287
|
switch (field.type) {
|
|
279
|
-
case "text":
|
|
288
|
+
case "text": {
|
|
289
|
+
if (field.choices?.length) {
|
|
290
|
+
return /* @__PURE__ */ jsx(ChoiceField, { field, value, onChange });
|
|
291
|
+
}
|
|
280
292
|
return /* @__PURE__ */ jsx(
|
|
281
293
|
"input",
|
|
282
294
|
{
|
|
@@ -287,6 +299,7 @@ function ConfigFieldRenderer({ field, value, onChange, renderCredentialField })
|
|
|
287
299
|
onChange: (e) => onChange(e.target.value)
|
|
288
300
|
}
|
|
289
301
|
);
|
|
302
|
+
}
|
|
290
303
|
case "textarea":
|
|
291
304
|
return /* @__PURE__ */ jsx(
|
|
292
305
|
"textarea",
|
|
@@ -364,10 +377,244 @@ function ConfigFieldRenderer({ field, value, onChange, renderCredentialField })
|
|
|
364
377
|
onChange: (e) => onChange(e.target.value)
|
|
365
378
|
}
|
|
366
379
|
);
|
|
380
|
+
case "repeater":
|
|
381
|
+
return /* @__PURE__ */ jsx(
|
|
382
|
+
RepeaterField,
|
|
383
|
+
{
|
|
384
|
+
field,
|
|
385
|
+
value,
|
|
386
|
+
onChange,
|
|
387
|
+
renderCredentialField,
|
|
388
|
+
renderDocumentField
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
case "keyvalue":
|
|
392
|
+
return /* @__PURE__ */ jsx(KeyValueField, { field, value, onChange });
|
|
393
|
+
case "document":
|
|
394
|
+
if (renderDocumentField) {
|
|
395
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderDocumentField({ documentType: field.documentType, value, onChange }) });
|
|
396
|
+
}
|
|
397
|
+
return /* @__PURE__ */ jsxs("p", { className: "ff-panel__hint ff-panel__hint--missing", children: [
|
|
398
|
+
"No document editor supplied. Pass ",
|
|
399
|
+
/* @__PURE__ */ jsx("code", { children: "renderDocumentField" }),
|
|
400
|
+
" to NodeConfigPanel to author this field."
|
|
401
|
+
] });
|
|
367
402
|
default:
|
|
368
403
|
return null;
|
|
369
404
|
}
|
|
370
405
|
}
|
|
406
|
+
function normalizeChoices(choices) {
|
|
407
|
+
return choices.map(
|
|
408
|
+
(c) => typeof c === "string" ? { value: c, label: c } : { value: c.value, label: c.label ?? c.value }
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
function ChoiceField({
|
|
412
|
+
field,
|
|
413
|
+
value,
|
|
414
|
+
onChange
|
|
415
|
+
}) {
|
|
416
|
+
const options = normalizeChoices(field.choices ?? []);
|
|
417
|
+
const current = typeof value === "string" ? value : "";
|
|
418
|
+
const known = options.some((o) => o.value === current);
|
|
419
|
+
return /* @__PURE__ */ jsxs(
|
|
420
|
+
"select",
|
|
421
|
+
{
|
|
422
|
+
className: "ff-panel__input",
|
|
423
|
+
value: current,
|
|
424
|
+
onChange: (e) => onChange(e.target.value),
|
|
425
|
+
children: [
|
|
426
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: field.placeholder ?? "\u2014" }),
|
|
427
|
+
options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value)),
|
|
428
|
+
current !== "" && !known && /* @__PURE__ */ jsxs("option", { value: current, children: [
|
|
429
|
+
current,
|
|
430
|
+
" (not in list)"
|
|
431
|
+
] })
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function RepeaterField({
|
|
437
|
+
field,
|
|
438
|
+
value,
|
|
439
|
+
onChange,
|
|
440
|
+
renderCredentialField,
|
|
441
|
+
renderDocumentField
|
|
442
|
+
}) {
|
|
443
|
+
const rows = Array.isArray(value) ? value : [];
|
|
444
|
+
const max = field.maxItems ?? Infinity;
|
|
445
|
+
const min = field.minItems ?? 0;
|
|
446
|
+
const replace = (next) => onChange(next);
|
|
447
|
+
const addRow = () => {
|
|
448
|
+
const blank = {};
|
|
449
|
+
for (const f of field.fields) {
|
|
450
|
+
if ("default" in f && f.default !== void 0) blank[f.key] = f.default;
|
|
451
|
+
}
|
|
452
|
+
replace([...rows, blank]);
|
|
453
|
+
};
|
|
454
|
+
const removeRow = (i) => replace(rows.filter((_, idx) => idx !== i));
|
|
455
|
+
const moveRow = (i, delta) => {
|
|
456
|
+
const target = i + delta;
|
|
457
|
+
if (target < 0 || target >= rows.length) return;
|
|
458
|
+
const next = [...rows];
|
|
459
|
+
[next[i], next[target]] = [next[target], next[i]];
|
|
460
|
+
replace(next);
|
|
461
|
+
};
|
|
462
|
+
const setCell = (i, key, cell) => replace(rows.map((row, idx) => idx === i ? { ...row, [key]: cell } : row));
|
|
463
|
+
const rowTitle = (row, i) => {
|
|
464
|
+
const key = field.titleKey ?? field.fields[0]?.key;
|
|
465
|
+
const raw = key ? row[key] : void 0;
|
|
466
|
+
if (typeof raw === "string" && raw.trim() !== "") return raw;
|
|
467
|
+
if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
|
|
468
|
+
return `Item ${i + 1}`;
|
|
469
|
+
};
|
|
470
|
+
return /* @__PURE__ */ jsxs("div", { className: "ff-repeater", "data-ff-repeater": field.key, children: [
|
|
471
|
+
rows.length === 0 && /* @__PURE__ */ jsx("p", { className: "ff-repeater__empty", children: "None yet." }),
|
|
472
|
+
rows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "ff-repeater__row", "data-ff-repeater-row": i, children: [
|
|
473
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-repeater__row-head", children: [
|
|
474
|
+
/* @__PURE__ */ jsx("span", { className: "ff-repeater__row-title", children: rowTitle(row, i) }),
|
|
475
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-repeater__row-actions", children: [
|
|
476
|
+
/* @__PURE__ */ jsx(
|
|
477
|
+
"button",
|
|
478
|
+
{
|
|
479
|
+
type: "button",
|
|
480
|
+
className: "ff-repeater__btn",
|
|
481
|
+
onClick: () => moveRow(i, -1),
|
|
482
|
+
disabled: i === 0,
|
|
483
|
+
"aria-label": `Move ${rowTitle(row, i)} up`,
|
|
484
|
+
children: "\u2191"
|
|
485
|
+
}
|
|
486
|
+
),
|
|
487
|
+
/* @__PURE__ */ jsx(
|
|
488
|
+
"button",
|
|
489
|
+
{
|
|
490
|
+
type: "button",
|
|
491
|
+
className: "ff-repeater__btn",
|
|
492
|
+
onClick: () => moveRow(i, 1),
|
|
493
|
+
disabled: i === rows.length - 1,
|
|
494
|
+
"aria-label": `Move ${rowTitle(row, i)} down`,
|
|
495
|
+
children: "\u2193"
|
|
496
|
+
}
|
|
497
|
+
),
|
|
498
|
+
/* @__PURE__ */ jsx(
|
|
499
|
+
"button",
|
|
500
|
+
{
|
|
501
|
+
type: "button",
|
|
502
|
+
className: "ff-repeater__btn ff-repeater__btn--danger",
|
|
503
|
+
onClick: () => removeRow(i),
|
|
504
|
+
disabled: rows.length <= min,
|
|
505
|
+
"aria-label": `Remove ${rowTitle(row, i)}`,
|
|
506
|
+
children: "\u2715"
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
] })
|
|
510
|
+
] }),
|
|
511
|
+
field.fields.map((sub) => /* @__PURE__ */ jsxs("div", { className: "ff-repeater__cell", children: [
|
|
512
|
+
/* @__PURE__ */ jsxs("label", { className: "ff-panel__label ff-panel__label--sub", children: [
|
|
513
|
+
sub.label,
|
|
514
|
+
sub.required && /* @__PURE__ */ jsx("span", { className: "ff-panel__required", "aria-hidden": true, children: " *" })
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ jsx(
|
|
517
|
+
ConfigFieldRenderer,
|
|
518
|
+
{
|
|
519
|
+
field: sub,
|
|
520
|
+
value: row[sub.key],
|
|
521
|
+
onChange: (cell) => setCell(i, sub.key, cell),
|
|
522
|
+
renderCredentialField,
|
|
523
|
+
renderDocumentField
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
] }, sub.key))
|
|
527
|
+
] }, i)),
|
|
528
|
+
/* @__PURE__ */ jsxs(
|
|
529
|
+
"button",
|
|
530
|
+
{
|
|
531
|
+
type: "button",
|
|
532
|
+
className: "ff-repeater__add",
|
|
533
|
+
onClick: addRow,
|
|
534
|
+
disabled: rows.length >= max,
|
|
535
|
+
children: [
|
|
536
|
+
"+ ",
|
|
537
|
+
field.addLabel ?? "Add"
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
)
|
|
541
|
+
] });
|
|
542
|
+
}
|
|
543
|
+
function KeyValueField({
|
|
544
|
+
field,
|
|
545
|
+
value,
|
|
546
|
+
onChange
|
|
547
|
+
}) {
|
|
548
|
+
const map = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
549
|
+
const entries = Object.entries(map);
|
|
550
|
+
const commit = (next) => {
|
|
551
|
+
const obj = {};
|
|
552
|
+
for (const [k, v] of next) {
|
|
553
|
+
if (k === "") continue;
|
|
554
|
+
obj[k] = v;
|
|
555
|
+
}
|
|
556
|
+
onChange(obj);
|
|
557
|
+
};
|
|
558
|
+
const setKey = (i, key) => commit(entries.map(([k, v], idx) => idx === i ? [key, v] : [k, v]));
|
|
559
|
+
const setVal = (i, val) => commit(entries.map(([k, v], idx) => idx === i ? [k, val] : [k, v]));
|
|
560
|
+
const remove = (i) => commit(entries.filter((_, idx) => idx !== i));
|
|
561
|
+
const add = () => commit([...entries, ["", ""]]);
|
|
562
|
+
return /* @__PURE__ */ jsxs("div", { className: "ff-keyvalue", "data-ff-keyvalue": field.key, children: [
|
|
563
|
+
entries.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ff-keyvalue__head", children: [
|
|
564
|
+
/* @__PURE__ */ jsx("span", { children: field.keyLabel ?? "Key" }),
|
|
565
|
+
/* @__PURE__ */ jsx("span", { children: field.valueLabel ?? "Value" }),
|
|
566
|
+
/* @__PURE__ */ jsx("span", {})
|
|
567
|
+
] }),
|
|
568
|
+
entries.map(([k, v], i) => /* @__PURE__ */ jsxs("div", { className: "ff-keyvalue__row", "data-ff-keyvalue-row": i, children: [
|
|
569
|
+
/* @__PURE__ */ jsx(
|
|
570
|
+
"input",
|
|
571
|
+
{
|
|
572
|
+
className: "ff-panel__input",
|
|
573
|
+
value: k,
|
|
574
|
+
placeholder: field.keyPlaceholder,
|
|
575
|
+
"aria-label": `${field.keyLabel ?? "Key"} ${i + 1}`,
|
|
576
|
+
onChange: (e) => setKey(i, e.target.value)
|
|
577
|
+
}
|
|
578
|
+
),
|
|
579
|
+
field.valueOptions ? /* @__PURE__ */ jsxs(
|
|
580
|
+
"select",
|
|
581
|
+
{
|
|
582
|
+
className: "ff-panel__input",
|
|
583
|
+
value: v ?? "",
|
|
584
|
+
"aria-label": `${field.valueLabel ?? "Value"} ${i + 1}`,
|
|
585
|
+
onChange: (e) => setVal(i, e.target.value),
|
|
586
|
+
children: [
|
|
587
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "\u2014" }),
|
|
588
|
+
field.valueOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
589
|
+
]
|
|
590
|
+
}
|
|
591
|
+
) : /* @__PURE__ */ jsx(
|
|
592
|
+
"input",
|
|
593
|
+
{
|
|
594
|
+
className: "ff-panel__input",
|
|
595
|
+
value: v ?? "",
|
|
596
|
+
placeholder: field.valuePlaceholder,
|
|
597
|
+
"aria-label": `${field.valueLabel ?? "Value"} ${i + 1}`,
|
|
598
|
+
onChange: (e) => setVal(i, e.target.value)
|
|
599
|
+
}
|
|
600
|
+
),
|
|
601
|
+
/* @__PURE__ */ jsx(
|
|
602
|
+
"button",
|
|
603
|
+
{
|
|
604
|
+
type: "button",
|
|
605
|
+
className: "ff-repeater__btn ff-repeater__btn--danger",
|
|
606
|
+
onClick: () => remove(i),
|
|
607
|
+
"aria-label": `Remove ${k || `entry ${i + 1}`}`,
|
|
608
|
+
children: "\u2715"
|
|
609
|
+
}
|
|
610
|
+
)
|
|
611
|
+
] }, i)),
|
|
612
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "ff-repeater__add", onClick: add, children: [
|
|
613
|
+
"+ ",
|
|
614
|
+
field.addLabel ?? "Add"
|
|
615
|
+
] })
|
|
616
|
+
] });
|
|
617
|
+
}
|
|
371
618
|
function JsonField({ value, onChange, rows }) {
|
|
372
619
|
const initial = useMemo(() => {
|
|
373
620
|
try {
|
|
@@ -402,6 +649,7 @@ function NodeConfigPanel({
|
|
|
402
649
|
onChange,
|
|
403
650
|
header,
|
|
404
651
|
renderCredentialField,
|
|
652
|
+
renderDocumentField,
|
|
405
653
|
className,
|
|
406
654
|
style
|
|
407
655
|
}) {
|
|
@@ -427,6 +675,8 @@ function NodeConfigPanel({
|
|
|
427
675
|
const setDescription = (description) => onChange({ ...node, data: { ...node.data, description } });
|
|
428
676
|
const setConfigValue = (key, value) => onChange({ ...node, data: { ...node.data, config: { ...config, [key]: value } } });
|
|
429
677
|
const issues = validateConfig(kind, config);
|
|
678
|
+
const adapter = getRichInputAdapter();
|
|
679
|
+
const documentField = renderDocumentField ?? (adapter?.renderEditor ? ({ value, onChange: set }) => adapter.renderEditor({ value, onChange: set }) : void 0);
|
|
430
680
|
return /* @__PURE__ */ jsxs("aside", { className: ["ff-panel", className ?? ""].filter(Boolean).join(" "), style, children: [
|
|
431
681
|
header,
|
|
432
682
|
/* @__PURE__ */ jsxs("header", { className: "ff-panel__header", children: [
|
|
@@ -475,7 +725,8 @@ function NodeConfigPanel({
|
|
|
475
725
|
field,
|
|
476
726
|
value: config[field.key],
|
|
477
727
|
onChange: (v) => setConfigValue(field.key, v),
|
|
478
|
-
renderCredentialField
|
|
728
|
+
renderCredentialField,
|
|
729
|
+
renderDocumentField: documentField
|
|
479
730
|
}
|
|
480
731
|
)
|
|
481
732
|
] }, field.key))
|
|
@@ -523,6 +774,17 @@ function removeEdges(edges, ids) {
|
|
|
523
774
|
const doomed = new Set(ids);
|
|
524
775
|
return edges.filter((e) => !doomed.has(e.id));
|
|
525
776
|
}
|
|
777
|
+
function setEdgeLabel(edges, id, label) {
|
|
778
|
+
const next = label?.trim();
|
|
779
|
+
return edges.map((e) => {
|
|
780
|
+
if (e.id !== id) return e;
|
|
781
|
+
if (!next) {
|
|
782
|
+
const { label: _drop, ...rest } = e;
|
|
783
|
+
return rest;
|
|
784
|
+
}
|
|
785
|
+
return { ...e, label: next };
|
|
786
|
+
});
|
|
787
|
+
}
|
|
526
788
|
function duplicateNode(node, id, offset = 40) {
|
|
527
789
|
return {
|
|
528
790
|
...node,
|
|
@@ -570,6 +832,7 @@ function FlowEditorInner({
|
|
|
570
832
|
onChange,
|
|
571
833
|
onSelectionChange,
|
|
572
834
|
onDelete,
|
|
835
|
+
onEdgeDelete,
|
|
573
836
|
apiRef
|
|
574
837
|
}) {
|
|
575
838
|
const internal = useFlowState(initial);
|
|
@@ -587,18 +850,39 @@ function FlowEditorInner({
|
|
|
587
850
|
const [selectedId, setSelectedId] = useState(null);
|
|
588
851
|
const selected = useMemo(() => flow.nodes.find((n) => n.id === selectedId) ?? null, [flow.nodes, selectedId]);
|
|
589
852
|
useEffect(() => onSelectionChange?.(selected), [selected, onSelectionChange]);
|
|
590
|
-
const
|
|
853
|
+
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
|
854
|
+
const selectedEdge = useMemo(
|
|
855
|
+
() => flow.edges.find((e) => e.id === selectedEdgeId) ?? null,
|
|
856
|
+
[flow.edges, selectedEdgeId]
|
|
857
|
+
);
|
|
858
|
+
const handleNodeClick = (_e, node) => {
|
|
859
|
+
setSelectedId(node.id);
|
|
860
|
+
setSelectedEdgeId(null);
|
|
861
|
+
};
|
|
591
862
|
const [menu, setMenu] = useState(null);
|
|
863
|
+
const [labelEdit, setLabelEdit] = useState(null);
|
|
592
864
|
const closeMenu = useCallback(() => setMenu(null), []);
|
|
593
865
|
const handleNodeContextMenu = (event, node) => {
|
|
594
866
|
event.preventDefault();
|
|
595
867
|
setSelectedId(node.id);
|
|
596
|
-
|
|
868
|
+
setSelectedEdgeId(null);
|
|
869
|
+
setMenu({ x: event.clientX, y: event.clientY, target: { type: "node", id: node.id } });
|
|
870
|
+
};
|
|
871
|
+
const handleEdgeClick = (_e, edge) => {
|
|
872
|
+
setSelectedEdgeId(edge.id);
|
|
873
|
+
};
|
|
874
|
+
const handleEdgeContextMenu = (event, edge) => {
|
|
875
|
+
event.preventDefault();
|
|
876
|
+
setSelectedEdgeId(edge.id);
|
|
877
|
+
setMenu({ x: event.clientX, y: event.clientY, target: { type: "edge", id: edge.id } });
|
|
597
878
|
};
|
|
598
879
|
useEffect(() => {
|
|
599
|
-
if (menu === null) return;
|
|
600
|
-
const close = () =>
|
|
601
|
-
|
|
880
|
+
if (menu === null && labelEdit === null) return;
|
|
881
|
+
const close = () => {
|
|
882
|
+
setMenu(null);
|
|
883
|
+
setLabelEdit(null);
|
|
884
|
+
};
|
|
885
|
+
const onKey = (e) => e.key === "Escape" && close();
|
|
602
886
|
window.addEventListener("click", close);
|
|
603
887
|
window.addEventListener("scroll", close, true);
|
|
604
888
|
window.addEventListener("keydown", onKey);
|
|
@@ -607,7 +891,7 @@ function FlowEditorInner({
|
|
|
607
891
|
window.removeEventListener("scroll", close, true);
|
|
608
892
|
window.removeEventListener("keydown", onKey);
|
|
609
893
|
};
|
|
610
|
-
}, [menu]);
|
|
894
|
+
}, [menu, labelEdit]);
|
|
611
895
|
useEffect(() => {
|
|
612
896
|
if (!controlled) onChange?.({ nodes: flow.nodes, edges: flow.edges });
|
|
613
897
|
}, [flow.nodes, flow.edges, onChange, controlled]);
|
|
@@ -650,14 +934,29 @@ function FlowEditorInner({
|
|
|
650
934
|
edges: flow.edges,
|
|
651
935
|
selectedId,
|
|
652
936
|
selected,
|
|
937
|
+
selectedEdgeId,
|
|
938
|
+
selectedEdge,
|
|
653
939
|
running: runner.running,
|
|
654
940
|
statuses: runner.statuses,
|
|
655
941
|
select: setSelectedId,
|
|
942
|
+
selectEdge: setSelectedEdgeId,
|
|
656
943
|
addNode,
|
|
657
944
|
updateNode: (next) => flow.setNodes((all) => all.map((x) => x.id === next.id ? next : x)),
|
|
658
945
|
deleteNodes,
|
|
659
946
|
deleteSelected: () => deleteNodes(selectedId ? [selectedId] : []),
|
|
660
|
-
deleteEdges: (ids) =>
|
|
947
|
+
deleteEdges: (ids) => {
|
|
948
|
+
if (ids.length === 0) return;
|
|
949
|
+
flow.setEdges((all) => removeEdges(all, ids));
|
|
950
|
+
setSelectedEdgeId((cur) => cur !== null && ids.includes(cur) ? null : cur);
|
|
951
|
+
onEdgeDelete?.(ids);
|
|
952
|
+
},
|
|
953
|
+
deleteSelectedEdge: () => {
|
|
954
|
+
if (!selectedEdgeId) return;
|
|
955
|
+
flow.setEdges((all) => removeEdges(all, [selectedEdgeId]));
|
|
956
|
+
setSelectedEdgeId(null);
|
|
957
|
+
onEdgeDelete?.([selectedEdgeId]);
|
|
958
|
+
},
|
|
959
|
+
setEdgeLabel: (id, label) => flow.setEdges((all) => setEdgeLabel(all, id, label)),
|
|
661
960
|
duplicateNode: (id) => {
|
|
662
961
|
const src = flow.nodes.find((n) => n.id === id);
|
|
663
962
|
if (!src) return null;
|
|
@@ -729,6 +1028,9 @@ function FlowEditorInner({
|
|
|
729
1028
|
onConnect: flow.onConnect,
|
|
730
1029
|
onNodeClick: handleNodeClick,
|
|
731
1030
|
onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
|
|
1031
|
+
onEdgeClick: handleEdgeClick,
|
|
1032
|
+
onEdgeContextMenu: builtins.edgeContextMenu === false ? void 0 : handleEdgeContextMenu,
|
|
1033
|
+
onEdgesDelete: (deleted) => onEdgeDelete?.(deleted.map((e) => e.id)),
|
|
732
1034
|
onNodesDelete: (deleted) => onDelete?.(deleted.map((n) => n.id)),
|
|
733
1035
|
deleteKeyCode: ["Delete", "Backspace"],
|
|
734
1036
|
height: "100%",
|
|
@@ -737,14 +1039,14 @@ function FlowEditorInner({
|
|
|
737
1039
|
}
|
|
738
1040
|
),
|
|
739
1041
|
slots.empty && api.nodes.length === 0 && /* @__PURE__ */ jsx("div", { className: "ff-editor__empty", children: slots.empty(api) }),
|
|
740
|
-
menu
|
|
1042
|
+
menu?.target.type === "node" && builtins.contextMenu !== false && /* @__PURE__ */ jsx(
|
|
741
1043
|
"div",
|
|
742
1044
|
{
|
|
743
1045
|
className: "ff-editor__ctx",
|
|
744
1046
|
style: { top: menu.y, left: menu.x },
|
|
745
1047
|
role: "menu",
|
|
746
1048
|
onClick: (e) => e.stopPropagation(),
|
|
747
|
-
children: slots.contextMenu ? slots.contextMenu(api, menu.
|
|
1049
|
+
children: slots.contextMenu ? slots.contextMenu(api, menu.target.id, closeMenu) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
748
1050
|
/* @__PURE__ */ jsx(
|
|
749
1051
|
"button",
|
|
750
1052
|
{
|
|
@@ -753,7 +1055,7 @@ function FlowEditorInner({
|
|
|
753
1055
|
className: "ff-editor__ctx-item",
|
|
754
1056
|
"data-action": "ctx-duplicate",
|
|
755
1057
|
onClick: () => {
|
|
756
|
-
api.duplicateNode(menu.
|
|
1058
|
+
api.duplicateNode(menu.target.id);
|
|
757
1059
|
closeMenu();
|
|
758
1060
|
},
|
|
759
1061
|
children: "Duplicate"
|
|
@@ -767,7 +1069,7 @@ function FlowEditorInner({
|
|
|
767
1069
|
className: "ff-editor__ctx-item ff-editor__ctx-item--danger",
|
|
768
1070
|
"data-action": "ctx-delete",
|
|
769
1071
|
onClick: () => {
|
|
770
|
-
api.deleteNodes([menu.
|
|
1072
|
+
api.deleteNodes([menu.target.id]);
|
|
771
1073
|
closeMenu();
|
|
772
1074
|
},
|
|
773
1075
|
children: "Delete"
|
|
@@ -776,6 +1078,58 @@ function FlowEditorInner({
|
|
|
776
1078
|
] })
|
|
777
1079
|
}
|
|
778
1080
|
),
|
|
1081
|
+
menu?.target.type === "edge" && builtins.edgeContextMenu !== false && /* @__PURE__ */ jsx(
|
|
1082
|
+
"div",
|
|
1083
|
+
{
|
|
1084
|
+
className: "ff-editor__ctx",
|
|
1085
|
+
style: { top: menu.y, left: menu.x },
|
|
1086
|
+
role: "menu",
|
|
1087
|
+
onClick: (e) => e.stopPropagation(),
|
|
1088
|
+
children: slots.edgeContextMenu ? slots.edgeContextMenu(api, menu.target.id, closeMenu) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1089
|
+
/* @__PURE__ */ jsx(
|
|
1090
|
+
"button",
|
|
1091
|
+
{
|
|
1092
|
+
type: "button",
|
|
1093
|
+
role: "menuitem",
|
|
1094
|
+
className: "ff-editor__ctx-item",
|
|
1095
|
+
"data-action": "ctx-edge-label",
|
|
1096
|
+
onClick: () => {
|
|
1097
|
+
setLabelEdit({ x: menu.x, y: menu.y, edgeId: menu.target.id });
|
|
1098
|
+
setMenu(null);
|
|
1099
|
+
},
|
|
1100
|
+
children: "Label\u2026"
|
|
1101
|
+
}
|
|
1102
|
+
),
|
|
1103
|
+
/* @__PURE__ */ jsx(
|
|
1104
|
+
"button",
|
|
1105
|
+
{
|
|
1106
|
+
type: "button",
|
|
1107
|
+
role: "menuitem",
|
|
1108
|
+
className: "ff-editor__ctx-item ff-editor__ctx-item--danger",
|
|
1109
|
+
"data-action": "ctx-edge-delete",
|
|
1110
|
+
onClick: () => {
|
|
1111
|
+
api.deleteEdges([menu.target.id]);
|
|
1112
|
+
closeMenu();
|
|
1113
|
+
},
|
|
1114
|
+
children: "Delete connection"
|
|
1115
|
+
}
|
|
1116
|
+
)
|
|
1117
|
+
] })
|
|
1118
|
+
}
|
|
1119
|
+
),
|
|
1120
|
+
labelEdit !== null && /* @__PURE__ */ jsx(
|
|
1121
|
+
EdgeLabelEditor,
|
|
1122
|
+
{
|
|
1123
|
+
x: labelEdit.x,
|
|
1124
|
+
y: labelEdit.y,
|
|
1125
|
+
initial: flow.edges.find((e) => e.id === labelEdit.edgeId)?.label ?? "",
|
|
1126
|
+
onCommit: (text) => {
|
|
1127
|
+
api.setEdgeLabel(labelEdit.edgeId, text);
|
|
1128
|
+
setLabelEdit(null);
|
|
1129
|
+
},
|
|
1130
|
+
onCancel: () => setLabelEdit(null)
|
|
1131
|
+
}
|
|
1132
|
+
),
|
|
779
1133
|
showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" }))
|
|
780
1134
|
] }),
|
|
781
1135
|
showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxs("div", { className: "ff-editor__panel-wrap", children: [
|
|
@@ -852,6 +1206,46 @@ function makeControlledFlowAdapter(value, onChange) {
|
|
|
852
1206
|
toGraph: () => value
|
|
853
1207
|
};
|
|
854
1208
|
}
|
|
1209
|
+
function EdgeLabelEditor({
|
|
1210
|
+
x,
|
|
1211
|
+
y,
|
|
1212
|
+
initial,
|
|
1213
|
+
onCommit,
|
|
1214
|
+
onCancel
|
|
1215
|
+
}) {
|
|
1216
|
+
const [text, setText] = useState(initial);
|
|
1217
|
+
const ref = useRef(null);
|
|
1218
|
+
useEffect(() => {
|
|
1219
|
+
ref.current?.focus();
|
|
1220
|
+
ref.current?.select();
|
|
1221
|
+
}, []);
|
|
1222
|
+
return /* @__PURE__ */ jsx(
|
|
1223
|
+
"div",
|
|
1224
|
+
{
|
|
1225
|
+
className: "ff-editor__ctx ff-editor__edge-label",
|
|
1226
|
+
style: { top: y, left: x },
|
|
1227
|
+
onClick: (e) => e.stopPropagation(),
|
|
1228
|
+
children: /* @__PURE__ */ jsx(
|
|
1229
|
+
"input",
|
|
1230
|
+
{
|
|
1231
|
+
ref,
|
|
1232
|
+
className: "ff-panel__input",
|
|
1233
|
+
value: text,
|
|
1234
|
+
placeholder: "Label this connection",
|
|
1235
|
+
"aria-label": "Connection label",
|
|
1236
|
+
"data-action": "edge-label-input",
|
|
1237
|
+
onChange: (e) => setText(e.target.value),
|
|
1238
|
+
onKeyDown: (e) => {
|
|
1239
|
+
e.stopPropagation();
|
|
1240
|
+
if (e.key === "Enter") onCommit(text);
|
|
1241
|
+
if (e.key === "Escape") onCancel();
|
|
1242
|
+
},
|
|
1243
|
+
onBlur: () => onCommit(text)
|
|
1244
|
+
}
|
|
1245
|
+
)
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
855
1249
|
function defineNode(render) {
|
|
856
1250
|
function Wrapped(props) {
|
|
857
1251
|
return render({
|