@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
@@ -0,0 +1,75 @@
1
+ import { BiRedo } from "../icons/bi/BiRedo.js";
2
+ import { BiUndo } from "../icons/bi/BiUndo.js";
3
+ import { IoSettingsOutline } from "../icons/io5/IoSettingsOutline.js";
4
+ import { TOOLTIP_ID } from "../components/Tooltip.js";
5
+ import { IconButton } from "../components/IconButton.js";
6
+ import { useGraphEditorStrings } from "./editorStrings.js";
7
+ import { ProblemsButton } from "./ProblemsButton.js";
8
+ import { ShortcutsButton } from "./ShortcutsButton.js";
9
+ import cx from "classnames";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/graph/editorChrome.tsx
12
+ /** What a drag carries, beside the pointer at `at` in its layer's coordinates. */
13
+ function DragLabel({ at, text, refused = false }) {
14
+ return /* @__PURE__ */ jsx("div", {
15
+ className: cx("pointer-events-none absolute z-30 rounded-md border bg-(--theme-panel-bg) px-2 py-1 text-xs shadow", refused ? "border-(--theme-error) text-(--theme-error)" : "theme-border"),
16
+ style: {
17
+ left: at.x + 12,
18
+ top: at.y + 12
19
+ },
20
+ children: text
21
+ });
22
+ }
23
+ function BarDivider() {
24
+ return /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-5 w-px bg-(--theme-border)" });
25
+ }
26
+ /** A toolbar chip that adds something: click to add it, or, given `onPointerDown`, drag it to where it goes. */
27
+ function AddChip({ icon, label, hint, className, onPointerDown, onClick }) {
28
+ return /* @__PURE__ */ jsxs("button", {
29
+ type: "button",
30
+ className: cx("flex h-7 items-center gap-1 whitespace-nowrap rounded px-2 text-xs theme-hover", onPointerDown ? "cursor-grab" : "cursor-pointer", className),
31
+ "data-tooltip-id": TOOLTIP_ID,
32
+ "data-tooltip-content": hint,
33
+ onPointerDown,
34
+ onClick,
35
+ children: [icon, label]
36
+ });
37
+ }
38
+ /** The bar both editors draw: problems, undo and redo, the editor's own controls, settings, shortcuts. */
39
+ function EditorBar({ editor, groups, className, children }) {
40
+ const t = useGraphEditorStrings();
41
+ return /* @__PURE__ */ jsxs("div", {
42
+ className: cx("flex max-w-full flex-wrap items-center justify-end gap-0.5 rounded-md border theme-border bg-(--theme-panel-bg) p-0.5", className),
43
+ children: [
44
+ !!editor.listedProblems?.length && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ProblemsButton, { problems: editor.listedProblems }), /* @__PURE__ */ jsx(BarDivider, {})] }),
45
+ /* @__PURE__ */ jsx(IconButton, {
46
+ icon: /* @__PURE__ */ jsx(BiUndo, { className: "h-4 w-4" }),
47
+ label: t.undo,
48
+ size: "sm",
49
+ variant: "ghost",
50
+ disabled: !editor.canUndo,
51
+ onClick: editor.onUndo
52
+ }),
53
+ /* @__PURE__ */ jsx(IconButton, {
54
+ icon: /* @__PURE__ */ jsx(BiRedo, { className: "h-4 w-4" }),
55
+ label: t.redo,
56
+ size: "sm",
57
+ variant: "ghost",
58
+ disabled: !editor.canRedo,
59
+ onClick: editor.onRedo
60
+ }),
61
+ /* @__PURE__ */ jsx(BarDivider, {}),
62
+ children,
63
+ editor.onOpenSettings && /* @__PURE__ */ jsx(IconButton, {
64
+ icon: /* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-4 w-4" }),
65
+ label: t.workflowSettings,
66
+ size: "sm",
67
+ variant: "ghost",
68
+ onClick: editor.onOpenSettings
69
+ }),
70
+ /* @__PURE__ */ jsx(ShortcutsButton, { groups })
71
+ ]
72
+ });
73
+ }
74
+ //#endregion
75
+ export { AddChip, BarDivider, DragLabel, EditorBar };
@@ -0,0 +1,535 @@
1
+ import { FaPlus } from "../icons/fa/FaPlus.js";
2
+ import { IoMdClose } from "../icons/io/IoMdClose.js";
3
+ import { LuBraces } from "../icons/lu/LuBraces.js";
4
+ import { LuChevronRight } from "../icons/lu/LuChevronRight.js";
5
+ import { withPositionKeys } from "../components/keys.js";
6
+ import { TOOLTIP_ID } from "../components/Tooltip.js";
7
+ import { BareModal, modalPanelClasses } from "../components/BareModal.js";
8
+ import { Button } from "../components/Button.js";
9
+ import { ghostButtonClasses, primaryButtonClasses } from "../components/ghostButton.js";
10
+ import { FilterPill } from "../components/FilterPill.js";
11
+ import { IconButton } from "../components/IconButton.js";
12
+ import { Input, Textarea } from "../components/Input.js";
13
+ import { Toggle } from "../components/Toggle.js";
14
+ import { useGraphEditorStrings } from "./editorStrings.js";
15
+ import { asRecord } from "./records.js";
16
+ import { useFieldLint } from "./fieldProblems.js";
17
+ import { SuggestionInput } from "./SuggestionInput.js";
18
+ import cx from "classnames";
19
+ import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
20
+ import { jsx, jsxs } from "react/jsx-runtime";
21
+ //#region src/graph/editorFields.tsx
22
+ var EXPRESSION = /^\$\{\{.*\}\}$/;
23
+ var ENV_KEY = /^[a-zA-Z0-9_]+$/;
24
+ var EMPTY_EXPRESSION = /^\$\{\{\s*\}\}$/;
25
+ var DURATION = /^(\d+)(d|h|m|s)?$/;
26
+ function withoutUndefined(value) {
27
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
28
+ }
29
+ function text(value) {
30
+ return value === void 0 || value === null ? "" : String(value);
31
+ }
32
+ function isScalar(value) {
33
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
34
+ }
35
+ function parseScalar(value) {
36
+ if (/^-?\d+(\.\d+)?$/.test(value) && String(Number(value)) === value) return Number(value);
37
+ if (value === "true" || value === "false") return value === "true";
38
+ return value;
39
+ }
40
+ function sameValue(a, b) {
41
+ if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, i) => sameValue(item, b[i]));
42
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
43
+ const ak = Object.keys(a);
44
+ const bk = Object.keys(b);
45
+ return ak.length === bk.length && ak.every((k) => sameValue(a[k], b[k]));
46
+ }
47
+ return a === b;
48
+ }
49
+ /** Keys whose value changed: undefined removes a key, anything else writes it. */
50
+ function diffPatch(original, next) {
51
+ const set = {};
52
+ const unset = [];
53
+ for (const [key, value] of Object.entries(next)) if (value === void 0) {
54
+ if (Object.hasOwn(original, key)) unset.push(key);
55
+ } else if (!sameValue(original[key], value)) set[key] = value;
56
+ return {
57
+ set,
58
+ unset
59
+ };
60
+ }
61
+ function isEmptyPatch(patch) {
62
+ return Object.keys(patch.set).length === 0 && patch.unset.length === 0;
63
+ }
64
+ function conditionText(value) {
65
+ return typeof value === "boolean" ? String(value) : text(value);
66
+ }
67
+ function parseCondition(value) {
68
+ const trimmed = value.trim();
69
+ if (!trimmed) return;
70
+ if (trimmed === "true" || trimmed === "false") return trimmed === "true";
71
+ return trimmed;
72
+ }
73
+ function durationError(value, t, pattern = DURATION) {
74
+ const trimmed = value.trim();
75
+ return !trimmed || pattern.test(trimmed) || EXPRESSION.test(trimmed) ? void 0 : t.invalidDuration;
76
+ }
77
+ function expressionError(value, t) {
78
+ const trimmed = value.trim();
79
+ return !trimmed || EXPRESSION.test(trimmed) && !EMPTY_EXPRESSION.test(trimmed) ? void 0 : t.invalidExpressionValue;
80
+ }
81
+ function orUndefined(value) {
82
+ return value.trim() ? value.trim() : void 0;
83
+ }
84
+ function countError(value, min, message) {
85
+ const trimmed = value.trim();
86
+ if (!trimmed || EXPRESSION.test(trimmed)) return;
87
+ return /^\d+$/.test(trimmed) && Number(trimmed) >= min ? void 0 : message;
88
+ }
89
+ function parseCount(value) {
90
+ const trimmed = value.trim();
91
+ if (!trimmed) return;
92
+ return /^\d+$/.test(trimmed) ? Number(trimmed) : trimmed;
93
+ }
94
+ function parseJson(value) {
95
+ try {
96
+ return {
97
+ ok: true,
98
+ value: JSON.parse(value)
99
+ };
100
+ } catch {
101
+ return { ok: false };
102
+ }
103
+ }
104
+ function unchangedValue(text, original, nested = false) {
105
+ return original !== void 0 && text === (nested ? JSON.stringify(original) : String(original));
106
+ }
107
+ /** Rows for a map; with `nested` false a non-scalar is shown as its JSON text and saved as a string. */
108
+ function rowsFrom(value, nested = true) {
109
+ return Object.entries(asRecord(value)).map(([key, item]) => isScalar(item) ? {
110
+ key,
111
+ value: String(item),
112
+ original: item
113
+ } : nested ? {
114
+ key,
115
+ value: JSON.stringify(item),
116
+ nested: true,
117
+ original: item
118
+ } : {
119
+ key,
120
+ value: JSON.stringify(item) ?? ""
121
+ });
122
+ }
123
+ function rowsTo(rows, parse) {
124
+ if (rows.length === 0) return;
125
+ return Object.fromEntries(rows.map((row) => [row.key.trim(), unchangedValue(row.value, row.original, row.nested) ? row.original : row.nested ? parseJson(row.value).value : parse(row.value)]));
126
+ }
127
+ function rowsError(rows, keyPattern, keyMessage, t, valueError) {
128
+ const keys = rows.map((row) => row.key.trim());
129
+ for (const [i, row] of rows.entries()) {
130
+ const key = keys[i] ?? "";
131
+ if (!keyPattern.test(key)) return keyMessage;
132
+ if (keys.indexOf(key) !== i) return t.duplicateKey;
133
+ const error = row.nested ? parseJson(row.value).ok ? void 0 : t.invalidJson : valueError?.(row.value);
134
+ if (error) return error;
135
+ }
136
+ }
137
+ function flagOf(value) {
138
+ return typeof value === "boolean" || typeof value === "string" ? value : void 0;
139
+ }
140
+ function flagError(value, t) {
141
+ return typeof value === "string" ? expressionError(value, t) : void 0;
142
+ }
143
+ function flagValue(checked, fallback, original) {
144
+ if (checked !== fallback) return checked;
145
+ return original === checked ? checked : void 0;
146
+ }
147
+ /** Whether each section of a dialog is open, kept while the dialog is up so switching views keeps it. */
148
+ var SectionMemory = createContext(null);
149
+ function useSectionMemory() {
150
+ return useState(() => /* @__PURE__ */ new Map())[0];
151
+ }
152
+ function Section({ title, description, open = false, alert = false, children }) {
153
+ const memory = useContext(SectionMemory);
154
+ const [initial] = useState(() => {
155
+ const decided = memory?.get(title) ?? open;
156
+ memory?.set(title, decided);
157
+ return decided;
158
+ });
159
+ const details = useRef(null);
160
+ useEffect(() => {
161
+ if (alert && details.current && !details.current.open) details.current.open = true;
162
+ }, [alert]);
163
+ return /* @__PURE__ */ jsxs("details", {
164
+ ref: details,
165
+ open: initial,
166
+ onToggle: (e) => memory?.set(title, e.currentTarget.open),
167
+ className: "group border-t theme-border",
168
+ children: [/* @__PURE__ */ jsxs("summary", {
169
+ className: "flex cursor-pointer select-none list-none items-center gap-1.5 py-2.5 text-sm font-semibold [&::-webkit-details-marker]:hidden",
170
+ children: [/* @__PURE__ */ jsx(LuChevronRight, {
171
+ "aria-hidden": "true",
172
+ className: "h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform group-open:rotate-90 motion-reduce:transition-none"
173
+ }), title]
174
+ }), /* @__PURE__ */ jsxs("div", {
175
+ className: "flex flex-col gap-3 pb-4",
176
+ children: [description && /* @__PURE__ */ jsx("div", {
177
+ className: "text-xs theme-muted-text",
178
+ children: description
179
+ }), children]
180
+ })]
181
+ });
182
+ }
183
+ function FieldError({ message }) {
184
+ return message ? /* @__PURE__ */ jsx("div", {
185
+ className: "text-xs text-(--theme-error)",
186
+ children: message
187
+ }) : null;
188
+ }
189
+ /** The YAML key a field sets, beside its name; left out where the name already says it. */
190
+ function KeyHint({ label, yamlKey }) {
191
+ return label.trim().toLowerCase() === yamlKey.toLowerCase() ? null : /* @__PURE__ */ jsx("code", {
192
+ "aria-hidden": "true",
193
+ className: "font-mono text-xs theme-muted-text",
194
+ children: yamlKey
195
+ });
196
+ }
197
+ /** Label props for an Input or Textarea that sets `yamlKey`. */
198
+ function labelled(label, yamlKey) {
199
+ return {
200
+ label,
201
+ labelHint: /* @__PURE__ */ jsx(KeyHint, {
202
+ label,
203
+ yamlKey
204
+ })
205
+ };
206
+ }
207
+ /** A field's name and help text, for controls without their own label; `actions` sit at its right. */
208
+ function FieldLabel({ label, yamlKey, description, htmlFor, descriptionId, actions }) {
209
+ const nameClass = "text-[0.8125rem] font-medium";
210
+ const text = /* @__PURE__ */ jsxs("div", {
211
+ className: "flex min-w-0 flex-col",
212
+ children: [/* @__PURE__ */ jsxs("div", {
213
+ className: "flex flex-wrap items-baseline gap-x-1.5",
214
+ children: [htmlFor ? /* @__PURE__ */ jsx("label", {
215
+ htmlFor,
216
+ className: nameClass,
217
+ children: label
218
+ }) : /* @__PURE__ */ jsx("span", {
219
+ className: nameClass,
220
+ children: label
221
+ }), yamlKey && /* @__PURE__ */ jsx(KeyHint, {
222
+ label,
223
+ yamlKey
224
+ })]
225
+ }), /* @__PURE__ */ jsx("span", {
226
+ id: descriptionId,
227
+ className: "text-xs theme-muted-text",
228
+ children: description
229
+ })]
230
+ });
231
+ return actions ? /* @__PURE__ */ jsxs("div", {
232
+ className: "flex items-start justify-between gap-3",
233
+ children: [text, /* @__PURE__ */ jsx("div", {
234
+ className: "flex shrink-0 items-center gap-1",
235
+ children: actions
236
+ })]
237
+ }) : text;
238
+ }
239
+ /** A field's label and description above the editor that sets it. */
240
+ function LabelledField({ children, ...label }) {
241
+ return /* @__PURE__ */ jsxs("div", {
242
+ className: "flex flex-col gap-1.5",
243
+ children: [/* @__PURE__ */ jsx(FieldLabel, { ...label }), children]
244
+ });
245
+ }
246
+ /**
247
+ * Switches a field between its own control and a typed expression, where the schema takes one. Its
248
+ * name says what pressing it does, so it isn't also announced as pressed.
249
+ */
250
+ function ExpressionToggle({ active, onChange }) {
251
+ const t = useGraphEditorStrings();
252
+ return /* @__PURE__ */ jsx(IconButton, {
253
+ icon: /* @__PURE__ */ jsx(LuBraces, { className: "h-4 w-4" }),
254
+ label: active ? t.stopExpression : t.useExpression,
255
+ size: "sm",
256
+ variant: "ghost",
257
+ active,
258
+ onClick: () => onChange(!active)
259
+ });
260
+ }
261
+ function ToggleField({ label, yamlKey, description, checked, onChange, disabled }) {
262
+ return /* @__PURE__ */ jsxs("div", {
263
+ className: "flex items-center justify-between gap-3",
264
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
265
+ label,
266
+ yamlKey,
267
+ description
268
+ }), /* @__PURE__ */ jsx(Toggle, {
269
+ checked,
270
+ onChange,
271
+ label,
272
+ ...disabled ? { disabled } : {}
273
+ })]
274
+ });
275
+ }
276
+ function ChoiceButtons({ options, value, onChange }) {
277
+ return /* @__PURE__ */ jsx("div", {
278
+ className: "flex flex-wrap gap-1",
279
+ children: options.map((option) => /* @__PURE__ */ jsx(FilterPill, {
280
+ active: value === option.value,
281
+ onClick: () => onChange(option.value),
282
+ children: option.label
283
+ }, option.value))
284
+ });
285
+ }
286
+ function AddRowButton({ label, onClick }) {
287
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
288
+ type: "button",
289
+ variant: "ghost",
290
+ size: "sm",
291
+ fullWidth: false,
292
+ icon: /* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }),
293
+ iconPosition: "left",
294
+ onClick,
295
+ children: label
296
+ }) });
297
+ }
298
+ function KeyValueEditor({ rows, onChange, error, valuePlaceholder, keyPlaceholder, addLabel, valueSuggestions = [], lint = false }) {
299
+ const t = useGraphEditorStrings();
300
+ return /* @__PURE__ */ jsxs("div", {
301
+ className: "flex flex-col gap-2",
302
+ children: [
303
+ withPositionKeys(rows).map(({ key, item: row }, i) => /* @__PURE__ */ jsx(KeyValueRow, {
304
+ row,
305
+ onChange: (next) => onChange(updateAt(rows, i, next)),
306
+ onRemove: () => onChange(removeAt(rows, i)),
307
+ keyPlaceholder: keyPlaceholder ?? t.key,
308
+ valuePlaceholder: valuePlaceholder ?? t.value,
309
+ valueSuggestions,
310
+ lint
311
+ }, key)),
312
+ /* @__PURE__ */ jsx(FieldError, { message: error }),
313
+ /* @__PURE__ */ jsx(AddRowButton, {
314
+ label: addLabel ?? t.addRow,
315
+ onClick: () => onChange([...rows, {
316
+ key: "",
317
+ value: ""
318
+ }])
319
+ })
320
+ ]
321
+ });
322
+ }
323
+ function KeyValueRow({ row, onChange, onRemove, keyPlaceholder, valuePlaceholder, valueSuggestions, lint: linted }) {
324
+ const t = useGraphEditorStrings();
325
+ const id = useId();
326
+ const lint = useFieldLint(linted ? row.key.trim() || void 0 : void 0, id, row.value, (value) => onChange({
327
+ ...row,
328
+ value
329
+ }));
330
+ return /* @__PURE__ */ jsxs("div", {
331
+ className: "flex flex-col gap-1.5",
332
+ children: [
333
+ /* @__PURE__ */ jsxs("div", {
334
+ className: "flex items-center gap-2",
335
+ children: [
336
+ /* @__PURE__ */ jsx("div", {
337
+ className: "w-2/5",
338
+ children: /* @__PURE__ */ jsx(Input, {
339
+ mono: true,
340
+ "aria-label": t.key,
341
+ placeholder: keyPlaceholder,
342
+ value: row.key,
343
+ onChange: (e) => onChange({
344
+ ...row,
345
+ key: e.target.value
346
+ })
347
+ })
348
+ }),
349
+ /* @__PURE__ */ jsx("div", {
350
+ className: "flex-1",
351
+ title: row.nested ? t.jsonValue : void 0,
352
+ children: /* @__PURE__ */ jsx(SuggestionInput, {
353
+ id,
354
+ ariaLabel: t.value,
355
+ placeholder: valuePlaceholder,
356
+ value: row.value,
357
+ suggestions: valueSuggestions.map((value) => ({ value })),
358
+ onChange: (value) => onChange({
359
+ ...row,
360
+ value
361
+ })
362
+ })
363
+ }),
364
+ /* @__PURE__ */ jsx(IconButton, {
365
+ icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
366
+ label: t.removeRow,
367
+ variant: "ghost",
368
+ size: "sm",
369
+ onClick: onRemove
370
+ })
371
+ ]
372
+ }),
373
+ /* @__PURE__ */ jsx(FieldError, { message: lint.message }),
374
+ lint.fixes
375
+ ]
376
+ });
377
+ }
378
+ function updateAt(items, index, item) {
379
+ return items.map((current, i) => i === index ? item : current);
380
+ }
381
+ function removeAt(items, index) {
382
+ return items.filter((_, i) => i !== index);
383
+ }
384
+ /** A list of single values, such as permissions or variable names. */
385
+ function StringListEditor({ label, values, onChange, addLabel, placeholder, error, suggestions = [] }) {
386
+ const t = useGraphEditorStrings();
387
+ return /* @__PURE__ */ jsxs("div", {
388
+ className: "flex flex-col gap-2",
389
+ children: [
390
+ withPositionKeys(values).map(({ key, item: value }, i) => /* @__PURE__ */ jsxs("div", {
391
+ className: "flex items-center gap-2",
392
+ children: [/* @__PURE__ */ jsx("div", {
393
+ className: "flex-1",
394
+ children: /* @__PURE__ */ jsx(SuggestionInput, {
395
+ ariaLabel: t.listItem(label, i + 1),
396
+ value,
397
+ placeholder,
398
+ suggestions: suggestions.map((option) => ({ value: option })),
399
+ onChange: (next) => onChange(updateAt(values, i, next))
400
+ })
401
+ }), /* @__PURE__ */ jsx(IconButton, {
402
+ icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
403
+ label: t.removeRow,
404
+ variant: "ghost",
405
+ size: "sm",
406
+ onClick: () => onChange(removeAt(values, i))
407
+ })]
408
+ }, key)),
409
+ /* @__PURE__ */ jsx(FieldError, { message: error }),
410
+ /* @__PURE__ */ jsx(AddRowButton, {
411
+ label: addLabel,
412
+ onClick: () => onChange([...values, ""])
413
+ })
414
+ ]
415
+ });
416
+ }
417
+ function SuggestedInput({ label, yamlKey, description, value, onChange, suggestions, error, placeholder }) {
418
+ const id = useId();
419
+ const lint = useFieldLint(yamlKey, id, value, onChange);
420
+ return /* @__PURE__ */ jsxs(LabelledField, {
421
+ label,
422
+ yamlKey,
423
+ description: description ?? "",
424
+ htmlFor: id,
425
+ ...description ? { descriptionId: `${id}-help` } : {},
426
+ children: [
427
+ /* @__PURE__ */ jsx(SuggestionInput, {
428
+ id,
429
+ "aria-describedby": description ? `${id}-help` : void 0,
430
+ value,
431
+ placeholder,
432
+ suggestions: suggestions.map((option) => ({ value: option })),
433
+ onChange
434
+ }),
435
+ /* @__PURE__ */ jsx(FieldError, { message: error ?? lint.message }),
436
+ lint.fixes
437
+ ]
438
+ });
439
+ }
440
+ /** A text area that starts one line tall and grows with its text. */
441
+ function useGrowingArea(value) {
442
+ const ref = useRef(null);
443
+ useLayoutEffect(() => {
444
+ const area = ref.current;
445
+ if (area) {
446
+ area.style.height = "auto";
447
+ area.style.height = `${area.scrollHeight + area.offsetHeight - area.clientHeight}px`;
448
+ }
449
+ }, [value]);
450
+ return ref;
451
+ }
452
+ var GROWING_AREA = "!min-h-0 !resize-none overflow-hidden";
453
+ function GrowingTextarea(props) {
454
+ const ref = useGrowingArea(props.value);
455
+ return /* @__PURE__ */ jsx(Textarea, {
456
+ ...props,
457
+ ref,
458
+ rows: 1,
459
+ className: GROWING_AREA
460
+ });
461
+ }
462
+ /** The dialogs' "open when adding" switch, when the host keeps the setting. */
463
+ function openOnAddOf(editor) {
464
+ return editor?.onOpenOnAddChange ? {
465
+ open: editor.openOnAdd !== false,
466
+ onChange: editor.onOpenOnAddChange
467
+ } : void 0;
468
+ }
469
+ function OpenOnAddSwitch({ open, onChange }) {
470
+ const t = useGraphEditorStrings();
471
+ return /* @__PURE__ */ jsxs("label", {
472
+ className: "flex cursor-pointer items-center gap-2 text-xs theme-muted-text",
473
+ "data-tooltip-id": TOOLTIP_ID,
474
+ "data-tooltip-content": t.openOnAddHelp,
475
+ children: [/* @__PURE__ */ jsx("input", {
476
+ type: "checkbox",
477
+ className: "h-4 w-4 cursor-pointer accent-(--theme-element)",
478
+ checked: open,
479
+ onChange: (e) => onChange(e.target.checked)
480
+ }), t.openOnAdd]
481
+ });
482
+ }
483
+ function DialogShell({ title, onClose, onSubmit, saveDisabled, dirty, locked = false, footerStart, headerEnd, openOnAdd, children }) {
484
+ const t = useGraphEditorStrings();
485
+ const submit = () => {
486
+ if (!saveDisabled) onSubmit();
487
+ };
488
+ return /* @__PURE__ */ jsx(BareModal, {
489
+ open: true,
490
+ onClose,
491
+ ariaLabel: title,
492
+ align: "center",
493
+ preventClose: dirty || locked,
494
+ className: cx(modalPanelClasses, "flex max-h-[85vh] w-full max-w-2xl flex-col"),
495
+ children: /* @__PURE__ */ jsxs("div", {
496
+ className: "flex min-h-0 flex-col",
497
+ children: [
498
+ /* @__PURE__ */ jsxs("div", {
499
+ className: "flex items-center gap-3 border-b theme-border px-5 py-4",
500
+ children: [/* @__PURE__ */ jsx("div", {
501
+ className: "flex-1 text-[1rem] font-semibold",
502
+ children: title
503
+ }), headerEnd]
504
+ }),
505
+ /* @__PURE__ */ jsx("div", {
506
+ className: "flex min-h-0 flex-col gap-3 overflow-y-auto px-5 py-4",
507
+ children
508
+ }),
509
+ /* @__PURE__ */ jsxs("div", {
510
+ className: "flex items-center gap-2 border-t theme-border px-5 py-3",
511
+ children: [
512
+ footerStart,
513
+ openOnAdd && /* @__PURE__ */ jsx(OpenOnAddSwitch, { ...openOnAdd }),
514
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
515
+ /* @__PURE__ */ jsx("button", {
516
+ type: "button",
517
+ className: ghostButtonClasses,
518
+ onClick: onClose,
519
+ children: t.cancel
520
+ }),
521
+ /* @__PURE__ */ jsx("button", {
522
+ type: "button",
523
+ disabled: saveDisabled,
524
+ onClick: submit,
525
+ className: cx(primaryButtonClasses, "disabled:cursor-not-allowed disabled:opacity-50"),
526
+ children: t.save
527
+ })
528
+ ]
529
+ })
530
+ ]
531
+ })
532
+ });
533
+ }
534
+ //#endregion
535
+ export { AddRowButton, ChoiceButtons, DURATION, DialogShell, ENV_KEY, EXPRESSION, ExpressionToggle, FieldError, FieldLabel, GROWING_AREA, GrowingTextarea, KeyHint, KeyValueEditor, LabelledField, Section, SectionMemory, StringListEditor, SuggestedInput, ToggleField, asRecord, conditionText, countError, diffPatch, durationError, expressionError, flagError, flagOf, flagValue, isEmptyPatch, isScalar, labelled, openOnAddOf, orUndefined, parseCondition, parseCount, parseJson, parseScalar, removeAt, rowsError, rowsFrom, rowsTo, sameValue, text, unchangedValue, updateAt, useGrowingArea, useSectionMemory, withoutUndefined };