@parallelworks/ui 0.37.0 → 0.39.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/ai/components/ChatInput.js +8 -23
  3. package/dist/ai/components/ChatSidebar.js +7 -4
  4. package/dist/ai/components/chatDraft.d.ts +7 -0
  5. package/dist/ai/components/chatDraft.js +66 -0
  6. package/dist/ai/index.d.ts +1 -1
  7. package/dist/ai/index.js +2 -2
  8. package/dist/ai/ui/sidebar.d.ts +6 -0
  9. package/dist/ai/ui/sidebar.js +15 -1
  10. package/dist/editing.d.ts +2 -0
  11. package/dist/editor/Monaco.js +1 -1
  12. package/dist/editor/index.js +1 -1
  13. package/dist/editor/lintContext.js +15 -3
  14. package/dist/form/FieldWrapper.js +1 -0
  15. package/dist/form/Form.js +64 -23
  16. package/dist/form/Wizard/WizardContainer.js +66 -6
  17. package/dist/form/Wizard/WizardStepContent.js +23 -10
  18. package/dist/form/Wizard/wizardPages.d.ts +12 -0
  19. package/dist/form/Wizard/wizardPages.js +5 -0
  20. package/dist/form/formEditing.d.ts +25 -0
  21. package/dist/form/formEditing.js +20 -0
  22. package/dist/graph/DependencyGraph.d.ts +8 -1
  23. package/dist/graph/DependencyGraph.js +433 -204
  24. package/dist/graph/GraphEditor.d.ts +27 -0
  25. package/dist/graph/GraphEditor.js +1543 -0
  26. package/dist/graph/GraphEditorDialogs.d.ts +87 -0
  27. package/dist/graph/GraphEditorDialogs.js +1808 -0
  28. package/dist/graph/InputDialog.js +1568 -54
  29. package/dist/graph/InputsEditor.d.ts +12 -0
  30. package/dist/graph/InputsEditor.js +1594 -0
  31. package/dist/graph/JobSummary.d.ts +3 -0
  32. package/dist/graph/JobSummary.js +34 -21
  33. package/dist/graph/MatrixGroup.d.ts +2 -1
  34. package/dist/graph/MatrixGroup.js +10 -3
  35. package/dist/graph/ShortcutsButton.js +56 -0
  36. package/dist/graph/SuggestionInput.js +29 -0
  37. package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
  38. package/dist/graph/WorkflowSettingsDialog.js +670 -0
  39. package/dist/graph/editorApi.js +8 -0
  40. package/dist/graph/editorChrome.js +75 -0
  41. package/dist/graph/editorFields.js +535 -0
  42. package/dist/graph/editorPrimitives.js +95 -4
  43. package/dist/graph/editorRows.d.ts +58 -0
  44. package/dist/graph/editorRows.js +460 -0
  45. package/dist/graph/editorStrings.d.ts +1 -0
  46. package/dist/graph/editorStrings.js +10 -1
  47. package/dist/graph/expressionRefs.js +39 -0
  48. package/dist/graph/fieldProblems.js +111 -0
  49. package/dist/graph/graphEditorState.d.ts +191 -0
  50. package/dist/graph/graphEditorState.js +74 -0
  51. package/dist/graph/index.d.ts +4 -0
  52. package/dist/graph/index.js +6 -2
  53. package/dist/graph/inputLayout.js +337 -0
  54. package/dist/graph/inputPages.d.ts +33 -0
  55. package/dist/graph/inputPages.js +186 -0
  56. package/dist/graph/inputRefs.js +388 -0
  57. package/dist/graph/inputRows.d.ts +43 -0
  58. package/dist/graph/inputRows.js +65 -0
  59. package/dist/graph/refusalText.js +13 -0
  60. package/dist/graph/settingsViews.js +62 -0
  61. package/dist/graph/stepWith.d.ts +38 -0
  62. package/dist/graph/stepWith.js +536 -0
  63. package/dist/styles.css +1 -1
  64. package/package.json +1 -1
@@ -1,6 +1,28 @@
1
- import "classnames";
2
- import "react";
3
- import "react/jsx-runtime";
1
+ import { useWorkflowEditing, useWorkflowEngine } from "../components/Provider.js";
2
+ import { BiSolidTrashAlt } from "../icons/bi/BiSolidTrashAlt.js";
3
+ import { FaArrowDown } from "../icons/fa/FaArrowDown.js";
4
+ import { FaArrowUp } from "../icons/fa/FaArrowUp.js";
5
+ import { FaEdit } from "../icons/fa/FaEdit.js";
6
+ import { IoMdClose } from "../icons/io/IoMdClose.js";
7
+ import { withPositionKeys } from "../components/keys.js";
8
+ import Dropdown from "../components/Dropdown.js";
9
+ import { FilterPill } from "../components/FilterPill.js";
10
+ import { IconButton } from "../components/IconButton.js";
11
+ import { Input, Textarea } from "../components/Input.js";
12
+ import { useGraphEditorStrings, useInputsEditorStrings } from "./editorStrings.js";
13
+ import { asRecord } from "./records.js";
14
+ import { INPUT_YAML_PATH } from "../editor/settingsYaml.js";
15
+ import { FIELD_TEXT_BOX, SuggestionInput } from "./SuggestionInput.js";
16
+ import { AddRowButton, ChoiceButtons, DialogShell, EXPRESSION, ExpressionToggle, FieldError, GrowingTextarea, KeyHint, LabelledField, Section, SectionMemory, StringListEditor, SuggestedInput, ToggleField, diffPatch, expressionError, flagError, isEmptyPatch, isScalar, labelled, parseJson, parseScalar, sameValue, text, unchangedValue, useSectionMemory, withoutUndefined } from "./editorFields.js";
17
+ import { FlagField, ValueOrInputField, inputRefs } from "./inputRefs.js";
18
+ import { formatDuration, parseDuration } from "../form/utils/duration.js";
19
+ import { FormEditingContext } from "../form/formEditing.js";
20
+ import { DynamicForm } from "../form/Form.js";
21
+ import { withInputLayouts } from "./inputLayout.js";
22
+ import { NO_SCOPED, ViewSwitch, YamlPane, yamlProblem } from "./settingsViews.js";
23
+ import cx from "classnames";
24
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
25
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
26
  //#region src/graph/InputDialog.tsx
5
27
  /** Input types by menu group, in the order the add menu lists them. */
6
28
  var INPUT_TYPE_GROUPS = [
@@ -200,6 +222,54 @@ var IN_NAMESPACE = [
200
222
  NAMESPACE,
201
223
  AUTOSELECT
202
224
  ];
225
+ var SLIDER = {
226
+ key: "slider",
227
+ kind: "flag",
228
+ help: "slider"
229
+ };
230
+ var FLATTEN = {
231
+ key: "flatten",
232
+ kind: "bool",
233
+ help: "flatten"
234
+ };
235
+ var TEMPLATE = {
236
+ key: "template",
237
+ kind: "template",
238
+ help: "template",
239
+ required: true
240
+ };
241
+ var LIST_DEFAULT = {
242
+ key: "default",
243
+ kind: "rows",
244
+ help: "listDefault"
245
+ };
246
+ var DURATION_DEFAULT = {
247
+ key: "default",
248
+ kind: "duration",
249
+ help: "durationDefault"
250
+ };
251
+ var DISABLE_LABEL = {
252
+ key: "disableLabel",
253
+ kind: "text",
254
+ help: "disableLabel",
255
+ prose: true
256
+ };
257
+ var DISABLE_VALUE = {
258
+ key: "disableValue",
259
+ kind: "number",
260
+ help: "disableValue"
261
+ };
262
+ var OPTION_KEY = {
263
+ key: "option-key",
264
+ kind: "ref",
265
+ help: "optionKey",
266
+ refTypes: [
267
+ "dropdown",
268
+ "radio",
269
+ "string"
270
+ ],
271
+ literal: false
272
+ };
203
273
  var TYPE_PROPS = {
204
274
  string: [
205
275
  DEFAULT,
@@ -259,11 +329,7 @@ var TYPE_PROPS = {
259
329
  kind: "number",
260
330
  help: "step"
261
331
  },
262
- {
263
- key: "slider",
264
- kind: "flag",
265
- help: "slider"
266
- }
332
+ SLIDER
267
333
  ],
268
334
  boolean: [{
269
335
  key: "default",
@@ -283,17 +349,7 @@ var TYPE_PROPS = {
283
349
  "color-picker": [DEFAULT],
284
350
  dropdown: [
285
351
  OPTIONS,
286
- {
287
- key: "option-key",
288
- kind: "ref",
289
- help: "optionKey",
290
- refTypes: [
291
- "dropdown",
292
- "radio",
293
- "string"
294
- ],
295
- literal: false
296
- },
352
+ OPTION_KEY,
297
353
  DEFAULT_OBJECT,
298
354
  PLACEHOLDER,
299
355
  AUTOSELECT
@@ -319,11 +375,7 @@ var TYPE_PROPS = {
319
375
  }
320
376
  ],
321
377
  duration: [
322
- {
323
- key: "default",
324
- kind: "duration",
325
- help: "durationDefault"
326
- },
378
+ DURATION_DEFAULT,
327
379
  PLACEHOLDER,
328
380
  {
329
381
  key: "min",
@@ -335,40 +387,18 @@ var TYPE_PROPS = {
335
387
  kind: "duration",
336
388
  help: "maxDuration"
337
389
  },
338
- {
339
- key: "disableLabel",
340
- kind: "text",
341
- help: "disableLabel",
342
- prose: true
343
- },
344
- {
345
- key: "disableValue",
346
- kind: "number",
347
- help: "disableValue"
348
- }
390
+ DISABLE_LABEL,
391
+ DISABLE_VALUE
349
392
  ],
350
393
  group: [{
351
394
  key: "collapsed",
352
395
  kind: "flag",
353
396
  help: "collapsed",
354
397
  conditional: true
355
- }, {
356
- key: "flatten",
357
- kind: "bool",
358
- help: "flatten"
359
- }],
398
+ }, FLATTEN],
360
399
  list: [
361
- {
362
- key: "template",
363
- kind: "template",
364
- help: "template",
365
- required: true
366
- },
367
- {
368
- key: "default",
369
- kind: "rows",
370
- help: "listDefault"
371
- },
400
+ TEMPLATE,
401
+ LIST_DEFAULT,
372
402
  {
373
403
  key: "items-collapsible",
374
404
  kind: "flag",
@@ -552,7 +582,22 @@ var COMMON_KEYS = {
552
582
  step: ["description"]
553
583
  };
554
584
  var ALL_COMMON = [...TEXT_FIELDS, ...FLAG_FIELDS];
555
- [...ALL_COMMON, ...Object.values(TYPE_PROPS).flatMap((props) => props.map((prop) => prop.key))];
585
+ var KNOWN_KEYS = /* @__PURE__ */ new Set([
586
+ "type",
587
+ ...ALL_COMMON,
588
+ ...Object.values(TYPE_PROPS).flatMap((props) => props.map((prop) => prop.key)),
589
+ "items",
590
+ "template",
591
+ "options"
592
+ ]);
593
+ var LANGUAGES = [
594
+ "bash",
595
+ "python",
596
+ "yaml",
597
+ "json",
598
+ "javascript",
599
+ "shell"
600
+ ];
556
601
  function inputTypes() {
557
602
  return INPUT_TYPE_GROUPS.flatMap(([, types]) => types);
558
603
  }
@@ -569,6 +614,972 @@ function offeredInputKeys(editing, type) {
569
614
  ...children ? [children] : []
570
615
  ];
571
616
  }
617
+ /** The starting definition of a new input of `type`, with what the schema requires. */
618
+ function newInputDefinition(type) {
619
+ switch (type) {
620
+ case "dropdown":
621
+ case "multi-dropdown":
622
+ case "radio":
623
+ case "checkbox-group": return {
624
+ type,
625
+ options: [{
626
+ value: "one",
627
+ label: "One"
628
+ }, {
629
+ value: "two",
630
+ label: "Two"
631
+ }]
632
+ };
633
+ case "group": return {
634
+ type,
635
+ items: {}
636
+ };
637
+ case "list": return {
638
+ type,
639
+ template: { value: { type: "string" } }
640
+ };
641
+ case "header": return {
642
+ type,
643
+ text: ""
644
+ };
645
+ case "step": return {
646
+ type,
647
+ title: "",
648
+ options: {}
649
+ };
650
+ default: return { type };
651
+ }
652
+ }
653
+ function optionsDraft(value) {
654
+ if (typeof value === "string") return {
655
+ mode: "expression",
656
+ rows: [],
657
+ text: value
658
+ };
659
+ if (Array.isArray(value)) {
660
+ const rows = [];
661
+ for (const item of value) if (isScalar(item)) rows.push({
662
+ value: String(item),
663
+ label: "",
664
+ bare: true,
665
+ original: item
666
+ });
667
+ else if (typeof item === "object" && item !== null) {
668
+ const { value: optionValue, label, ...rest } = item;
669
+ if (!isScalar(optionValue) || label !== void 0 && !isScalar(label)) return {
670
+ mode: "json",
671
+ rows: [],
672
+ text: JSON.stringify(value, null, 2)
673
+ };
674
+ rows.push({
675
+ value: String(optionValue),
676
+ original: optionValue,
677
+ label: text(label),
678
+ ...Object.keys(rest).length > 0 ? { rest } : {}
679
+ });
680
+ }
681
+ return {
682
+ mode: "list",
683
+ rows,
684
+ text: ""
685
+ };
686
+ }
687
+ if (value !== void 0 && value !== null) return {
688
+ mode: "byKey",
689
+ rows: [],
690
+ text: JSON.stringify(value, null, 2)
691
+ };
692
+ return {
693
+ mode: "list",
694
+ rows: [],
695
+ text: ""
696
+ };
697
+ }
698
+ function optionsValue(draft, { labelled = false, textValues = false }) {
699
+ if (draft.mode === "expression") return draft.text.trim() || void 0;
700
+ if (draft.mode === "byKey" || draft.mode === "json") return draft.text.trim() ? parseJson(draft.text).value : void 0;
701
+ return draft.rows.map((row) => {
702
+ const written = unchangedValue(row.value, row.original) ? row.original : parseScalar(row.value.trim());
703
+ const value = textValues && isScalar(written) ? String(written) : written;
704
+ if (row.bare && !row.label.trim() && !row.rest && typeof value === "string") return value;
705
+ const label = row.label.trim() || (labelled || typeof value !== "string" ? row.value.trim() : "");
706
+ return {
707
+ value,
708
+ ...label ? { label } : {},
709
+ ...row.rest
710
+ };
711
+ });
712
+ }
713
+ function optionsError(draft, t, g) {
714
+ if (draft.mode === "expression") return draft.text.trim() ? expressionError(draft.text, g) : g.required;
715
+ if (draft.mode === "byKey" || draft.mode === "json") {
716
+ const parsed = parseJson(draft.text);
717
+ if (!parsed.ok) return g.invalidJson;
718
+ if (draft.mode === "json") return Array.isArray(parsed.value) ? void 0 : t.optionsJsonShape;
719
+ const keyed = asRecord(parsed.value);
720
+ return parsed.value === keyed && Object.values(keyed).length > 0 && Object.values(keyed).every(Array.isArray) ? void 0 : t.optionsByKeyShape;
721
+ }
722
+ return draft.rows.length === 0 || draft.rows.some((row) => !row.value.trim()) ? t.needsOptions : void 0;
723
+ }
724
+ function OptionsEditor({ draft, onChange, error, keyed, described }) {
725
+ const t = useInputsEditorStrings();
726
+ const update = (i, patch) => onChange({
727
+ ...draft,
728
+ rows: draft.rows.map((row, j) => j === i ? {
729
+ ...row,
730
+ ...patch
731
+ } : row)
732
+ });
733
+ const modes = [
734
+ {
735
+ value: "list",
736
+ label: t.optionsList
737
+ },
738
+ ...keyed || draft.mode === "byKey" ? [{
739
+ value: "byKey",
740
+ label: t.optionsByKey
741
+ }] : [],
742
+ ...draft.mode === "json" ? [{
743
+ value: "json",
744
+ label: t.optionsJson
745
+ }] : []
746
+ ];
747
+ return /* @__PURE__ */ jsxs("div", {
748
+ className: "flex flex-col gap-2",
749
+ children: [
750
+ draft.mode !== "expression" && modes.length > 1 && /* @__PURE__ */ jsx(ChoiceButtons, {
751
+ options: modes,
752
+ value: draft.mode,
753
+ onChange: (mode) => onChange({
754
+ ...draft,
755
+ mode
756
+ })
757
+ }),
758
+ draft.mode === "list" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
759
+ draft.rows.length > 0 && /* @__PURE__ */ jsxs("div", {
760
+ "aria-hidden": "true",
761
+ className: "flex items-center gap-2 text-xs theme-muted-text",
762
+ children: [
763
+ /* @__PURE__ */ jsx("div", {
764
+ className: "w-2/5",
765
+ children: t.optionValue
766
+ }),
767
+ /* @__PURE__ */ jsx("div", {
768
+ className: "flex-1",
769
+ children: t.optionLabel
770
+ }),
771
+ described && /* @__PURE__ */ jsx("div", {
772
+ className: "flex-1",
773
+ children: t.optionDescription
774
+ }),
775
+ /* @__PURE__ */ jsx("div", { className: "w-7 shrink-0" })
776
+ ]
777
+ }),
778
+ withPositionKeys(draft.rows).map(({ key, item: row }, i) => /* @__PURE__ */ jsxs("div", {
779
+ className: "flex items-center gap-2",
780
+ children: [
781
+ /* @__PURE__ */ jsx("div", {
782
+ className: "w-2/5",
783
+ children: /* @__PURE__ */ jsx(Input, {
784
+ mono: true,
785
+ "aria-label": t.optionValue,
786
+ placeholder: t.optionValue,
787
+ value: row.value,
788
+ onChange: (e) => update(i, { value: e.target.value })
789
+ })
790
+ }),
791
+ /* @__PURE__ */ jsx("div", {
792
+ className: "flex-1",
793
+ children: /* @__PURE__ */ jsx(Input, {
794
+ "aria-label": t.optionLabel,
795
+ placeholder: t.optionLabel,
796
+ value: row.label,
797
+ onChange: (e) => update(i, { label: e.target.value })
798
+ })
799
+ }),
800
+ described && /* @__PURE__ */ jsx("div", {
801
+ className: "flex-1",
802
+ children: /* @__PURE__ */ jsx(Input, {
803
+ "aria-label": t.optionDescription,
804
+ placeholder: t.optionDescription,
805
+ value: text(row.rest?.["description"]),
806
+ onChange: (e) => {
807
+ const { description: _old, ...rest } = row.rest ?? {};
808
+ update(i, { rest: e.target.value ? {
809
+ ...rest,
810
+ description: e.target.value
811
+ } : rest });
812
+ }
813
+ })
814
+ }),
815
+ /* @__PURE__ */ jsx(IconButton, {
816
+ icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
817
+ label: t.removeOption,
818
+ variant: "ghost",
819
+ size: "sm",
820
+ onClick: () => onChange({
821
+ ...draft,
822
+ rows: draft.rows.filter((_, j) => j !== i)
823
+ })
824
+ })
825
+ ]
826
+ }, key)),
827
+ /* @__PURE__ */ jsx(AddRowButton, {
828
+ label: t.addOption,
829
+ onClick: () => onChange({
830
+ ...draft,
831
+ rows: [...draft.rows, {
832
+ value: "",
833
+ label: ""
834
+ }]
835
+ })
836
+ })
837
+ ] }) : draft.mode === "expression" ? /* @__PURE__ */ jsx(Input, {
838
+ mono: true,
839
+ "aria-label": t.fields.options,
840
+ value: draft.text,
841
+ onChange: (e) => onChange({
842
+ ...draft,
843
+ text: e.target.value
844
+ })
845
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
846
+ className: "text-xs theme-muted-text",
847
+ children: draft.mode === "byKey" ? t.optionsByKeyHelp : t.optionsJsonHelp
848
+ }), /* @__PURE__ */ jsx(Textarea, {
849
+ mono: true,
850
+ "aria-label": t.fields.options,
851
+ rows: 6,
852
+ value: draft.text,
853
+ placeholder: draft.mode === "byKey" ? "{\n \"aws\": [\"us-east-1\", \"us-west-2\"],\n \"google\": [\"us-central1\"]\n}" : void 0,
854
+ onChange: (e) => onChange({
855
+ ...draft,
856
+ text: e.target.value
857
+ })
858
+ })] }),
859
+ /* @__PURE__ */ jsx(FieldError, { message: error })
860
+ ]
861
+ });
862
+ }
863
+ function impliesDraft(value) {
864
+ return Object.fromEntries(Object.entries(asRecord(value)).map(([key, list]) => [key, Array.isArray(list) ? list.map((item) => String(item)) : []]));
865
+ }
866
+ /** Each option with the others as switches: ticking that option in the form ticks these too. */
867
+ function ImpliesEditor({ draft, options, onChange }) {
868
+ const t = useInputsEditorStrings();
869
+ const listed = options.filter((option) => option.value.trim());
870
+ if (listed.length < 2) return /* @__PURE__ */ jsx("div", {
871
+ className: "text-xs theme-muted-text",
872
+ children: t.impliesNeedsOptions
873
+ });
874
+ const nameOf = (value) => listed.find((option) => option.value === value)?.label.trim() || value;
875
+ const toggle = (from, to) => {
876
+ const current = draft[from] ?? [];
877
+ onChange({
878
+ ...draft,
879
+ [from]: current.includes(to) ? current.filter((value) => value !== to) : [...current, to]
880
+ });
881
+ };
882
+ return /* @__PURE__ */ jsx("div", {
883
+ className: "flex flex-col gap-2",
884
+ children: listed.map((option) => /* @__PURE__ */ jsxs("div", {
885
+ className: "flex flex-wrap items-center gap-1.5",
886
+ children: [/* @__PURE__ */ jsx("span", {
887
+ className: "mr-1 text-sm font-medium",
888
+ children: t.impliesWhen(nameOf(option.value))
889
+ }), listed.filter((other) => other.value !== option.value).map((other) => {
890
+ return /* @__PURE__ */ jsx(FilterPill, {
891
+ active: (draft[option.value] ?? []).includes(other.value),
892
+ onClick: () => toggle(option.value, other.value),
893
+ children: nameOf(other.value)
894
+ }, other.value);
895
+ })]
896
+ }, option.value))
897
+ });
898
+ }
899
+ /** A duration's checkbox that sends a set value in place of a time, such as -1 for none. */
900
+ function OptOutCheckbox({ label, value, onLabel, onValue, errors }) {
901
+ const t = useInputsEditorStrings();
902
+ const [on, setOn] = useState(label.trim() !== "" || value.trim() !== "");
903
+ return /* @__PURE__ */ jsxs("div", {
904
+ className: "flex flex-col gap-3",
905
+ children: [/* @__PURE__ */ jsx(ToggleField, {
906
+ label: t.fields.optOut,
907
+ description: t.help.optOut,
908
+ checked: on,
909
+ onChange: (next) => {
910
+ setOn(next);
911
+ if (!next) {
912
+ onLabel("");
913
+ onValue("");
914
+ }
915
+ }
916
+ }), on && /* @__PURE__ */ jsxs("div", {
917
+ className: "flex flex-col gap-3 border-l theme-border pl-4",
918
+ children: [/* @__PURE__ */ jsx(Input, {
919
+ ...labelled(t.fields.disableLabel, "disableLabel"),
920
+ description: t.help.disableLabel,
921
+ value: label,
922
+ error: errors.label,
923
+ placeholder: t.optOutPlaceholder,
924
+ onChange: (e) => onLabel(e.target.value)
925
+ }), /* @__PURE__ */ jsx(Input, {
926
+ mono: true,
927
+ ...labelled(t.fields.disableValue, "disableValue"),
928
+ description: t.help.disableValue,
929
+ value,
930
+ error: errors.value,
931
+ placeholder: "-1",
932
+ onChange: (e) => onValue(e.target.value)
933
+ })]
934
+ })]
935
+ });
936
+ }
937
+ function TypeSelect({ value, onChange, allowStep, disabled }) {
938
+ const t = useInputsEditorStrings();
939
+ const id = useId();
940
+ const types = t.types;
941
+ const help = t.typeHelp;
942
+ const known = inputTypes().includes(value);
943
+ return /* @__PURE__ */ jsxs("div", {
944
+ className: "flex flex-col gap-1.5",
945
+ children: [
946
+ /* @__PURE__ */ jsxs("div", {
947
+ className: "flex flex-wrap items-baseline gap-x-1.5",
948
+ children: [/* @__PURE__ */ jsx("label", {
949
+ htmlFor: id,
950
+ className: "text-[0.8125rem] font-medium text-(--theme-app)",
951
+ children: t.inputType
952
+ }), /* @__PURE__ */ jsx(KeyHint, {
953
+ label: t.inputType,
954
+ yamlKey: "type"
955
+ })]
956
+ }),
957
+ /* @__PURE__ */ jsx(Dropdown, {
958
+ id,
959
+ value,
960
+ disabled,
961
+ textBoxClassName: FIELD_TEXT_BOX,
962
+ options: [...known ? [] : [{
963
+ label: value,
964
+ value
965
+ }], ...INPUT_TYPE_GROUPS.map(([group, list]) => ({
966
+ category: t.typeGroups[group],
967
+ options: list.filter((type) => allowStep || type !== "step" || value === "step").map((type) => ({
968
+ label: types[type] ?? type,
969
+ value: type
970
+ }))
971
+ }))],
972
+ onChange: (next) => {
973
+ if (typeof next === "string" && next) onChange(next);
974
+ }
975
+ }),
976
+ /* @__PURE__ */ jsx("span", {
977
+ className: "text-xs text-(--theme-muted-text-color)",
978
+ children: help[value] ?? t.help.type
979
+ })
980
+ ]
981
+ });
982
+ }
983
+ var SCALAR_KINDS = /* @__PURE__ */ new Set([
984
+ "text",
985
+ "textarea",
986
+ "number",
987
+ "flag",
988
+ "bool",
989
+ "language",
990
+ "duration",
991
+ "ref",
992
+ "choice"
993
+ ]);
994
+ function effectiveKind(prop, original) {
995
+ if (original === null || typeof original !== "object") return prop.kind;
996
+ return (Array.isArray(original) ? prop.nested === "any" : prop.nested !== void 0) && prop.kind !== "options" && prop.kind !== "template" && prop.kind !== "providers" ? "json" : prop.kind;
997
+ }
998
+ function listItemText(item) {
999
+ if (isScalar(item)) return String(item);
1000
+ const value = asRecord(item)["value"];
1001
+ return isScalar(value) ? String(value) : JSON.stringify(item);
1002
+ }
1003
+ function draftOf(kind, value) {
1004
+ switch (kind) {
1005
+ case "flag": return typeof value === "boolean" || typeof value === "string" ? value : void 0;
1006
+ case "bool": return typeof value === "boolean" ? value : void 0;
1007
+ case "json": return value === void 0 ? "" : JSON.stringify(value, null, 2);
1008
+ case "options": return optionsDraft(value);
1009
+ case "providers": return Array.isArray(value) ? value.join(", ") : text(value);
1010
+ case "template": return typeof value === "string" ? value : asRecord(value);
1011
+ case "implies": return impliesDraft(value);
1012
+ case "rows": return typeof value === "string" ? {
1013
+ expression: value,
1014
+ rows: []
1015
+ } : {
1016
+ expression: void 0,
1017
+ rows: Array.isArray(value) ? value.map(asRecord) : []
1018
+ };
1019
+ case "duration": return typeof value === "number" && value >= 0 ? formatDuration(value) : text(value);
1020
+ case "number": return typeof value === "string" && value.trim() !== "" && !EXPRESSION.test(value.trim()) && Number.isFinite(Number(value)) ? JSON.stringify(value) : text(value);
1021
+ case "perCopy": return Array.isArray(value) ? {
1022
+ list: true,
1023
+ text: "",
1024
+ values: value.map(listItemText)
1025
+ } : {
1026
+ list: false,
1027
+ text: text(value),
1028
+ values: []
1029
+ };
1030
+ case "values": return typeof value === "string" && EXPRESSION.test(value.trim()) ? {
1031
+ expression: value,
1032
+ values: []
1033
+ } : {
1034
+ expression: void 0,
1035
+ values: (Array.isArray(value) ? value : value === void 0 ? [] : [value]).map(listItemText)
1036
+ };
1037
+ default:
1038
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) return value.join("\n");
1039
+ return value !== null && typeof value === "object" ? JSON.stringify(value) : text(value);
1040
+ }
1041
+ }
1042
+ var OPTION_KEYS = {
1043
+ radio: [],
1044
+ "checkbox-group": ["description"]
1045
+ };
1046
+ function textOption(option, keys) {
1047
+ if (isScalar(option)) return String(option);
1048
+ const record = asRecord(option);
1049
+ if (option !== record) return option;
1050
+ return Object.fromEntries(Object.entries(record).filter(([key]) => key === "value" || key === "label" || keys.includes(key)).map(([key, value]) => [key, isScalar(value) ? String(value) : value]));
1051
+ }
1052
+ function fitsTextOption(option, keys) {
1053
+ if (typeof option === "string") return true;
1054
+ const record = asRecord(option);
1055
+ return option === record && typeof record["value"] === "string" && Object.entries(record).every(([key, value]) => (key === "value" || key === "label" || keys.includes(key)) && typeof value === "string");
1056
+ }
1057
+ function writtenValue(kind, draft, context = {}) {
1058
+ switch (kind) {
1059
+ case "flag":
1060
+ case "bool": return draft;
1061
+ case "json": {
1062
+ const raw = String(draft ?? "").trim();
1063
+ return raw ? parseJson(raw).value : void 0;
1064
+ }
1065
+ case "options": return optionsValue(draft, context);
1066
+ case "perCopy": {
1067
+ const perCopy = draft;
1068
+ const values = perCopy.values.map((value) => value.trim()).filter(Boolean);
1069
+ return perCopy.list ? values.length > 0 ? values : void 0 : perCopy.text.trim() || void 0;
1070
+ }
1071
+ case "values": {
1072
+ const list = draft;
1073
+ if (list.expression !== void 0) return list.expression.trim() || void 0;
1074
+ const values = list.values.map((value) => value.trim()).filter(Boolean);
1075
+ if (values.length === 0) return;
1076
+ return context.textValues ? values : values.map(parseScalar);
1077
+ }
1078
+ case "rows": {
1079
+ const list = draft;
1080
+ if (list.expression !== void 0) return list.expression.trim() || void 0;
1081
+ const rows = list.rows.map((row) => Object.fromEntries(Object.entries(row).filter(([, value]) => value !== "" && value !== void 0 && value !== null)));
1082
+ return rows.length > 0 ? rows : void 0;
1083
+ }
1084
+ case "duration": {
1085
+ const raw = String(draft ?? "").trim();
1086
+ if (!raw) return;
1087
+ if (EXPRESSION.test(raw)) return raw;
1088
+ return parseDuration(raw) ?? (/^-\d+$/.test(raw) ? Number(raw) : raw);
1089
+ }
1090
+ case "ref":
1091
+ case "choice": return String(draft ?? "").trim() || void 0;
1092
+ case "number": {
1093
+ const raw = String(draft ?? "").trim();
1094
+ if (!raw) return;
1095
+ return EXPRESSION.test(raw) ? raw : Number(raw);
1096
+ }
1097
+ case "implies": {
1098
+ const entries = Object.entries(draft).filter(([, values]) => values.length > 0);
1099
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
1100
+ }
1101
+ case "providers": {
1102
+ const raw = String(draft ?? "").trim();
1103
+ if (!raw) return;
1104
+ return EXPRESSION.test(raw) ? raw : raw.split(",").map((item) => item.trim()).filter(Boolean);
1105
+ }
1106
+ case "template": return typeof draft === "string" ? draft.trim() || void 0 : draft;
1107
+ case "textarea": return String(draft ?? "") === "" ? void 0 : String(draft);
1108
+ default: {
1109
+ const raw = String(draft ?? "").trim();
1110
+ return raw ? raw : void 0;
1111
+ }
1112
+ }
1113
+ }
1114
+ function valueError(prop, kind, draft, t, g) {
1115
+ const value = writtenValue(kind, draft);
1116
+ if (prop.required && (value === void 0 || value === "")) return g.required;
1117
+ switch (kind) {
1118
+ case "json": {
1119
+ const raw = String(draft ?? "").trim();
1120
+ const parsed = parseJson(raw);
1121
+ if (!raw) return;
1122
+ if (!parsed.ok) return g.invalidJson;
1123
+ const list = Array.isArray(parsed.value);
1124
+ return !list && parsed.value === asRecord(parsed.value) || list && prop.nested === "any" ? void 0 : prop.nested === "any" ? t.jsonObjectOrList : t.jsonObject;
1125
+ }
1126
+ case "values": {
1127
+ const list = draft;
1128
+ return list.expression !== void 0 ? expressionError(list.expression, g) : void 0;
1129
+ }
1130
+ case "rows": {
1131
+ const list = draft;
1132
+ return list.expression !== void 0 ? expressionError(list.expression, g) : void 0;
1133
+ }
1134
+ case "duration": {
1135
+ const raw = String(draft ?? "").trim();
1136
+ return raw && !EXPRESSION.test(raw) && parseDuration(raw) === null ? t.invalidDurationText : void 0;
1137
+ }
1138
+ case "number": {
1139
+ const raw = String(draft ?? "").trim();
1140
+ if (raw && prop.wholeFrom !== void 0) return /^\d+$/.test(raw) && Number(raw) >= prop.wholeFrom ? void 0 : t.wholeNumberFrom(prop.wholeFrom);
1141
+ return raw && !EXPRESSION.test(raw) && !Number.isFinite(Number(raw)) ? t.invalidNumber : void 0;
1142
+ }
1143
+ case "flag": return flagError(draft, g);
1144
+ case "options": return optionsError(draft, t, g);
1145
+ case "ref": {
1146
+ const raw = String(draft ?? "").trim();
1147
+ if (!raw || EXPRESSION.test(raw)) return;
1148
+ if (prop.literal === false) return g.invalidExpressionValue;
1149
+ return prop.choices && !prop.choices.includes(raw) ? g.invalidChoice(prop.key) : void 0;
1150
+ }
1151
+ case "choice": {
1152
+ const raw = String(draft ?? "").trim();
1153
+ return raw && prop.choices && !prop.choices.includes(raw) ? g.invalidChoice(prop.key) : void 0;
1154
+ }
1155
+ case "template":
1156
+ if (typeof draft === "string") return draft.trim() ? expressionError(draft, g) : g.required;
1157
+ return Object.keys(asRecord(draft)).length === 0 ? t.needsFields : void 0;
1158
+ default:
1159
+ if (prop.pattern && typeof value === "string" && !EXPRESSION.test(value)) try {
1160
+ new RegExp(value, "g");
1161
+ } catch {
1162
+ return t.invalidPattern;
1163
+ }
1164
+ return prop.choices && typeof value === "string" && !prop.choices.includes(value) ? g.invalidChoice(prop.key) : void 0;
1165
+ }
1166
+ }
1167
+ /** A list's template fields, edited here since the form repeats them per row. */
1168
+ function TemplateFields({ fields, onChange, error, inputs, adopt, home, onDialog, onFieldKey }) {
1169
+ const t = useInputsEditorStrings();
1170
+ const workflowEditing = useWorkflowEditing();
1171
+ const { freeName } = workflowEditing;
1172
+ const [editing, setEditing] = useState(null);
1173
+ const open = editing !== null;
1174
+ useEffect(() => {
1175
+ onDialog(open);
1176
+ }, [onDialog, open]);
1177
+ const names = Object.keys(fields).filter((name) => name !== "$meta");
1178
+ const types = t.types;
1179
+ const update = (edit) => {
1180
+ const next = workflowEditing.applyGraphEdit({
1181
+ yml: workflowEditing.dumpYaml({ on: { execute: { inputs: fields } } }),
1182
+ layout: void 0
1183
+ }, withInputLayouts(workflowEditing, fields, edit));
1184
+ onChange(asRecord(workflowEditing.workflowInputsSchema(asRecord(workflowEditing.loadYaml(next.yml)))));
1185
+ };
1186
+ const move = (from, to) => {
1187
+ const name = names[from];
1188
+ if (name) update({
1189
+ type: "moveInputs",
1190
+ paths: [[name]],
1191
+ parent: [],
1192
+ index: to > from ? to + 1 : to
1193
+ });
1194
+ };
1195
+ return /* @__PURE__ */ jsxs("div", {
1196
+ className: "flex flex-col gap-1.5",
1197
+ children: [
1198
+ names.length === 0 && /* @__PURE__ */ jsx("div", {
1199
+ className: "text-xs theme-muted-text",
1200
+ children: t.noFields
1201
+ }),
1202
+ names.map((name, i) => {
1203
+ const type = text(asRecord(fields[name])["type"]);
1204
+ return /* @__PURE__ */ jsxs("div", {
1205
+ className: "flex items-center gap-2 rounded-md border theme-border px-2 py-1 text-sm",
1206
+ children: [
1207
+ /* @__PURE__ */ jsx("span", {
1208
+ className: "font-mono",
1209
+ children: name
1210
+ }),
1211
+ /* @__PURE__ */ jsx("span", {
1212
+ className: "rounded border theme-border px-1 text-xs theme-muted-text",
1213
+ children: types[type] ?? type
1214
+ }),
1215
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1216
+ /* @__PURE__ */ jsx(IconButton, {
1217
+ icon: /* @__PURE__ */ jsx(FaEdit, { className: "h-3.5 w-3.5" }),
1218
+ label: t.editInput,
1219
+ variant: "ghost",
1220
+ size: "sm",
1221
+ onClick: () => setEditing({ name })
1222
+ }),
1223
+ /* @__PURE__ */ jsx(IconButton, {
1224
+ icon: /* @__PURE__ */ jsx(FaArrowUp, { className: "h-3.5 w-3.5" }),
1225
+ label: t.moveUp,
1226
+ variant: "ghost",
1227
+ size: "sm",
1228
+ disabled: i === 0,
1229
+ onClick: () => move(i, i - 1)
1230
+ }),
1231
+ /* @__PURE__ */ jsx(IconButton, {
1232
+ icon: /* @__PURE__ */ jsx(FaArrowDown, { className: "h-3.5 w-3.5" }),
1233
+ label: t.moveDown,
1234
+ variant: "ghost",
1235
+ size: "sm",
1236
+ disabled: i === names.length - 1,
1237
+ onClick: () => move(i, i + 1)
1238
+ }),
1239
+ /* @__PURE__ */ jsx(IconButton, {
1240
+ icon: /* @__PURE__ */ jsx(BiSolidTrashAlt, { className: "h-3.5 w-3.5" }),
1241
+ label: t.deleteInput,
1242
+ variant: "ghost",
1243
+ size: "sm",
1244
+ onClick: () => {
1245
+ update({
1246
+ type: "deleteInput",
1247
+ path: [name]
1248
+ });
1249
+ onFieldKey(name, void 0);
1250
+ }
1251
+ })
1252
+ ]
1253
+ }, name);
1254
+ }),
1255
+ /* @__PURE__ */ jsx(FieldError, { message: error }),
1256
+ /* @__PURE__ */ jsx(AddRowButton, {
1257
+ label: t.addField,
1258
+ onClick: () => setEditing({ create: true })
1259
+ }),
1260
+ editing && /* @__PURE__ */ jsx(InputDialog, {
1261
+ name: "name" in editing ? editing.name : nextFieldName(freeName, names),
1262
+ definition: "name" in editing ? asRecord(fields[editing.name]) : newInputDefinition("string"),
1263
+ isNew: !("name" in editing),
1264
+ siblings: names.filter((name) => !("name" in editing) || name !== editing.name),
1265
+ allowStep: false,
1266
+ inputs,
1267
+ home,
1268
+ onClose: () => setEditing(null),
1269
+ onSave: (name, definition, patch, extra) => {
1270
+ adopt(extra);
1271
+ if ("name" in editing) {
1272
+ update({
1273
+ type: "updateInput",
1274
+ path: [editing.name],
1275
+ name,
1276
+ ...patch
1277
+ });
1278
+ if (name !== editing.name) onFieldKey(editing.name, name);
1279
+ } else update({
1280
+ type: "addInput",
1281
+ parent: [],
1282
+ name,
1283
+ definition
1284
+ });
1285
+ setEditing(null);
1286
+ }
1287
+ })
1288
+ ]
1289
+ });
1290
+ }
1291
+ function nextFieldName(freeName, names) {
1292
+ return freeName(names, (n) => `field_${n}`);
1293
+ }
1294
+ function ValuesField({ label, draft, suggestions, onChange, error }) {
1295
+ const t = useInputsEditorStrings();
1296
+ if (draft.expression === void 0) return /* @__PURE__ */ jsx(StringListEditor, {
1297
+ label,
1298
+ values: draft.values,
1299
+ onChange: (values) => onChange({
1300
+ ...draft,
1301
+ values
1302
+ }),
1303
+ addLabel: t.addValue,
1304
+ suggestions,
1305
+ error
1306
+ });
1307
+ return /* @__PURE__ */ jsx(Input, {
1308
+ mono: true,
1309
+ "aria-label": label,
1310
+ value: draft.expression,
1311
+ error,
1312
+ onChange: (e) => onChange({
1313
+ ...draft,
1314
+ expression: e.target.value
1315
+ })
1316
+ });
1317
+ }
1318
+ /** A list's default rows, entered through the same form a run shows. */
1319
+ function RowsField({ draft, template, label, onChange, error }) {
1320
+ const { convertInputs } = useWorkflowEngine();
1321
+ const formJSONs = useMemo(() => convertInputs({ rows: {
1322
+ type: "list",
1323
+ label,
1324
+ template
1325
+ } }), [
1326
+ convertInputs,
1327
+ label,
1328
+ template
1329
+ ]);
1330
+ const touched = useRef(false);
1331
+ const touch = () => {
1332
+ touched.current = true;
1333
+ };
1334
+ if (draft.expression !== void 0) return /* @__PURE__ */ jsx(Input, {
1335
+ mono: true,
1336
+ "aria-label": label,
1337
+ value: draft.expression,
1338
+ error,
1339
+ onChange: (e) => onChange({
1340
+ ...draft,
1341
+ expression: e.target.value
1342
+ })
1343
+ });
1344
+ return /* @__PURE__ */ jsxs("div", {
1345
+ className: "flex flex-col gap-2 rounded-md border theme-border p-2",
1346
+ onClickCapture: touch,
1347
+ onInputCapture: touch,
1348
+ onKeyDownCapture: touch,
1349
+ children: [/* @__PURE__ */ jsx(FormEditingContext.Provider, {
1350
+ value: null,
1351
+ children: /* @__PURE__ */ jsx(DynamicForm, {
1352
+ formJSONs,
1353
+ initialValues: { rows: draft.rows },
1354
+ labelPosition: "top",
1355
+ workflowForm: true,
1356
+ setValues: (values) => {
1357
+ if (touched.current) {
1358
+ const rows = values["rows"];
1359
+ onChange({
1360
+ expression: void 0,
1361
+ rows: Array.isArray(rows) ? rows.map(asRecord) : []
1362
+ });
1363
+ }
1364
+ }
1365
+ }, JSON.stringify(template))
1366
+ }), /* @__PURE__ */ jsx(FieldError, { message: error })]
1367
+ });
1368
+ }
1369
+ function PropField({ prop, kind, draft, original, error, onChange, suggestions, inputType, optionRows, source, adopt, allInputs, home, template, inputLabel, onDialog, onFieldKey }) {
1370
+ const t = useInputsEditorStrings();
1371
+ const description = t.help[prop.help];
1372
+ const name = t.fields[prop.label ?? prop.key] ?? prop.key;
1373
+ const field = {
1374
+ label: name,
1375
+ yamlKey: prop.key,
1376
+ description
1377
+ };
1378
+ switch (kind) {
1379
+ case "flag": return /* @__PURE__ */ jsx(FlagField, {
1380
+ ...field,
1381
+ value: draft,
1382
+ original,
1383
+ fallback: prop.fallback ?? false,
1384
+ onChange,
1385
+ source: prop.conditional ? source : void 0
1386
+ });
1387
+ case "ref": return /* @__PURE__ */ jsx(ValueOrInputField, {
1388
+ ...field,
1389
+ value: String(draft ?? ""),
1390
+ onChange,
1391
+ source,
1392
+ types: prop.refTypes ?? [],
1393
+ literal: prop.literal ?? true,
1394
+ required: prop.required ?? false,
1395
+ error,
1396
+ suggestions: prop.choices ?? []
1397
+ });
1398
+ case "bool": {
1399
+ const fallback = prop.fallback ?? false;
1400
+ return /* @__PURE__ */ jsxs("div", {
1401
+ className: "flex flex-col gap-1.5",
1402
+ children: [/* @__PURE__ */ jsx(ToggleField, {
1403
+ ...field,
1404
+ checked: typeof draft === "boolean" ? draft : fallback,
1405
+ onChange: (on) => onChange(on !== fallback ? on : original === on ? on : void 0)
1406
+ }), /* @__PURE__ */ jsx(FieldError, { message: error })]
1407
+ });
1408
+ }
1409
+ case "options": {
1410
+ const options = draft;
1411
+ return /* @__PURE__ */ jsx(LabelledField, {
1412
+ ...field,
1413
+ actions: /* @__PURE__ */ jsx(ExpressionToggle, {
1414
+ active: options.mode === "expression",
1415
+ onChange: (on) => onChange({
1416
+ ...options,
1417
+ mode: on ? "expression" : "list",
1418
+ text: ""
1419
+ })
1420
+ }),
1421
+ children: /* @__PURE__ */ jsx(OptionsEditor, {
1422
+ draft,
1423
+ onChange,
1424
+ error,
1425
+ keyed: inputType === "dropdown",
1426
+ described: inputType === "checkbox-group"
1427
+ })
1428
+ });
1429
+ }
1430
+ case "perCopy": {
1431
+ const perCopy = draft;
1432
+ return /* @__PURE__ */ jsx(LabelledField, {
1433
+ ...field,
1434
+ actions: /* @__PURE__ */ jsxs("label", {
1435
+ className: "flex cursor-pointer items-center gap-1.5 text-xs theme-muted-text",
1436
+ children: [/* @__PURE__ */ jsx("input", {
1437
+ type: "checkbox",
1438
+ checked: perCopy.list,
1439
+ onChange: (e) => onChange({
1440
+ ...perCopy,
1441
+ list: e.target.checked,
1442
+ values: e.target.checked && perCopy.values.length === 0 && perCopy.text.trim() ? [perCopy.text] : perCopy.values
1443
+ })
1444
+ }), t.onePerCopy]
1445
+ }),
1446
+ children: perCopy.list ? /* @__PURE__ */ jsx(ValuesField, {
1447
+ label: name,
1448
+ draft: {
1449
+ expression: void 0,
1450
+ values: perCopy.values
1451
+ },
1452
+ suggestions: [],
1453
+ onChange: (next) => onChange({
1454
+ ...perCopy,
1455
+ values: next.values
1456
+ }),
1457
+ error
1458
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SuggestionInput, {
1459
+ "aria-label": name,
1460
+ value: perCopy.text,
1461
+ suggestions: [],
1462
+ onChange: (next) => onChange({
1463
+ ...perCopy,
1464
+ text: next
1465
+ })
1466
+ }), /* @__PURE__ */ jsx(FieldError, { message: error })] })
1467
+ });
1468
+ }
1469
+ case "values": return /* @__PURE__ */ jsx(LabelledField, {
1470
+ ...field,
1471
+ actions: /* @__PURE__ */ jsx(ExpressionToggle, {
1472
+ active: draft.expression !== void 0,
1473
+ onChange: (on) => onChange({
1474
+ expression: on ? "" : void 0,
1475
+ values: []
1476
+ })
1477
+ }),
1478
+ children: /* @__PURE__ */ jsx(ValuesField, {
1479
+ label: name,
1480
+ draft,
1481
+ suggestions: inputType === "multi-dropdown" || inputType === "checkbox-group" ? optionRows.map((row) => row.value.trim()).filter(Boolean) : [],
1482
+ onChange,
1483
+ error
1484
+ })
1485
+ });
1486
+ case "rows": return /* @__PURE__ */ jsx(LabelledField, {
1487
+ ...field,
1488
+ actions: /* @__PURE__ */ jsx(ExpressionToggle, {
1489
+ active: draft.expression !== void 0,
1490
+ onChange: (on) => onChange({
1491
+ expression: on ? "" : void 0,
1492
+ rows: []
1493
+ })
1494
+ }),
1495
+ children: /* @__PURE__ */ jsx(RowsField, {
1496
+ draft,
1497
+ template,
1498
+ label: inputLabel,
1499
+ onChange,
1500
+ error
1501
+ })
1502
+ });
1503
+ case "duration": return /* @__PURE__ */ jsx(Input, {
1504
+ mono: true,
1505
+ ...labelled(name, prop.key),
1506
+ description,
1507
+ value: String(draft ?? ""),
1508
+ placeholder: "HH:MM:SS",
1509
+ error,
1510
+ onChange: (e) => onChange(e.target.value)
1511
+ });
1512
+ case "template": return /* @__PURE__ */ jsx(LabelledField, {
1513
+ ...field,
1514
+ actions: /* @__PURE__ */ jsx(ExpressionToggle, {
1515
+ active: typeof draft === "string",
1516
+ onChange: (on) => onChange(on ? "" : {})
1517
+ }),
1518
+ children: typeof draft === "string" ? /* @__PURE__ */ jsx(Input, {
1519
+ mono: true,
1520
+ "aria-label": name,
1521
+ value: draft,
1522
+ error,
1523
+ onChange: (e) => onChange(e.target.value)
1524
+ }) : /* @__PURE__ */ jsx(TemplateFields, {
1525
+ fields: asRecord(draft),
1526
+ onChange,
1527
+ error,
1528
+ inputs: allInputs,
1529
+ adopt,
1530
+ home,
1531
+ onDialog,
1532
+ onFieldKey
1533
+ })
1534
+ });
1535
+ case "implies": return /* @__PURE__ */ jsx(LabelledField, {
1536
+ ...field,
1537
+ children: /* @__PURE__ */ jsx(ImpliesEditor, {
1538
+ draft,
1539
+ options: optionRows,
1540
+ onChange
1541
+ })
1542
+ });
1543
+ case "choice": {
1544
+ const choiceLabels = t.choices[prop.key];
1545
+ return /* @__PURE__ */ jsxs(LabelledField, {
1546
+ ...field,
1547
+ children: [/* @__PURE__ */ jsx(ChoiceButtons, {
1548
+ options: (prop.choices ?? []).map((choice) => ({
1549
+ value: choice,
1550
+ label: choiceLabels?.[choice] ?? choice
1551
+ })),
1552
+ value: String(draft ?? "") || (prop.choices?.[0] ?? ""),
1553
+ onChange
1554
+ }), /* @__PURE__ */ jsx(FieldError, { message: error })]
1555
+ });
1556
+ }
1557
+ case "json":
1558
+ case "textarea": return prop.grow ? /* @__PURE__ */ jsx(GrowingTextarea, {
1559
+ mono: !prop.prose,
1560
+ ...labelled(name, prop.key),
1561
+ description,
1562
+ value: String(draft ?? ""),
1563
+ ...error ? { error } : {},
1564
+ onChange: (e) => onChange(e.target.value)
1565
+ }) : /* @__PURE__ */ jsx(Textarea, {
1566
+ mono: !prop.prose,
1567
+ ...labelled(name, prop.key),
1568
+ description,
1569
+ rows: kind === "json" ? 4 : 6,
1570
+ value: String(draft ?? ""),
1571
+ ...error ? { error } : {},
1572
+ onChange: (e) => onChange(e.target.value)
1573
+ });
1574
+ default: return /* @__PURE__ */ jsx(SuggestedInput, {
1575
+ ...field,
1576
+ value: String(draft ?? ""),
1577
+ onChange,
1578
+ suggestions: prop.choices ?? (kind === "language" ? LANGUAGES : suggestions),
1579
+ error
1580
+ });
1581
+ }
1582
+ }
572
1583
  function commonProp(key) {
573
1584
  const flag = FLAG_FIELDS.includes(key);
574
1585
  return {
@@ -584,13 +1595,516 @@ var STEP_DESCRIPTION = {
584
1595
  ...commonProp("description"),
585
1596
  kind: "perCopy"
586
1597
  };
1598
+ function commonPropsOf(type) {
1599
+ return commonKeys(type).map((key) => type === "step" && key === "description" ? STEP_DESCRIPTION : commonProp(key));
1600
+ }
1601
+ /** Every field a type has, as the type an input had before a type change read its settings. */
1602
+ function propsOf(type) {
1603
+ return [...commonPropsOf(type), ...TYPE_PROPS[type] ?? []];
1604
+ }
1605
+ function isCommon(prop) {
1606
+ return COMMON_PROPS.some((common) => draftKey(common) === draftKey(prop));
1607
+ }
587
1608
  function draftKey(prop) {
588
1609
  return `${prop.kind}:${prop.key}`;
589
1610
  }
590
- [...new Map([
1611
+ var ALL_PROPS = [...new Map([
591
1612
  ...COMMON_PROPS,
592
1613
  STEP_DESCRIPTION,
593
1614
  ...Object.values(TYPE_PROPS).flat()
594
1615
  ].map((prop) => [draftKey(prop), prop])).values()];
1616
+ var SUGGESTED_NAMES = {
1617
+ "compute-clusters": "cluster",
1618
+ "compute-resources": "cluster",
1619
+ "slurm-accounts": "account",
1620
+ "slurm-partitions": "partition",
1621
+ "kubernetes-clusters": "kubernetes_cluster",
1622
+ "kubernetes-namespaces": "namespace",
1623
+ region: "region",
1624
+ boolean: "enabled",
1625
+ dropdown: "choice",
1626
+ "multi-dropdown": "choices",
1627
+ string: "text"
1628
+ };
1629
+ function allNames(editing, map, out) {
1630
+ for (const [name, value] of Object.entries(map)) {
1631
+ out.add(name);
1632
+ const definition = asRecord(value);
1633
+ const childKey = editing.inputChildrenKey(definition["type"]);
1634
+ if (childKey) allNames(editing, asRecord(definition[childKey]), out);
1635
+ }
1636
+ return out;
1637
+ }
1638
+ function suggestedName(editing, type, inputs) {
1639
+ const taken = allNames(editing, inputs, /* @__PURE__ */ new Set());
1640
+ const base = SUGGESTED_NAMES[type];
1641
+ return base ? editing.nextName(taken, base) : editing.newInputName(taken);
1642
+ }
1643
+ function defaultHome(inputs) {
1644
+ const wizard = asRecord(asRecord(inputs["$meta"])["wizard"]);
1645
+ const last = Object.entries(inputs).filter(([name, value]) => name !== "$meta" && asRecord(value)["type"] === "step").at(-1)?.[0];
1646
+ return wizard["mode"] === "wizard" && last ? { parent: [last] } : { parent: [] };
1647
+ }
1648
+ function homeContainer(editing, inputs, home) {
1649
+ const [step] = home.parent;
1650
+ if (!step) return inputs;
1651
+ const key = editing.inputChildrenKey(text(asRecord(inputs[step])["type"]));
1652
+ return key ? asRecord(asRecord(inputs[step])[key]) : {};
1653
+ }
1654
+ function withPending(editing, inputs, home, pending) {
1655
+ const added = Object.fromEntries(pending.map((input) => [input.name, input.definition]));
1656
+ const [step] = home.parent;
1657
+ if (!step) return {
1658
+ ...inputs,
1659
+ ...added
1660
+ };
1661
+ const definition = asRecord(inputs[step]);
1662
+ const key = editing.inputChildrenKey(text(definition["type"])) ?? "options";
1663
+ return {
1664
+ ...inputs,
1665
+ [step]: {
1666
+ ...definition,
1667
+ [key]: {
1668
+ ...homeContainer(editing, inputs, home),
1669
+ ...added
1670
+ }
1671
+ }
1672
+ };
1673
+ }
1674
+ /** `edit` after the edits that add `created` at `home`, as one undo step. */
1675
+ function withCreatedInputs(editing, created, home, edit) {
1676
+ return editing.batchOf([...created.map((input, i) => ({
1677
+ type: "addInput",
1678
+ parent: home.parent,
1679
+ ...home.index !== void 0 ? { index: home.index + i } : {},
1680
+ name: input.name,
1681
+ definition: input.definition
1682
+ })), ...edit ? [edit] : []]);
1683
+ }
1684
+ /** Inputs a dialog's fields can read, plus the ones it creates on save. */
1685
+ function useNewInputs(inputs, home) {
1686
+ const editing = useWorkflowEditing();
1687
+ const root = asRecord(inputs);
1688
+ const place = home ?? defaultHome(root);
1689
+ const [pending, setPending] = useState([]);
1690
+ const [creating, setCreating] = useState(null);
1691
+ const all = withPending(editing, root, place, pending);
1692
+ const source = {
1693
+ refs: inputRefs(editing, all),
1694
+ create: (type, onCreated) => setCreating({
1695
+ type,
1696
+ onCreated
1697
+ })
1698
+ };
1699
+ const adopt = (created) => setPending((current) => [...current, ...created]);
1700
+ const pathOf = (name) => place.parent.length > 0 && !editing.wizardFlattens(root) ? [...place.parent, name] : [name];
1701
+ return {
1702
+ source,
1703
+ all,
1704
+ home: place,
1705
+ pending,
1706
+ adopt,
1707
+ dialog: creating ? /* @__PURE__ */ jsx(InputDialog, {
1708
+ name: suggestedName(editing, creating.type, all),
1709
+ definition: newInputDefinition(creating.type),
1710
+ isNew: true,
1711
+ lockedType: true,
1712
+ siblings: [...allNames(editing, all, /* @__PURE__ */ new Set())],
1713
+ allowStep: false,
1714
+ inputs: all,
1715
+ home: place,
1716
+ onClose: () => setCreating(null),
1717
+ onSave: (name, definition, _patch, extra) => {
1718
+ adopt([...extra, {
1719
+ name,
1720
+ definition
1721
+ }]);
1722
+ creating.onCreated(pathOf(name));
1723
+ }
1724
+ }) : null,
1725
+ save: (edit) => withCreatedInputs(editing, pending, place, edit)
1726
+ };
1727
+ }
1728
+ /** An input's settings in a form, or as YAML when `yaml` says how to save it; switching carries the edits across. */
1729
+ function InputDialog({ yaml, view, onViewChange, ...props }) {
1730
+ return yaml ? /* @__PURE__ */ jsx(InputViews, {
1731
+ ...props,
1732
+ textual: yaml,
1733
+ view,
1734
+ onViewChange
1735
+ }) : /* @__PURE__ */ jsx(SectionMemory.Provider, {
1736
+ value: null,
1737
+ children: /* @__PURE__ */ jsx(InputForm, { ...props })
1738
+ });
1739
+ }
1740
+ function startingYaml(editing, textual, definition) {
1741
+ if (textual.source !== void 0 && textual.path) try {
1742
+ return editing.inputYaml(textual.source, textual.path);
1743
+ } catch {}
1744
+ return editing.dumpYaml(definition);
1745
+ }
1746
+ function InputViews({ textual, view, onViewChange, ...props }) {
1747
+ const t = useInputsEditorStrings();
1748
+ const g = useGraphEditorStrings();
1749
+ const editing = useWorkflowEditing();
1750
+ const sections = useSectionMemory();
1751
+ const newInputs = useNewInputs(props.inputs, props.home);
1752
+ const [original] = useState(() => props.isNew ? {} : props.definition);
1753
+ const [opening] = useState(() => startingYaml(editing, textual, props.definition));
1754
+ const [shown, setShown] = useState(opening);
1755
+ const [base, setBase] = useState(props.definition);
1756
+ const [yaml, setYaml] = useState(view === "form" ? null : opening);
1757
+ const [name, setName] = useState(props.name);
1758
+ const [version, setVersion] = useState(0);
1759
+ const [problem, setProblem] = useState();
1760
+ const draft = useRef(null);
1761
+ const toYaml = () => {
1762
+ const current = draft.current;
1763
+ if (current) setName(current.name);
1764
+ const text = !current || isEmptyPatch(diffPatch(base, current.next)) ? shown : editing.dumpYaml(withoutUndefined(current.next));
1765
+ setShown(text);
1766
+ setYaml(text);
1767
+ setProblem(void 0);
1768
+ onViewChange?.("yaml");
1769
+ };
1770
+ const toForm = () => {
1771
+ if (yaml === null) return;
1772
+ const issue = yamlProblem(yaml, g, editing);
1773
+ if (issue) {
1774
+ setProblem(issue);
1775
+ return;
1776
+ }
1777
+ setShown(yaml);
1778
+ setBase(asRecord(editing.loadYaml(yaml)));
1779
+ setYaml(null);
1780
+ setVersion((current) => current + 1);
1781
+ onViewChange?.("form");
1782
+ };
1783
+ const switcher = /* @__PURE__ */ jsx(ViewSwitch, {
1784
+ yaml: yaml !== null,
1785
+ onForm: toForm,
1786
+ onYaml: toYaml
1787
+ });
1788
+ if (yaml === null) return /* @__PURE__ */ jsx(SectionMemory.Provider, {
1789
+ value: sections,
1790
+ children: /* @__PURE__ */ jsx(InputForm, {
1791
+ ...props,
1792
+ name,
1793
+ originalName: props.name,
1794
+ definition: base,
1795
+ original,
1796
+ newInputs,
1797
+ headerEnd: switcher,
1798
+ onDraft: (next, settings) => {
1799
+ draft.current = {
1800
+ name: next,
1801
+ next: settings
1802
+ };
1803
+ }
1804
+ }, version)
1805
+ });
1806
+ const nextName = name.trim();
1807
+ const nameError = !editing.isValidInputName(nextName) ? t.invalidInputName : nextName !== props.name && props.siblings.includes(nextName) || newInputs.pending.some((input) => input.name === nextName) ? t.inputExists : void 0;
1808
+ const dirty = props.isNew || nextName !== props.name || yaml !== opening || newInputs.pending.length > 0;
1809
+ return /* @__PURE__ */ jsxs(DialogShell, {
1810
+ title: t.editInput,
1811
+ onClose: props.onClose,
1812
+ dirty,
1813
+ saveDisabled: yamlProblem(yaml, g, editing) !== void 0 || nameError !== void 0,
1814
+ onSubmit: () => {
1815
+ if (dirty) textual.onSave(nextName, yaml, newInputs.pending);
1816
+ props.onClose();
1817
+ },
1818
+ headerEnd: switcher,
1819
+ openOnAdd: props.openOnAdd,
1820
+ ...props.onDelete ? { footerStart: deleteButton(t, props) } : {},
1821
+ children: [/* @__PURE__ */ jsx(Input, {
1822
+ mono: true,
1823
+ label: t.inputName,
1824
+ description: t.help.name,
1825
+ value: name,
1826
+ error: nameError,
1827
+ onChange: (e) => setName(e.target.value)
1828
+ }), /* @__PURE__ */ jsx(YamlPane, {
1829
+ path: INPUT_YAML_PATH,
1830
+ value: yaml,
1831
+ onChange: (text) => {
1832
+ setYaml(text);
1833
+ setProblem(void 0);
1834
+ },
1835
+ problem: problem ?? yamlProblem(yaml, g, editing),
1836
+ scoped: NO_SCOPED
1837
+ })]
1838
+ });
1839
+ }
1840
+ function deleteButton(t, { onDelete, onClose }) {
1841
+ return /* @__PURE__ */ jsx(IconButton, {
1842
+ icon: /* @__PURE__ */ jsx(BiSolidTrashAlt, { className: "h-4 w-4" }),
1843
+ label: t.deleteInput,
1844
+ variant: "ghost",
1845
+ size: "sm",
1846
+ onClick: () => {
1847
+ onDelete?.();
1848
+ onClose();
1849
+ }
1850
+ });
1851
+ }
1852
+ function InputForm({ name, definition, isNew, siblings, allowStep, inputs, home, lockedType = false, onSave, onDelete, onClose, openOnAdd, original: startOriginal, originalName = name, newInputs: shared, headerEnd, onDraft }) {
1853
+ const t = useInputsEditorStrings();
1854
+ const g = useGraphEditorStrings();
1855
+ const editing = useWorkflowEditing();
1856
+ const own = useNewInputs(inputs, home);
1857
+ const newInputs = shared ?? own;
1858
+ const [original] = useState(() => startOriginal ?? (isNew ? {} : definition));
1859
+ const originalType = text(original["type"]);
1860
+ const childrenOf = (settings, withTemplate = true) => {
1861
+ for (const from of [text(settings["type"]), originalType]) {
1862
+ const key = editing.inputChildrenKey(from);
1863
+ if (key && (withTemplate || key !== "template") && settings[key] !== void 0) return settings[key];
1864
+ }
1865
+ };
1866
+ const [draftName, setDraftName] = useState(name);
1867
+ const [type, setType] = useState(() => text(definition["type"]) || "string");
1868
+ const [initial] = useState(() => {
1869
+ const out = {};
1870
+ for (const prop of ALL_PROPS) out[draftKey(prop)] = draftOf(effectiveKind(prop, definition[prop.key]), definition[prop.key]);
1871
+ return out;
1872
+ });
1873
+ const [drafts, setDrafts] = useState(() => {
1874
+ const fields = text(definition["type"]) === "list" && definition["template"] === void 0 ? childrenOf(definition, false) : void 0;
1875
+ return fields === void 0 ? initial : {
1876
+ ...initial,
1877
+ [draftKey(TEMPLATE)]: draftOf("template", fields)
1878
+ };
1879
+ });
1880
+ const [nested, setNested] = useState(false);
1881
+ const rowsKey = draftKey(LIST_DEFAULT);
1882
+ const onFieldKey = (from, to) => setDrafts((current) => {
1883
+ const rows = current[rowsKey];
1884
+ if (!rows || rows.expression !== void 0) return current;
1885
+ const moved = rows.rows.map((row) => {
1886
+ if (!Object.hasOwn(row, from)) return row;
1887
+ const { [from]: value, ...rest } = row;
1888
+ return to === void 0 ? rest : {
1889
+ ...rest,
1890
+ [to]: value
1891
+ };
1892
+ });
1893
+ return {
1894
+ ...current,
1895
+ [rowsKey]: {
1896
+ ...rows,
1897
+ rows: moved
1898
+ }
1899
+ };
1900
+ });
1901
+ const optionsDraftNow = drafts[draftKey(OPTIONS)];
1902
+ const optionRows = optionsDraftNow?.mode === "list" ? optionsDraftNow.rows : [];
1903
+ const keyedDropdown = type === "dropdown" && (optionsDraftNow?.mode === "byKey" || optionsDraftNow?.mode === "expression" && String(drafts[draftKey(OPTION_KEY)] ?? "").trim() !== "");
1904
+ const multi = drafts[draftKey(MULTI)] === true;
1905
+ const shown = (prop) => prop === RESOURCE_DEFAULTS ? multi : prop === RESOURCE_DEFAULT ? !multi : type !== "dropdown" ? true : keyedDropdown ? prop.key !== "placeholder" && prop.key !== "autoselect" : prop.key !== "option-key" || optionsDraftNow?.mode === "expression";
1906
+ const settingProps = (TYPE_PROPS[type] ?? []).filter(shown);
1907
+ const props = [...commonPropsOf(type), ...settingProps];
1908
+ const kindOf = (prop) => effectiveKind(prop, definition[prop.key]);
1909
+ const optionKeys = OPTION_KEYS[type];
1910
+ const context = {
1911
+ labelled: type === "multi-dropdown",
1912
+ textValues: !!optionKeys
1913
+ };
1914
+ const typeChanged = !isNew && type !== originalType;
1915
+ const valueFor = (prop) => {
1916
+ const key = draftKey(prop);
1917
+ const kept = definition[prop.key];
1918
+ const becameList = kindOf(prop) === "values" && isScalar(kept) && !(typeof kept === "string" && EXPRESSION.test(kept.trim()));
1919
+ const readsAlike = !typeChanged || propsOf(originalType).some((before) => draftKey(before) === key);
1920
+ return sameValue(drafts[key], initial[key]) && !becameList && readsAlike ? kept : writtenValue(kindOf(prop), drafts[key], context);
1921
+ };
1922
+ const edited = (key) => props.some((prop) => prop.key === key && !sameValue(drafts[draftKey(prop)], initial[draftKey(prop)]));
1923
+ const optionValues = type === "dropdown" || type === "radio" ? optionRows.map((row) => row.value) : [];
1924
+ const slider = type === "number" && valueFor(SLIDER) === true;
1925
+ const trimmedName = draftName.trim();
1926
+ const errors = { name: !editing.isValidInputName(trimmedName) ? t.invalidInputName : siblings.includes(trimmedName) || newInputs.pending.some((input) => input.name === trimmedName) ? t.inputExists : void 0 };
1927
+ for (const prop of props) {
1928
+ const key = draftKey(prop);
1929
+ const required = prop.required || keyedDropdown && optionsDraftNow?.mode === "byKey" && prop === OPTION_KEY || slider && [
1930
+ "min",
1931
+ "max",
1932
+ "step"
1933
+ ].includes(prop.key);
1934
+ const untouched = sameValue(drafts[key], initial[key]);
1935
+ const kept = definition[prop.key];
1936
+ errors[key] = typeChanged && untouched && kept !== null && typeof kept === "object" && SCALAR_KINDS.has(kindOf(prop)) ? t.keptValueDoesNotFit : untouched && !required && !typeChanged ? void 0 : valueError({
1937
+ ...prop,
1938
+ required
1939
+ }, kindOf(prop), drafts[key], t, g);
1940
+ }
1941
+ if (type !== "dropdown" && optionsDraftNow?.mode === "byKey" && props.includes(OPTIONS)) errors[draftKey(OPTIONS)] = t.optionsByKeyDropdownOnly;
1942
+ const clash = type === "group" && drafts[draftKey(FLATTEN)] === true ? Object.keys(asRecord(definition["items"])).find((child) => siblings.includes(child)) : void 0;
1943
+ if (clash) errors[draftKey(FLATTEN)] = t.flattenClash(clash);
1944
+ if (type === "duration") {
1945
+ const hasLabel = valueFor(DISABLE_LABEL) !== void 0;
1946
+ const optOut = valueFor(DISABLE_VALUE);
1947
+ if (hasLabel !== (optOut !== void 0)) errors[draftKey(hasLabel ? DISABLE_VALUE : DISABLE_LABEL)] = t.disableNeedsBoth;
1948
+ if (typeof optOut === "string") errors[draftKey(DISABLE_VALUE)] = t.invalidNumber;
1949
+ const defaultKey = draftKey(DURATION_DEFAULT);
1950
+ if (optOut !== void 0 && valueFor(DURATION_DEFAULT) === optOut) errors[defaultKey] = void 0;
1951
+ }
1952
+ const nextDefinition = () => {
1953
+ const next = {
1954
+ ...definition,
1955
+ type
1956
+ };
1957
+ for (const key of Object.keys(original)) if (!Object.hasOwn(next, key)) next[key] = void 0;
1958
+ for (const prop of props) next[prop.key] = valueFor(prop);
1959
+ const childKey = editing.inputChildrenKey(type);
1960
+ if (childKey && childKey !== "template") next[childKey] = childrenOf(definition) ?? {};
1961
+ for (const prop of TYPE_PROPS[type] ?? []) if (!settingProps.some((other) => other.key === prop.key)) next[prop.key] = void 0;
1962
+ const options = next["options"];
1963
+ const listed = Array.isArray(options) ? options.map((option) => isScalar(option) ? option : asRecord(option)["value"]) : [];
1964
+ const known = Array.isArray(options) && listed.every(isScalar);
1965
+ if (type === "checkbox-group" && next["implies"] !== void 0 && known && (edited("options") || edited("implies"))) {
1966
+ const values = new Set(listed.map(String));
1967
+ const links = Object.entries(asRecord(next["implies"])).filter(([from]) => values.has(from)).map(([from, to]) => [from, (Array.isArray(to) ? to : []).filter((value) => values.has(String(value)))]).filter(([, to]) => to.length > 0);
1968
+ next["implies"] = links.length > 0 ? Object.fromEntries(links) : void 0;
1969
+ }
1970
+ if (type === "dropdown" && typeof next["default"] === "string" && known && (typeChanged || edited("options") || edited("default"))) {
1971
+ const picked = listed.find((value) => typeof value !== "string" && String(value) === next["default"]);
1972
+ if (picked !== void 0) next["default"] = picked;
1973
+ }
1974
+ if (type !== originalType) {
1975
+ const takes = new Set(offeredInputKeys(editing, type));
1976
+ for (const key of Object.keys(next)) if (KNOWN_KEYS.has(key) && !takes.has(key)) next[key] = void 0;
1977
+ }
1978
+ if (optionKeys && Array.isArray(next["options"]) && (typeChanged || edited("options"))) next["options"] = next["options"].map((option) => textOption(option, optionKeys));
1979
+ if (optionKeys && typeChanged) {
1980
+ const picked = next["default"];
1981
+ next["default"] = Array.isArray(picked) ? picked.map((value) => isScalar(value) ? String(value) : value) : isScalar(picked) ? String(picked) : picked;
1982
+ }
1983
+ return next;
1984
+ };
1985
+ const next = nextDefinition();
1986
+ const shaped = next["options"];
1987
+ if (optionKeys && (typeChanged || edited("options")) && Array.isArray(shaped) && !shaped.every((option) => fitsTextOption(option, optionKeys)) && !errors[draftKey(OPTIONS)]) errors[draftKey(OPTIONS)] = type === "radio" ? t.radioOptionShape : t.checkboxOptionShape;
1988
+ const patch = diffPatch(original, next);
1989
+ const dirty = isNew || trimmedName !== originalName || !isEmptyPatch(patch);
1990
+ const invalid = Object.values(errors).some(Boolean);
1991
+ const setDraft = (key, value) => setDrafts((current) => ({
1992
+ ...current,
1993
+ [key]: value
1994
+ }));
1995
+ const changeType = (next) => {
1996
+ setType(next);
1997
+ const fields = childrenOf(definition, false);
1998
+ const templateKey = draftKey(TEMPLATE);
1999
+ if (next === "list" && fields && sameValue(drafts[templateKey], initial[templateKey])) setDraft(templateKey, draftOf("template", fields));
2000
+ if (!isNew) return;
2001
+ const seeded = newInputDefinition(next);
2002
+ for (const prop of TYPE_PROPS[next] ?? []) {
2003
+ const key = draftKey(prop);
2004
+ if (seeded[prop.key] !== void 0 && sameValue(drafts[key], initial[key])) setDraft(key, draftOf(prop.kind, seeded[prop.key]));
2005
+ }
2006
+ };
2007
+ const renderProp = (prop) => /* @__PURE__ */ jsx(PropField, {
2008
+ prop,
2009
+ kind: kindOf(prop),
2010
+ draft: drafts[draftKey(prop)],
2011
+ original: definition[prop.key],
2012
+ error: errors[draftKey(prop)],
2013
+ onChange: (value) => setDraft(draftKey(prop), value),
2014
+ suggestions: prop.key === "default" ? optionValues : [],
2015
+ inputType: type,
2016
+ optionRows,
2017
+ source: newInputs.source,
2018
+ adopt: newInputs.adopt,
2019
+ allInputs: newInputs.all,
2020
+ home: newInputs.home,
2021
+ template: asRecord(drafts[draftKey(TEMPLATE)]),
2022
+ inputLabel: String(drafts[draftKey(commonProp("label"))] ?? "").trim() || trimmedName,
2023
+ onDialog: setNested,
2024
+ onFieldKey
2025
+ }, draftKey(prop));
2026
+ const textProps = props.filter((prop) => TEXT_FIELDS.includes(prop.key) && (isCommon(prop) || prop === STEP_DESCRIPTION));
2027
+ const behaviorProps = props.filter((prop) => FLAG_FIELDS.includes(prop.key) && isCommon(prop));
2028
+ const holds = (prop) => original[prop.key] !== void 0;
2029
+ const erred = (prop) => errors[draftKey(prop)] !== void 0;
2030
+ const fieldOpen = isNew || textProps.some(holds);
2031
+ const settingsOpen = settingProps.some((prop) => prop.required || holds(prop));
2032
+ const written = withoutUndefined(next);
2033
+ onDraft?.(trimmedName, next);
2034
+ return /* @__PURE__ */ jsxs(DialogShell, {
2035
+ title: t.editInput,
2036
+ onClose,
2037
+ dirty,
2038
+ locked: newInputs.dialog !== null || nested,
2039
+ saveDisabled: invalid,
2040
+ onSubmit: () => {
2041
+ if (dirty) onSave(trimmedName, written, patch, newInputs.pending);
2042
+ onClose();
2043
+ },
2044
+ headerEnd,
2045
+ openOnAdd,
2046
+ ...onDelete ? { footerStart: deleteButton(t, {
2047
+ onDelete,
2048
+ onClose
2049
+ }) } : {},
2050
+ children: [
2051
+ /* @__PURE__ */ jsx(Input, {
2052
+ autoFocus: true,
2053
+ mono: true,
2054
+ label: t.inputName,
2055
+ description: t.help.name,
2056
+ value: draftName,
2057
+ error: errors["name"],
2058
+ onChange: (e) => setDraftName(e.target.value)
2059
+ }),
2060
+ /* @__PURE__ */ jsx(TypeSelect, {
2061
+ value: type,
2062
+ onChange: changeType,
2063
+ allowStep,
2064
+ disabled: lockedType
2065
+ }),
2066
+ !isNew && originalType && type !== originalType && /* @__PURE__ */ jsx("div", {
2067
+ className: "text-xs theme-muted-text",
2068
+ children: t.typeChangeNote
2069
+ }),
2070
+ textProps.length > 0 && /* @__PURE__ */ jsx(Section, {
2071
+ title: t.sectionField,
2072
+ open: fieldOpen,
2073
+ alert: textProps.some(erred),
2074
+ children: textProps.map(renderProp)
2075
+ }),
2076
+ settingProps.length > 0 && /* @__PURE__ */ jsx(Section, {
2077
+ title: t.sectionSettings,
2078
+ open: settingsOpen,
2079
+ alert: settingProps.some(erred),
2080
+ children: settingProps.map((prop) => prop === DISABLE_VALUE ? null : prop === DISABLE_LABEL ? /* @__PURE__ */ jsx(OptOutCheckbox, {
2081
+ label: String(drafts[draftKey(DISABLE_LABEL)] ?? ""),
2082
+ value: String(drafts[draftKey(DISABLE_VALUE)] ?? ""),
2083
+ onLabel: (next) => setDraft(draftKey(DISABLE_LABEL), next),
2084
+ onValue: (next) => setDraft(draftKey(DISABLE_VALUE), next),
2085
+ errors: {
2086
+ label: errors[draftKey(DISABLE_LABEL)],
2087
+ value: errors[draftKey(DISABLE_VALUE)]
2088
+ }
2089
+ }, "opt-out") : renderProp(prop))
2090
+ }),
2091
+ behaviorProps.length > 0 && /* @__PURE__ */ jsx(Section, {
2092
+ title: t.sectionBehavior,
2093
+ open: behaviorProps.some(holds),
2094
+ alert: behaviorProps.some(erred),
2095
+ children: behaviorProps.map(renderProp)
2096
+ }),
2097
+ newInputs.dialog
2098
+ ]
2099
+ });
2100
+ }
2101
+ /** A compact type chip for rows that list inputs. */
2102
+ function TypeBadge({ type }) {
2103
+ const types = useInputsEditorStrings().types;
2104
+ return /* @__PURE__ */ jsx("span", {
2105
+ className: cx("rounded border theme-border px-1 text-[11px] leading-4 theme-muted-text"),
2106
+ children: types[type] ?? (type || "?")
2107
+ });
2108
+ }
595
2109
  //#endregion
596
- export { INPUT_TYPE_GROUPS, commonKeys, inputTypes, offeredInputKeys };
2110
+ export { INPUT_TYPE_GROUPS, InputDialog, TypeBadge, allNames, commonKeys, inputTypes, newInputDefinition, offeredInputKeys, useNewInputs, withCreatedInputs };