@parallelworks/ui 0.36.0 → 0.38.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 (106) hide show
  1. package/README.md +16 -0
  2. package/dist/ai/agent/permissions.d.ts +2 -2
  3. package/dist/ai/agent/permissions.js +3 -1
  4. package/dist/ai/components/ChatSidebar.js +7 -4
  5. package/dist/ai/components/agent/PermissionPicker.js +1 -0
  6. package/dist/ai/index.d.ts +1 -1
  7. package/dist/ai/index.js +2 -2
  8. package/dist/ai/strings.d.ts +2 -0
  9. package/dist/ai/strings.js +2 -0
  10. package/dist/ai/ui/sidebar.d.ts +6 -0
  11. package/dist/ai/ui/sidebar.js +15 -1
  12. package/dist/components/Dropdown.d.ts +2 -0
  13. package/dist/components/Dropdown.js +7 -2
  14. package/dist/components/Input.d.ts +11 -6
  15. package/dist/components/Input.js +93 -74
  16. package/dist/components/Provider.d.ts +31 -0
  17. package/dist/components/Provider.js +50 -2
  18. package/dist/editing.d.ts +404 -0
  19. package/dist/editor/EditorField.js +1 -1
  20. package/dist/editor/Monaco.d.ts +15 -1
  21. package/dist/editor/Monaco.js +117 -13
  22. package/dist/editor/editorModel.d.ts +5 -0
  23. package/dist/editor/editorModel.js +23 -0
  24. package/dist/editor/index.d.ts +4 -0
  25. package/dist/editor/index.js +5 -1
  26. package/dist/editor/lintContext.d.ts +21 -0
  27. package/dist/editor/lintContext.js +136 -0
  28. package/dist/editor/lintOwner.d.ts +2 -0
  29. package/dist/editor/lintOwner.js +5 -0
  30. package/dist/editor/nestedText.d.ts +13 -0
  31. package/dist/editor/nestedText.js +15 -0
  32. package/dist/editor/reveal.d.ts +4 -0
  33. package/dist/editor/reveal.js +15 -0
  34. package/dist/editor/settingsYaml.d.ts +12 -0
  35. package/dist/editor/settingsYaml.js +41 -0
  36. package/dist/form/FieldWrapper.js +1 -0
  37. package/dist/form/Fields/DropdownField.js +1 -1
  38. package/dist/form/Fields/DurationField.js +1 -1
  39. package/dist/form/Form.js +65 -24
  40. package/dist/form/Wizard/WizardContainer.js +66 -6
  41. package/dist/form/Wizard/WizardStepContent.js +23 -10
  42. package/dist/form/Wizard/wizardPages.d.ts +12 -0
  43. package/dist/form/Wizard/wizardPages.js +5 -0
  44. package/dist/form/fieldRegistry.js +1 -1
  45. package/dist/form/formEditing.d.ts +25 -0
  46. package/dist/form/formEditing.js +20 -0
  47. package/dist/form/index.js +5 -5
  48. package/dist/graph/InputDialog.d.ts +80 -0
  49. package/dist/graph/InputDialog.js +2110 -0
  50. package/dist/graph/InputsEditor.d.ts +12 -0
  51. package/dist/graph/InputsEditor.js +1594 -0
  52. package/dist/graph/ProblemsButton.d.ts +9 -0
  53. package/dist/graph/ProblemsButton.js +61 -0
  54. package/dist/graph/ShortcutsButton.d.ts +15 -0
  55. package/dist/graph/ShortcutsButton.js +56 -0
  56. package/dist/graph/SuggestionInput.d.ts +18 -0
  57. package/dist/graph/SuggestionInput.js +29 -0
  58. package/dist/graph/ToolbarPopover.d.ts +8 -0
  59. package/dist/graph/ToolbarPopover.js +71 -0
  60. package/dist/graph/editorApi.d.ts +51 -0
  61. package/dist/graph/editorApi.js +8 -0
  62. package/dist/graph/editorChrome.d.ts +27 -0
  63. package/dist/graph/editorChrome.js +75 -0
  64. package/dist/graph/editorFields.d.ts +180 -0
  65. package/dist/graph/editorFields.js +394 -0
  66. package/dist/graph/editorPrimitives.d.ts +60 -0
  67. package/dist/graph/editorPrimitives.js +150 -0
  68. package/dist/graph/editorStrings.d.ts +695 -0
  69. package/dist/graph/editorStrings.js +707 -0
  70. package/dist/graph/expressionRefs.d.ts +12 -0
  71. package/dist/graph/fieldProblems.d.ts +32 -0
  72. package/dist/graph/fieldProblems.js +77 -0
  73. package/dist/graph/index.d.ts +7 -0
  74. package/dist/graph/index.js +6 -1
  75. package/dist/graph/inputLayout.d.ts +25 -0
  76. package/dist/graph/inputLayout.js +337 -0
  77. package/dist/graph/inputPages.d.ts +33 -0
  78. package/dist/graph/inputPages.js +186 -0
  79. package/dist/graph/inputRefs.d.ts +103 -0
  80. package/dist/graph/inputRefs.js +196 -0
  81. package/dist/graph/inputRows.d.ts +43 -0
  82. package/dist/graph/inputRows.js +65 -0
  83. package/dist/graph/records.d.ts +3 -0
  84. package/dist/graph/records.js +7 -0
  85. package/dist/graph/refusalText.d.ts +6 -0
  86. package/dist/graph/settingsViews.d.ts +26 -0
  87. package/dist/graph/settingsViews.js +62 -0
  88. package/dist/graph/usesInputs.d.ts +17 -0
  89. package/dist/graph/usesInputs.js +40 -0
  90. package/dist/icons/bi/BiRedo.js +16 -0
  91. package/dist/icons/bi/BiUndo.js +16 -0
  92. package/dist/icons/lu/LuBraces.js +27 -0
  93. package/dist/icons/lu/LuCode.js +27 -0
  94. package/dist/icons/lu/LuKeyboard.js +71 -0
  95. package/dist/icons/lu/LuNetwork.js +63 -0
  96. package/dist/icons/lu/LuTextCursorInput.js +45 -0
  97. package/dist/icons.d.ts +2 -2
  98. package/dist/icons.js +8 -1
  99. package/dist/index.d.ts +2 -1
  100. package/dist/index.js +2 -2
  101. package/dist/list/ActionToolbar.d.ts +2 -0
  102. package/dist/list/ActionToolbar.js +18 -5
  103. package/dist/list/RowContextMenu.d.ts +10 -1
  104. package/dist/list/RowContextMenu.js +185 -30
  105. package/dist/styles.css +1 -1
  106. package/package.json +1 -1
@@ -0,0 +1,2110 @@
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 { INPUT_YAML_PATH } from "../editor/settingsYaml.js";
14
+ import { formatDuration, parseDuration } from "../form/utils/duration.js";
15
+ import { FormEditingContext } from "../form/formEditing.js";
16
+ import { DynamicForm } from "../form/Form.js";
17
+ import { asRecord } from "./records.js";
18
+ import { FIELD_TEXT_BOX, SuggestionInput } from "./SuggestionInput.js";
19
+ 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";
20
+ import { withInputLayouts } from "./inputLayout.js";
21
+ import { FlagField, ValueOrInputField, inputRefs } from "./inputRefs.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";
26
+ //#region src/graph/InputDialog.tsx
27
+ /** Input types by menu group, in the order the add menu lists them. */
28
+ var INPUT_TYPE_GROUPS = [
29
+ ["basic", [
30
+ "string",
31
+ "number",
32
+ "duration",
33
+ "boolean",
34
+ "password",
35
+ "editor",
36
+ "color-picker"
37
+ ]],
38
+ ["choices", [
39
+ "dropdown",
40
+ "multi-dropdown",
41
+ "radio",
42
+ "checkbox-group"
43
+ ]],
44
+ ["layout", [
45
+ "group",
46
+ "list",
47
+ "header",
48
+ "step"
49
+ ]],
50
+ ["compute", [
51
+ "compute-clusters",
52
+ "compute-resources",
53
+ "compute-target",
54
+ "bucket"
55
+ ]],
56
+ ["schedulers", [
57
+ "slurm-accounts",
58
+ "slurm-partitions",
59
+ "slurm-qos",
60
+ "pbs-queues"
61
+ ]],
62
+ ["cloud", [
63
+ "region",
64
+ "zone",
65
+ "instance-type"
66
+ ]],
67
+ ["kubernetes", [
68
+ "kubernetes-clusters",
69
+ "kubernetes-namespaces",
70
+ "kubernetes-pods",
71
+ "kubernetes-pvc",
72
+ "kubernetes-secrets",
73
+ "kubernetes-configmaps",
74
+ "kubernetes-statefulsets",
75
+ "kubernetes-deployments",
76
+ "kubernetes-services",
77
+ "kubernetes-workloads"
78
+ ]],
79
+ ["platform", [
80
+ "organization-groups",
81
+ "ai-model",
82
+ "allocations"
83
+ ]]
84
+ ];
85
+ var DEFAULT = {
86
+ key: "default",
87
+ kind: "text",
88
+ help: "default"
89
+ };
90
+ var DEFAULT_OBJECT = {
91
+ ...DEFAULT,
92
+ nested: "object"
93
+ };
94
+ var DEFAULT_LIST = {
95
+ key: "default",
96
+ kind: "values",
97
+ help: "defaultList"
98
+ };
99
+ var RESOURCE_DEFAULT = {
100
+ ...DEFAULT,
101
+ help: "defaultResource",
102
+ nested: "any"
103
+ };
104
+ var RESOURCE_DEFAULTS = {
105
+ ...DEFAULT_LIST,
106
+ help: "defaultResources"
107
+ };
108
+ var AUTOSELECT = {
109
+ key: "autoselect",
110
+ kind: "flag",
111
+ help: "autoselect",
112
+ nested: "object",
113
+ conditional: true
114
+ };
115
+ var PLACEHOLDER = {
116
+ key: "placeholder",
117
+ kind: "text",
118
+ help: "placeholder",
119
+ prose: true
120
+ };
121
+ var CLUSTER = {
122
+ key: "clusterName",
123
+ kind: "ref",
124
+ help: "clusterName",
125
+ required: true,
126
+ refTypes: ["kubernetes-clusters"]
127
+ };
128
+ var NAMESPACE = {
129
+ key: "namespace",
130
+ kind: "ref",
131
+ help: "namespace",
132
+ required: true,
133
+ refTypes: ["kubernetes-namespaces"]
134
+ };
135
+ var CSP = {
136
+ key: "csp",
137
+ kind: "ref",
138
+ help: "csp",
139
+ refTypes: ["dropdown", "radio"],
140
+ choices: [
141
+ "aws",
142
+ "azure",
143
+ "google",
144
+ "openstack"
145
+ ]
146
+ };
147
+ var CSP_REQUIRED = {
148
+ ...CSP,
149
+ required: true
150
+ };
151
+ var REGION = {
152
+ key: "region",
153
+ kind: "ref",
154
+ help: "region",
155
+ required: true,
156
+ refTypes: ["region"]
157
+ };
158
+ var RESOURCE = {
159
+ key: "resource",
160
+ kind: "ref",
161
+ help: "resource",
162
+ required: true,
163
+ refTypes: ["compute-clusters", "compute-resources"],
164
+ literal: false
165
+ };
166
+ var ACCOUNT = {
167
+ key: "account",
168
+ kind: "ref",
169
+ help: "account",
170
+ refTypes: ["slurm-accounts"],
171
+ literal: false
172
+ };
173
+ var DEFAULT_FROM_INPUT = {
174
+ key: "default",
175
+ kind: "ref",
176
+ help: "defaultFromInput",
177
+ refTypes: [
178
+ "string",
179
+ "dropdown",
180
+ "radio"
181
+ ],
182
+ literal: false
183
+ };
184
+ var OPTIONS = {
185
+ key: "options",
186
+ kind: "options",
187
+ help: "options",
188
+ required: true
189
+ };
190
+ var MULTI = {
191
+ key: "multi",
192
+ kind: "bool",
193
+ help: "multi"
194
+ };
195
+ var COMPUTE = [
196
+ RESOURCE_DEFAULT,
197
+ RESOURCE_DEFAULTS,
198
+ AUTOSELECT,
199
+ {
200
+ key: "include-workspace",
201
+ kind: "flag",
202
+ help: "includeWorkspace",
203
+ conditional: true
204
+ },
205
+ {
206
+ key: "include-unprovisioned",
207
+ kind: "flag",
208
+ help: "includeUnprovisioned",
209
+ conditional: true
210
+ },
211
+ CSP,
212
+ {
213
+ key: "provider",
214
+ kind: "providers",
215
+ help: "provider"
216
+ },
217
+ MULTI
218
+ ];
219
+ var IN_NAMESPACE = [
220
+ DEFAULT_OBJECT,
221
+ CLUSTER,
222
+ NAMESPACE,
223
+ AUTOSELECT
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
+ };
273
+ var TYPE_PROPS = {
274
+ string: [
275
+ DEFAULT,
276
+ PLACEHOLDER,
277
+ {
278
+ key: "textarea",
279
+ kind: "bool",
280
+ help: "textarea"
281
+ },
282
+ {
283
+ key: "prefillDefault",
284
+ kind: "flag",
285
+ help: "prefillDefault"
286
+ },
287
+ {
288
+ key: "minLength",
289
+ kind: "number",
290
+ help: "minLength",
291
+ wholeFrom: 0
292
+ },
293
+ {
294
+ key: "maxLength",
295
+ kind: "number",
296
+ help: "maxLength",
297
+ wholeFrom: 1
298
+ },
299
+ {
300
+ key: "lowercase",
301
+ kind: "bool",
302
+ help: "lowercase"
303
+ },
304
+ {
305
+ key: "sanitize",
306
+ kind: "text",
307
+ help: "sanitize",
308
+ pattern: true
309
+ }
310
+ ],
311
+ number: [
312
+ {
313
+ key: "default",
314
+ kind: "number",
315
+ help: "default"
316
+ },
317
+ {
318
+ key: "min",
319
+ kind: "number",
320
+ help: "minNumber"
321
+ },
322
+ {
323
+ key: "max",
324
+ kind: "number",
325
+ help: "maxNumber"
326
+ },
327
+ {
328
+ key: "step",
329
+ kind: "number",
330
+ help: "step"
331
+ },
332
+ SLIDER
333
+ ],
334
+ boolean: [{
335
+ key: "default",
336
+ kind: "flag",
337
+ help: "default"
338
+ }],
339
+ password: [DEFAULT],
340
+ editor: [{
341
+ key: "default",
342
+ kind: "textarea",
343
+ help: "default"
344
+ }, {
345
+ key: "language",
346
+ kind: "language",
347
+ help: "language"
348
+ }],
349
+ "color-picker": [DEFAULT],
350
+ dropdown: [
351
+ OPTIONS,
352
+ OPTION_KEY,
353
+ DEFAULT_OBJECT,
354
+ PLACEHOLDER,
355
+ AUTOSELECT
356
+ ],
357
+ "multi-dropdown": [OPTIONS, DEFAULT_LIST],
358
+ radio: [
359
+ OPTIONS,
360
+ DEFAULT,
361
+ {
362
+ key: "optionLabelPosition",
363
+ kind: "choice",
364
+ help: "optionLabelPosition",
365
+ choices: ["left", "top"]
366
+ }
367
+ ],
368
+ "checkbox-group": [
369
+ OPTIONS,
370
+ DEFAULT_LIST,
371
+ {
372
+ key: "implies",
373
+ kind: "implies",
374
+ help: "implies"
375
+ }
376
+ ],
377
+ duration: [
378
+ DURATION_DEFAULT,
379
+ PLACEHOLDER,
380
+ {
381
+ key: "min",
382
+ kind: "duration",
383
+ help: "minDuration"
384
+ },
385
+ {
386
+ key: "max",
387
+ kind: "duration",
388
+ help: "maxDuration"
389
+ },
390
+ DISABLE_LABEL,
391
+ DISABLE_VALUE
392
+ ],
393
+ group: [{
394
+ key: "collapsed",
395
+ kind: "flag",
396
+ help: "collapsed",
397
+ conditional: true
398
+ }, FLATTEN],
399
+ list: [
400
+ TEMPLATE,
401
+ LIST_DEFAULT,
402
+ {
403
+ key: "items-collapsible",
404
+ kind: "flag",
405
+ help: "itemsCollapsible"
406
+ },
407
+ {
408
+ key: "min",
409
+ kind: "number",
410
+ help: "minItems"
411
+ },
412
+ {
413
+ key: "max",
414
+ kind: "number",
415
+ help: "maxItems"
416
+ }
417
+ ],
418
+ header: [
419
+ {
420
+ key: "text",
421
+ kind: "textarea",
422
+ help: "text",
423
+ required: true,
424
+ grow: true,
425
+ prose: true
426
+ },
427
+ {
428
+ key: "size",
429
+ kind: "number",
430
+ help: "size"
431
+ },
432
+ {
433
+ key: "bold",
434
+ kind: "flag",
435
+ help: "bold",
436
+ fallback: true
437
+ }
438
+ ],
439
+ step: [
440
+ {
441
+ key: "title",
442
+ kind: "perCopy",
443
+ help: "title",
444
+ required: true,
445
+ prose: true
446
+ },
447
+ {
448
+ key: "nextLabel",
449
+ kind: "text",
450
+ help: "nextLabel",
451
+ prose: true
452
+ },
453
+ {
454
+ key: "prevLabel",
455
+ kind: "text",
456
+ help: "prevLabel",
457
+ prose: true
458
+ },
459
+ {
460
+ key: "multi",
461
+ kind: "bool",
462
+ help: "multiStep",
463
+ label: "multiStep"
464
+ },
465
+ {
466
+ key: "min",
467
+ kind: "number",
468
+ help: "minCopies"
469
+ },
470
+ {
471
+ key: "max",
472
+ kind: "number",
473
+ help: "maxCopies"
474
+ }
475
+ ],
476
+ "compute-clusters": COMPUTE,
477
+ "compute-resources": COMPUTE,
478
+ bucket: [
479
+ RESOURCE_DEFAULT,
480
+ RESOURCE_DEFAULTS,
481
+ CSP,
482
+ {
483
+ key: "generateCredentials",
484
+ kind: "flag",
485
+ help: "generateCredentials"
486
+ },
487
+ MULTI
488
+ ],
489
+ region: [
490
+ DEFAULT_OBJECT,
491
+ CSP_REQUIRED,
492
+ AUTOSELECT
493
+ ],
494
+ zone: [
495
+ DEFAULT_OBJECT,
496
+ CSP_REQUIRED,
497
+ REGION,
498
+ AUTOSELECT
499
+ ],
500
+ "instance-type": [
501
+ DEFAULT_OBJECT,
502
+ CSP_REQUIRED,
503
+ REGION,
504
+ AUTOSELECT
505
+ ],
506
+ "slurm-accounts": [DEFAULT_FROM_INPUT, RESOURCE],
507
+ "slurm-partitions": [
508
+ DEFAULT_FROM_INPUT,
509
+ RESOURCE,
510
+ ACCOUNT
511
+ ],
512
+ "slurm-qos": [
513
+ DEFAULT_FROM_INPUT,
514
+ RESOURCE,
515
+ ACCOUNT,
516
+ {
517
+ key: "partition",
518
+ kind: "ref",
519
+ help: "partition",
520
+ refTypes: ["slurm-partitions"],
521
+ literal: false
522
+ }
523
+ ],
524
+ "pbs-queues": [DEFAULT_FROM_INPUT, RESOURCE],
525
+ "organization-groups": [DEFAULT_FROM_INPUT],
526
+ "ai-model": [
527
+ DEFAULT,
528
+ PLACEHOLDER,
529
+ AUTOSELECT
530
+ ],
531
+ allocations: [
532
+ DEFAULT,
533
+ PLACEHOLDER,
534
+ AUTOSELECT,
535
+ {
536
+ key: "includeEmpty",
537
+ kind: "bool",
538
+ help: "includeEmpty",
539
+ fallback: true
540
+ }
541
+ ],
542
+ "kubernetes-clusters": [DEFAULT_OBJECT, AUTOSELECT],
543
+ "kubernetes-namespaces": [
544
+ DEFAULT_OBJECT,
545
+ CLUSTER,
546
+ AUTOSELECT
547
+ ],
548
+ "kubernetes-pods": IN_NAMESPACE,
549
+ "kubernetes-pvc": IN_NAMESPACE,
550
+ "kubernetes-secrets": IN_NAMESPACE,
551
+ "kubernetes-configmaps": IN_NAMESPACE,
552
+ "kubernetes-statefulsets": IN_NAMESPACE,
553
+ "kubernetes-deployments": IN_NAMESPACE,
554
+ "kubernetes-services": IN_NAMESPACE,
555
+ "kubernetes-workloads": IN_NAMESPACE
556
+ };
557
+ var TEXT_FIELDS = [
558
+ "label",
559
+ "description",
560
+ "tooltip"
561
+ ];
562
+ var FLAG_FIELDS = [
563
+ "optional",
564
+ "hidden",
565
+ "disabled",
566
+ "ignore"
567
+ ];
568
+ var COMMON_KEYS = {
569
+ group: [
570
+ "label",
571
+ "description",
572
+ "tooltip",
573
+ "hidden",
574
+ "ignore"
575
+ ],
576
+ header: [
577
+ "label",
578
+ "description",
579
+ "tooltip",
580
+ "hidden"
581
+ ],
582
+ step: ["description"]
583
+ };
584
+ var ALL_COMMON = [...TEXT_FIELDS, ...FLAG_FIELDS];
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
+ ];
601
+ function inputTypes() {
602
+ return INPUT_TYPE_GROUPS.flatMap(([, types]) => types);
603
+ }
604
+ function commonKeys(type) {
605
+ return COMMON_KEYS[type] ?? ALL_COMMON;
606
+ }
607
+ /** Every key the dialog can write for an input of `type`. */
608
+ function offeredInputKeys(editing, type) {
609
+ const children = editing.inputChildrenKey(type);
610
+ return [
611
+ "type",
612
+ ...commonKeys(type),
613
+ ...(TYPE_PROPS[type] ?? []).map((prop) => prop.key),
614
+ ...children ? [children] : []
615
+ ];
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
+ }
1583
+ function commonProp(key) {
1584
+ const flag = FLAG_FIELDS.includes(key);
1585
+ return {
1586
+ key,
1587
+ kind: flag ? "flag" : key === "label" ? "text" : "textarea",
1588
+ help: key,
1589
+ ...flag ? { conditional: true } : { prose: true },
1590
+ ...key === "description" || key === "tooltip" ? { grow: true } : {}
1591
+ };
1592
+ }
1593
+ var COMMON_PROPS = ALL_COMMON.map(commonProp);
1594
+ var STEP_DESCRIPTION = {
1595
+ ...commonProp("description"),
1596
+ kind: "perCopy"
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
+ }
1608
+ function draftKey(prop) {
1609
+ return `${prop.kind}:${prop.key}`;
1610
+ }
1611
+ var ALL_PROPS = [...new Map([
1612
+ ...COMMON_PROPS,
1613
+ STEP_DESCRIPTION,
1614
+ ...Object.values(TYPE_PROPS).flat()
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
+ }
2109
+ //#endregion
2110
+ export { INPUT_TYPE_GROUPS, InputDialog, TypeBadge, allNames, commonKeys, inputTypes, newInputDefinition, offeredInputKeys, useNewInputs, withCreatedInputs };