@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,536 @@
1
+ import { useRepoSuggestions, useWorkflowActions, useWorkflowEditing, useWorkflowJsonResolver } from "../components/Provider.js";
2
+ import { IoMdClose } from "../icons/io/IoMdClose.js";
3
+ import { IconButton } from "../components/IconButton.js";
4
+ import { Input, Textarea } from "../components/Input.js";
5
+ import { useGraphEditorStrings, useInputsEditorStrings } from "./editorStrings.js";
6
+ import { asRecord } from "./records.js";
7
+ import { loadUsesInputs, splitAtRef } from "./usesInputs.js";
8
+ import { FixButtons, LintScope, useFieldLint, useFieldProblems } from "./fieldProblems.js";
9
+ import { SuggestionInput } from "./SuggestionInput.js";
10
+ import { ChoiceButtons, EXPRESSION, FieldError, FieldLabel, KeyValueEditor, LabelledField, StringListEditor, SuggestedInput, ToggleField, isScalar, labelled, parseJson, parseScalar, rowsError, rowsFrom, rowsTo, sameValue } from "./editorFields.js";
11
+ import { ValueOrInputField, refSuggestions } from "./inputRefs.js";
12
+ import { DynamicForm } from "../form/Form.js";
13
+ import { useEffect, useId, useState } from "react";
14
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
+ //#region src/graph/stepWith.tsx
16
+ var REPO_INPUTS = [{
17
+ key: "$yaml",
18
+ kind: "text",
19
+ label: "$yaml",
20
+ description: ""
21
+ }, {
22
+ key: "$thumbnail",
23
+ kind: "text",
24
+ label: "$thumbnail",
25
+ description: ""
26
+ }];
27
+ var GITLAB_INPUTS = [...REPO_INPUTS, {
28
+ key: "$host",
29
+ kind: "text",
30
+ label: "$host",
31
+ description: ""
32
+ }];
33
+ var allSpecs = (actions) => [...Object.values(actions).flatMap((action) => action.inputs), ...GITLAB_INPUTS];
34
+ /** Every `with` key the step dialog writes for `actions`, for a schema coverage test. */
35
+ function withFields(actions) {
36
+ return [...new Set(allSpecs(actions).flatMap((spec) => [spec.key, ...(spec.fields ?? []).map((field) => `${spec.key}.${field.key}`)]))];
37
+ }
38
+ var INPUT_KEY = /^[a-zA-Z0-9_-]+$/;
39
+ function usesKind(uses, actions) {
40
+ if (Object.hasOwn(actions, uses)) return "action";
41
+ if (uses.startsWith("github/")) return "github";
42
+ if (uses.startsWith("gitlab/")) return "gitlab";
43
+ return /^(marketplace|workflow)\//.test(uses) ? "subworkflow" : "other";
44
+ }
45
+ /** Named inputs for `uses`: an action's own, or a repository form's `$` keys. */
46
+ function specsFor(uses, actions) {
47
+ const kind = usesKind(uses, actions);
48
+ return kind === "action" ? actions[uses]?.inputs ?? [] : kind === "github" ? REPO_INPUTS : kind === "gitlab" ? GITLAB_INPUTS : [];
49
+ }
50
+ function textDraft(value) {
51
+ if (value === void 0 || value === null) return "";
52
+ return isScalar(value) ? String(value) : JSON.stringify(value);
53
+ }
54
+ function draftFor(spec, value) {
55
+ switch (spec.kind) {
56
+ case "list": return Array.isArray(value) ? value.map(textDraft) : [];
57
+ case "bool": return typeof value === "boolean" ? value : void 0;
58
+ case "flags": return rowsFrom(value);
59
+ case "target": {
60
+ const target = asRecord(value);
61
+ return Object.fromEntries((spec.fields ?? []).map((field) => [field.key, textDraft(target[field.key])]));
62
+ }
63
+ default: return textDraft(value);
64
+ }
65
+ }
66
+ function withDraftFrom(value, actions) {
67
+ const original = asRecord(value);
68
+ const values = {};
69
+ for (const spec of allSpecs(actions)) values[`${spec.kind}:${spec.key}`] = draftFor(spec, original[spec.key]);
70
+ return {
71
+ values,
72
+ rows: rowsFrom(Object.fromEntries(Object.entries(original).filter(([key]) => !key.startsWith("$")))),
73
+ removed: []
74
+ };
75
+ }
76
+ var slot = (spec) => `${spec.kind}:${spec.key}`;
77
+ function valueFor(spec, draft) {
78
+ switch (spec.kind) {
79
+ case "list": {
80
+ const items = draft.map((item) => item.trim()).filter(Boolean);
81
+ return items.length > 0 ? items : void 0;
82
+ }
83
+ case "bool": return draft;
84
+ case "flags": return rowsTo(draft, parseScalar);
85
+ case "target": {
86
+ const entries = Object.entries(draft).map(([key, item]) => [key, item.trim()]).filter(([, item]) => item);
87
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
88
+ }
89
+ case "number":
90
+ case "any": {
91
+ const raw = String(draft).trim();
92
+ return raw ? parseScalar(raw) : void 0;
93
+ }
94
+ case "textarea": return String(draft) ? String(draft) : void 0;
95
+ default: {
96
+ const raw = String(draft).trim();
97
+ return raw ? raw : void 0;
98
+ }
99
+ }
100
+ }
101
+ /** Keys the step already has that `uses` doesn't take: any for an action, `$` ones otherwise. */
102
+ function extraKeys(uses, actions, original, draft) {
103
+ const kind = usesKind(uses, actions);
104
+ if (kind === "other") return [];
105
+ const known = new Set(specsFor(uses, actions).map((spec) => spec.key));
106
+ return Object.keys(asRecord(original)).filter((key) => !known.has(key) && !draft.removed.includes(key) && (kind === "action" || key.startsWith("$")));
107
+ }
108
+ /** The step's `with` for `uses`, or its original when nothing changed. */
109
+ function withValue(uses, actions, draft, initial, originalUses, original) {
110
+ if (uses === originalUses && sameValue(draft, initial)) return original;
111
+ const kind = usesKind(uses, actions);
112
+ const out = {};
113
+ for (const spec of specsFor(uses, actions)) {
114
+ const value = valueFor(spec, draft.values[slot(spec)]);
115
+ if (value !== void 0) out[spec.key] = value;
116
+ }
117
+ if (kind !== "action") Object.assign(out, rowsTo(draft.rows, parseScalar));
118
+ const kept = asRecord(original);
119
+ for (const key of extraKeys(uses, actions, original, draft)) out[key] = kept[key];
120
+ return Object.keys(out).length > 0 ? out : void 0;
121
+ }
122
+ function withError(uses, actions, draft, original, t) {
123
+ const kind = usesKind(uses, actions);
124
+ if (extraKeys(uses, actions, original, draft).length > 0) return t.notActionInput;
125
+ for (const spec of specsFor(uses, actions)) {
126
+ const value = valueFor(spec, draft.values[slot(spec)]);
127
+ if (spec.required && value === void 0) return t.missingInput(spec.key);
128
+ if (spec.kind === "number" && value !== void 0 && typeof value !== "number") return t.invalidNumberInput;
129
+ if (spec.kind === "choice" && typeof value === "string" && !spec.choices?.includes(value) && !(spec.expression && EXPRESSION.test(value))) return t.invalidChoice(spec.key);
130
+ if (spec.kind === "flags") {
131
+ const error = rowsError(draft.values[slot(spec)], INPUT_KEY, t.invalidKey, t);
132
+ if (error) return error;
133
+ }
134
+ }
135
+ return kind === "action" ? void 0 : rowsError(draft.rows, kind === "other" ? /^\$?[a-zA-Z0-9_-]+$/ : INPUT_KEY, t.invalidKey, t);
136
+ }
137
+ function SpecField({ spec, draft, onChange, references, source }) {
138
+ const t = useGraphEditorStrings();
139
+ const { description, label } = spec;
140
+ if (spec.refTypes) return /* @__PURE__ */ jsx(ValueOrInputField, {
141
+ label,
142
+ yamlKey: spec.key,
143
+ description,
144
+ value: String(draft),
145
+ onChange,
146
+ source,
147
+ types: spec.refTypes,
148
+ ...spec.suffix ? { suffix: spec.suffix } : {},
149
+ suggestions: spec.choices ?? []
150
+ });
151
+ switch (spec.kind) {
152
+ case "list": return /* @__PURE__ */ jsx(LabelledField, {
153
+ label,
154
+ yamlKey: spec.key,
155
+ description,
156
+ children: /* @__PURE__ */ jsx(StringListEditor, {
157
+ label,
158
+ values: draft,
159
+ onChange,
160
+ addLabel: t.addRow
161
+ })
162
+ });
163
+ case "bool": return /* @__PURE__ */ jsx(ToggleField, {
164
+ label,
165
+ yamlKey: spec.key,
166
+ description,
167
+ checked: typeof draft === "boolean" ? draft : spec.fallback ?? false,
168
+ onChange: (on) => onChange(on === (spec.fallback ?? false) ? void 0 : on)
169
+ });
170
+ case "flags": return /* @__PURE__ */ jsx(LabelledField, {
171
+ label,
172
+ yamlKey: spec.key,
173
+ description,
174
+ children: /* @__PURE__ */ jsx(KeyValueEditor, {
175
+ rows: draft,
176
+ onChange,
177
+ error: void 0,
178
+ valueSuggestions: references
179
+ })
180
+ });
181
+ case "target": {
182
+ const target = draft;
183
+ return /* @__PURE__ */ jsxs("div", {
184
+ className: "flex flex-col gap-2",
185
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
186
+ label,
187
+ yamlKey: spec.key,
188
+ description
189
+ }), (spec.fields ?? []).map((field) => /* @__PURE__ */ jsx(Input, {
190
+ mono: true,
191
+ ...labelled(field.label, field.key),
192
+ description: field.description,
193
+ value: target[field.key] ?? "",
194
+ onChange: (e) => onChange({
195
+ ...target,
196
+ [field.key]: e.target.value
197
+ })
198
+ }, field.key))]
199
+ });
200
+ }
201
+ case "textarea": return /* @__PURE__ */ jsx(Textarea, {
202
+ mono: true,
203
+ ...labelled(label, spec.key),
204
+ description,
205
+ rows: 4,
206
+ value: String(draft),
207
+ onChange: (e) => onChange(e.target.value)
208
+ });
209
+ default: return /* @__PURE__ */ jsx(SuggestedInput, {
210
+ label,
211
+ yamlKey: spec.key,
212
+ description,
213
+ value: String(draft),
214
+ onChange,
215
+ suggestions: spec.choices ?? references
216
+ });
217
+ }
218
+ }
219
+ /** Where a repository `uses` reads its workflow from, for the repository fields. */
220
+ function repositoryOf(uses, host) {
221
+ if (!uses.startsWith("github/") && !uses.startsWith("gitlab/")) return {
222
+ repo: "",
223
+ branch: ""
224
+ };
225
+ const [path, ref] = splitAtRef(uses.slice(uses.indexOf("/") + 1));
226
+ const server = uses.startsWith("github/") ? "github.com" : host || "gitlab.com";
227
+ return {
228
+ repo: path ? `https://${server}/${path}` : "",
229
+ branch: ref
230
+ };
231
+ }
232
+ /** The `uses` for a repository URL and branch, plus the $host a self-hosted server needs. */
233
+ function usesOfRepository(repo, branch) {
234
+ let url;
235
+ try {
236
+ url = new URL(repo.includes("://") ? repo : `https://${repo}`);
237
+ } catch {
238
+ return null;
239
+ }
240
+ const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
241
+ if (!path) return null;
242
+ const ref = branch.trim() ? `@${branch.trim()}` : "";
243
+ return url.host === "github.com" ? {
244
+ uses: `github/${path}${ref}`,
245
+ host: ""
246
+ } : {
247
+ uses: `gitlab/${path}${ref}`,
248
+ host: url.host === "gitlab.com" ? "" : url.host
249
+ };
250
+ }
251
+ /** The repository fields the workflow import form uses, so branches and files come from the repository's host. */
252
+ function RepositoryForm({ uses, draft, onUses, onDraft }) {
253
+ const { readme: _readme, ...fields } = useWorkflowEditing().WORKFLOW_FORMS.github;
254
+ const host = String(draft.values["text:$host"] ?? "");
255
+ const [initial] = useState(() => ({
256
+ ...repositoryOf(uses, host),
257
+ yaml: String(draft.values["text:$yaml"] ?? "") || "workflow.yaml",
258
+ thumbnail: String(draft.values["text:$thumbnail"] ?? "")
259
+ }));
260
+ return /* @__PURE__ */ jsx(DynamicForm, {
261
+ formJSONs: fields,
262
+ initialValues: initial,
263
+ labelPosition: "top",
264
+ workflowForm: true,
265
+ setValues: (values) => {
266
+ if ([
267
+ "repo",
268
+ "branch",
269
+ "yaml",
270
+ "thumbnail"
271
+ ].every((key) => String(values[key] ?? "") === initial[key])) return;
272
+ const next = usesOfRepository(String(values["repo"] ?? ""), String(values["branch"] ?? ""));
273
+ if (next) onUses(next.uses);
274
+ onDraft({
275
+ ...draft,
276
+ values: {
277
+ ...draft.values,
278
+ "text:$yaml": values["yaml"] === "workflow.yaml" ? "" : String(values["yaml"] ?? ""),
279
+ "text:$thumbnail": String(values["thumbnail"] ?? ""),
280
+ "text:$host": next?.host ?? host
281
+ }
282
+ });
283
+ }
284
+ });
285
+ }
286
+ function usesMode(uses, actions) {
287
+ const kind = usesKind(uses, actions);
288
+ if (kind === "action") return "action";
289
+ if (kind === "github" || kind === "gitlab") return "repository";
290
+ return uses.startsWith("marketplace/") ? "marketplace" : "workflow";
291
+ }
292
+ /** What a step uses: a built-in action, a workflow of yours, one from the marketplace, or one in a repository. */
293
+ function UsesPicker({ uses, onUses, choices, draft, onDraft, error }) {
294
+ const t = useGraphEditorStrings();
295
+ const actions = useWorkflowActions();
296
+ const id = useId();
297
+ const lint = useFieldLint("uses", id, uses, onUses);
298
+ const [mode, setMode] = useState(() => usesMode(uses, actions));
299
+ const options = (prefix) => choices.filter((choice) => choice.startsWith(prefix));
300
+ const select = (values, placeholder, labels) => /* @__PURE__ */ jsx(SuggestionInput, {
301
+ id,
302
+ ariaLabel: t.fields.uses,
303
+ value: uses,
304
+ placeholder,
305
+ suggestions: values.map((value) => ({
306
+ value,
307
+ label: labels?.(value)
308
+ })),
309
+ onChange: onUses
310
+ });
311
+ return /* @__PURE__ */ jsxs("div", {
312
+ className: "flex flex-col gap-2",
313
+ children: [
314
+ /* @__PURE__ */ jsx(FieldLabel, {
315
+ label: t.fields.uses,
316
+ yamlKey: "uses",
317
+ description: t.help.uses
318
+ }),
319
+ /* @__PURE__ */ jsx(ChoiceButtons, {
320
+ options: [
321
+ {
322
+ value: "action",
323
+ label: t.usesAction
324
+ },
325
+ {
326
+ value: "workflow",
327
+ label: t.usesWorkflow
328
+ },
329
+ {
330
+ value: "marketplace",
331
+ label: t.usesMarketplace
332
+ },
333
+ {
334
+ value: "repository",
335
+ label: t.usesRepository
336
+ }
337
+ ],
338
+ value: mode,
339
+ onChange: (next) => {
340
+ setMode(next);
341
+ if (next !== usesMode(uses, actions)) onUses("");
342
+ }
343
+ }),
344
+ mode === "action" ? select(Object.keys(actions), t.chooseAction, (value) => actions[value]?.description) : mode === "workflow" ? select(options("workflow/"), t.chooseWorkflow) : mode === "marketplace" ? select(options("marketplace/"), t.chooseMarketplace) : /* @__PURE__ */ jsx(RepositoryForm, {
345
+ uses,
346
+ draft,
347
+ onUses,
348
+ onDraft
349
+ }),
350
+ /* @__PURE__ */ jsx(FieldError, { message: error ?? lint.message })
351
+ ]
352
+ });
353
+ }
354
+ /** The inputs of the workflow a step uses, fetched the way the YAML editor's completions are. */
355
+ function useSubworkflowInputs(uses, draft) {
356
+ const editing = useWorkflowEditing();
357
+ const resolve = useWorkflowJsonResolver();
358
+ const repos = useRepoSuggestions();
359
+ const [inputs, setInputs] = useState(null);
360
+ const yamlPath = String(draft.values["text:$yaml"] ?? "") || "workflow.yaml";
361
+ const host = String(draft.values["text:$host"] ?? "") || "gitlab.com";
362
+ useEffect(() => {
363
+ let live = true;
364
+ setInputs(null);
365
+ const timer = window.setTimeout(() => {
366
+ loadUsesInputs(editing, uses, {
367
+ yamlPath,
368
+ host
369
+ }, {
370
+ resolve,
371
+ repos
372
+ }).then((loaded) => {
373
+ if (live) setInputs(loaded);
374
+ }).catch(() => {
375
+ if (live) setInputs(null);
376
+ });
377
+ }, 300);
378
+ return () => {
379
+ live = false;
380
+ window.clearTimeout(timer);
381
+ };
382
+ }, [
383
+ editing,
384
+ uses,
385
+ resolve,
386
+ repos,
387
+ yamlPath,
388
+ host
389
+ ]);
390
+ return inputs;
391
+ }
392
+ /** The step's `with`: an action's own inputs, or a subworkflow's by name. */
393
+ function WithEditor({ uses, draft, original, onChange, error, source }) {
394
+ const t = useGraphEditorStrings();
395
+ const inputsEditor = useInputsEditorStrings();
396
+ const actions = useWorkflowActions();
397
+ const typeNames = inputsEditor.types;
398
+ const kind = usesKind(uses, actions);
399
+ const extras = extraKeys(uses, actions, original, draft);
400
+ const references = refSuggestions(source);
401
+ const subworkflow = useSubworkflowInputs(uses, draft);
402
+ const known = subworkflow ? Object.entries(subworkflow).filter(([key]) => key !== "$meta") : [];
403
+ const knownKeys = new Set(known.map(([key]) => key));
404
+ const setRow = (key, value) => {
405
+ const nested = /^[[{]/.test(value.trim()) && parseJson(value).ok;
406
+ const rows = value === "" ? draft.rows.filter((row) => row.key !== key) : draft.rows.some((row) => row.key === key) ? draft.rows.map((row) => row.key === key ? {
407
+ ...row,
408
+ value,
409
+ nested
410
+ } : row) : [...draft.rows, {
411
+ key,
412
+ value,
413
+ nested
414
+ }];
415
+ onChange({
416
+ ...draft,
417
+ rows
418
+ });
419
+ };
420
+ return /* @__PURE__ */ jsx(LintScope, {
421
+ at: "with",
422
+ children: /* @__PURE__ */ jsxs("div", {
423
+ className: "flex flex-col gap-3",
424
+ children: [
425
+ /* @__PURE__ */ jsx(FieldLabel, {
426
+ label: t.fields.with,
427
+ yamlKey: "with",
428
+ description: t.help.with
429
+ }),
430
+ kind === "action" && specsFor(uses, actions).map((spec) => /* @__PURE__ */ jsx(SpecField, {
431
+ spec,
432
+ draft: draft.values[slot(spec)],
433
+ references,
434
+ source,
435
+ onChange: (value) => onChange({
436
+ ...draft,
437
+ values: {
438
+ ...draft.values,
439
+ [slot(spec)]: value
440
+ }
441
+ })
442
+ }, slot(spec))),
443
+ extras.map((key) => /* @__PURE__ */ jsx(ExtraKey, {
444
+ name: key,
445
+ uses,
446
+ draft,
447
+ original,
448
+ onChange
449
+ }, key)),
450
+ kind !== "action" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
451
+ known.map(([key, raw]) => {
452
+ const definition = asRecord(raw);
453
+ const label = String(definition["label"] ?? "");
454
+ const about = String(definition["description"] ?? "");
455
+ const type = String(definition["type"] ?? "");
456
+ return /* @__PURE__ */ jsx(SuggestedInput, {
457
+ label: label || key,
458
+ yamlKey: key,
459
+ description: about || (typeNames[type] ?? type),
460
+ value: draft.rows.find((row) => row.key === key)?.value ?? "",
461
+ suggestions: references,
462
+ onChange: (value) => setRow(key, value)
463
+ }, key);
464
+ }),
465
+ known.length > 0 && /* @__PURE__ */ jsx(FieldLabel, {
466
+ label: t.otherInputs,
467
+ description: t.help.withInputs
468
+ }),
469
+ /* @__PURE__ */ jsx(KeyValueEditor, {
470
+ rows: draft.rows.filter((row) => !knownKeys.has(row.key)),
471
+ onChange: (rows) => onChange({
472
+ ...draft,
473
+ rows: [...draft.rows.filter((row) => knownKeys.has(row.key)), ...rows]
474
+ }),
475
+ error: void 0,
476
+ valueSuggestions: references,
477
+ lint: true
478
+ })
479
+ ] }),
480
+ /* @__PURE__ */ jsx(FieldError, { message: error })
481
+ ]
482
+ })
483
+ });
484
+ }
485
+ /** A key an action doesn't take, with the input it was likely meant as when the linter knows. */
486
+ function ExtraKey({ name, uses, draft, original, onChange }) {
487
+ const t = useGraphEditorStrings();
488
+ const actions = useWorkflowActions();
489
+ const id = useId();
490
+ const fixes = useFieldProblems(name, id).flatMap(({ fix }) => fix?.key && fix.find === name ? [fix] : []);
491
+ const remove = () => onChange({
492
+ ...draft,
493
+ removed: [...draft.removed, name]
494
+ });
495
+ const rename = (fix) => {
496
+ const spec = specsFor(uses, actions).find((spec) => spec.key === fix.replace);
497
+ if (spec) onChange({
498
+ ...draft,
499
+ values: {
500
+ ...draft.values,
501
+ [slot(spec)]: draftFor(spec, asRecord(original)[name])
502
+ },
503
+ removed: [...draft.removed, name]
504
+ });
505
+ };
506
+ return /* @__PURE__ */ jsxs("div", {
507
+ id,
508
+ tabIndex: -1,
509
+ className: "flex flex-col gap-1",
510
+ children: [/* @__PURE__ */ jsxs("div", {
511
+ className: "flex items-center gap-2 text-sm",
512
+ children: [
513
+ /* @__PURE__ */ jsx("span", {
514
+ className: "font-mono",
515
+ children: name
516
+ }),
517
+ /* @__PURE__ */ jsx("span", {
518
+ className: "text-xs text-(--theme-error)",
519
+ children: t.notActionInput
520
+ }),
521
+ /* @__PURE__ */ jsx(IconButton, {
522
+ icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
523
+ label: t.removeRow,
524
+ variant: "ghost",
525
+ size: "sm",
526
+ onClick: remove
527
+ })
528
+ ]
529
+ }), /* @__PURE__ */ jsx(FixButtons, {
530
+ fixes,
531
+ onFix: rename
532
+ })]
533
+ });
534
+ }
535
+ //#endregion
536
+ export { UsesPicker, WithEditor, usesKind, withDraftFrom, withError, withFields, withValue };