@parallelworks/ui 0.37.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/dist/ai/components/ChatInput.js +8 -23
  3. package/dist/ai/components/ChatSidebar.js +7 -4
  4. package/dist/ai/components/chatDraft.d.ts +7 -0
  5. package/dist/ai/components/chatDraft.js +66 -0
  6. package/dist/ai/index.d.ts +1 -1
  7. package/dist/ai/index.js +2 -2
  8. package/dist/ai/ui/sidebar.d.ts +6 -0
  9. package/dist/ai/ui/sidebar.js +15 -1
  10. package/dist/editing.d.ts +2 -0
  11. package/dist/editor/Monaco.js +1 -1
  12. package/dist/editor/index.js +1 -1
  13. package/dist/editor/lintContext.js +15 -3
  14. package/dist/form/FieldWrapper.js +1 -0
  15. package/dist/form/Form.js +64 -23
  16. package/dist/form/Wizard/WizardContainer.js +66 -6
  17. package/dist/form/Wizard/WizardStepContent.js +23 -10
  18. package/dist/form/Wizard/wizardPages.d.ts +12 -0
  19. package/dist/form/Wizard/wizardPages.js +5 -0
  20. package/dist/form/formEditing.d.ts +25 -0
  21. package/dist/form/formEditing.js +20 -0
  22. package/dist/graph/DependencyGraph.d.ts +8 -1
  23. package/dist/graph/DependencyGraph.js +433 -204
  24. package/dist/graph/GraphEditor.d.ts +27 -0
  25. package/dist/graph/GraphEditor.js +1543 -0
  26. package/dist/graph/GraphEditorDialogs.d.ts +87 -0
  27. package/dist/graph/GraphEditorDialogs.js +1808 -0
  28. package/dist/graph/InputDialog.js +1568 -54
  29. package/dist/graph/InputsEditor.d.ts +12 -0
  30. package/dist/graph/InputsEditor.js +1594 -0
  31. package/dist/graph/JobSummary.d.ts +3 -0
  32. package/dist/graph/JobSummary.js +34 -21
  33. package/dist/graph/MatrixGroup.d.ts +2 -1
  34. package/dist/graph/MatrixGroup.js +10 -3
  35. package/dist/graph/ShortcutsButton.js +56 -0
  36. package/dist/graph/SuggestionInput.js +29 -0
  37. package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
  38. package/dist/graph/WorkflowSettingsDialog.js +670 -0
  39. package/dist/graph/editorApi.js +8 -0
  40. package/dist/graph/editorChrome.js +75 -0
  41. package/dist/graph/editorFields.js +535 -0
  42. package/dist/graph/editorPrimitives.js +95 -4
  43. package/dist/graph/editorRows.d.ts +58 -0
  44. package/dist/graph/editorRows.js +460 -0
  45. package/dist/graph/editorStrings.d.ts +1 -0
  46. package/dist/graph/editorStrings.js +10 -1
  47. package/dist/graph/expressionRefs.js +39 -0
  48. package/dist/graph/fieldProblems.js +111 -0
  49. package/dist/graph/graphEditorState.d.ts +191 -0
  50. package/dist/graph/graphEditorState.js +74 -0
  51. package/dist/graph/index.d.ts +4 -0
  52. package/dist/graph/index.js +6 -2
  53. package/dist/graph/inputLayout.js +337 -0
  54. package/dist/graph/inputPages.d.ts +33 -0
  55. package/dist/graph/inputPages.js +186 -0
  56. package/dist/graph/inputRefs.js +388 -0
  57. package/dist/graph/inputRows.d.ts +43 -0
  58. package/dist/graph/inputRows.js +65 -0
  59. package/dist/graph/refusalText.js +13 -0
  60. package/dist/graph/settingsViews.js +62 -0
  61. package/dist/graph/stepWith.d.ts +38 -0
  62. package/dist/graph/stepWith.js +536 -0
  63. package/dist/styles.css +1 -1
  64. package/package.json +1 -1
@@ -1,7 +1,61 @@
1
- import "classnames";
2
- import "react";
3
- import "react/jsx-runtime";
1
+ import cx from "classnames";
2
+ import { useEffect, useRef, useSyncExternalStore } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
4
  //#region src/graph/editorPrimitives.tsx
5
+ /** A small store for an editor's view state, so a change redraws only what reads it. */
6
+ function createStore(initial) {
7
+ let state = initial;
8
+ const listeners = /* @__PURE__ */ new Set();
9
+ return {
10
+ get: () => state,
11
+ set(patch) {
12
+ state = {
13
+ ...state,
14
+ ...patch
15
+ };
16
+ for (const listener of listeners) listener();
17
+ },
18
+ subscribe(listener) {
19
+ listeners.add(listener);
20
+ return () => {
21
+ listeners.delete(listener);
22
+ };
23
+ }
24
+ };
25
+ }
26
+ function useStore(store, select) {
27
+ return useSyncExternalStore(store.subscribe, () => select(store.get()));
28
+ }
29
+ /**
30
+ * Gives `container` focus back once it has fallen to the page, as when the row, menu or dialog
31
+ * holding it goes away, so the editor's shortcuts, Cmd+Z among them, keep reaching it.
32
+ */
33
+ function reclaimFocus(container) {
34
+ window.setTimeout(() => {
35
+ const active = document.activeElement;
36
+ if (container?.isConnected && (!active || active === document.body)) container.focus({ preventScroll: true });
37
+ });
38
+ }
39
+ /** Reclaims focus for the editor's container each time its dialog closes. */
40
+ function useFocusAfterDialog(store, container) {
41
+ const latest = useRef(container);
42
+ latest.current = container;
43
+ useEffect(() => {
44
+ let open = store.get().dialog !== null;
45
+ return store.subscribe(() => {
46
+ const now = store.get().dialog !== null;
47
+ if (open && !now) reclaimFocus(latest.current());
48
+ open = now;
49
+ });
50
+ }, [store]);
51
+ }
52
+ /** `openMenu` whose menus reclaim focus for the editor when they close. */
53
+ function returningFocus(openMenu, container) {
54
+ return (x, y, items, onClose, search) => openMenu(x, y, items, () => {
55
+ onClose?.();
56
+ reclaimFocus(container());
57
+ }, search);
58
+ }
5
59
  var DRAG_THRESHOLD = 4;
6
60
  function suppressNextClick() {
7
61
  const swallow = (e) => {
@@ -55,5 +109,42 @@ function trackDrag(start, { onMove, onEnd, onCancel, escape: cancelOnEscape = fa
55
109
  window.addEventListener("pointercancel", cancel);
56
110
  if (cancelOnEscape) window.addEventListener("keydown", key, true);
57
111
  }
112
+ function place(box) {
113
+ return {
114
+ left: box.left,
115
+ top: box.top,
116
+ width: box.right - box.left,
117
+ height: box.bottom - box.top
118
+ };
119
+ }
120
+ /** The box a shift + drag draws, in its layer's coordinates. */
121
+ function MarqueeBox({ box, className }) {
122
+ return /* @__PURE__ */ jsx("div", {
123
+ className: cx("absolute rounded-md border-2 border-dashed border-(--theme-element) bg-(--theme-element)/10", className),
124
+ style: place(box)
125
+ });
126
+ }
127
+ var OPENED = "[role=\"menu\"], [role=\"dialog\"], [role=\"listbox\"]";
128
+ /** Calls `clear` when a press lands outside the editor and everything it has open. */
129
+ function usePressOutside(container, clear) {
130
+ const latest = useRef({
131
+ container,
132
+ clear
133
+ });
134
+ latest.current = {
135
+ container,
136
+ clear
137
+ };
138
+ useEffect(() => {
139
+ const onDown = (e) => {
140
+ const root = latest.current.container();
141
+ const target = e.target;
142
+ if (!root || !(target instanceof Element) || root.contains(target) || target.closest(OPENED) || target.querySelector(`:scope > :is(${OPENED})`)) return;
143
+ latest.current.clear();
144
+ };
145
+ document.addEventListener("pointerdown", onDown, true);
146
+ return () => document.removeEventListener("pointerdown", onDown, true);
147
+ }, []);
148
+ }
58
149
  //#endregion
59
- export { trackDrag };
150
+ export { MarqueeBox, createStore, place, reclaimFocus, returningFocus, trackDrag, useFocusAfterDialog, usePressOutside, useStore };
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type Point } from './editorPrimitives.js';
3
+ import { type EdgeSelection } from './graphEditorState.js';
4
+ /** Drop indicators, the connection line and the selection, drawn in the graph's own coordinates. */
5
+ export declare function GraphEditorCanvas(): import("react").JSX.Element | null;
6
+ /** A multi-job box's handle for moving all its jobs at once. */
7
+ export declare function BoxGrip({ jobs }: {
8
+ jobs: string[];
9
+ }): import("react").JSX.Element | null;
10
+ /** A job row made draggable, with its menu and matrix badge; shift-click selects it. */
11
+ export declare function EditableJobRow({ job, label, badge, children, }: {
12
+ job: string;
13
+ label: string;
14
+ badge?: boolean;
15
+ children: ReactNode;
16
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
17
+ /**
18
+ * A node's connector circles, where the graph draws its connector dots. Drag from the
19
+ * right one to add jobs that depend on the node, from the left one to add what it needs.
20
+ */
21
+ export declare function NodePorts({ jobs, left, right, drawLeft, drawRight, zIndex, }: {
22
+ jobs: string[];
23
+ left: Point;
24
+ right: Point;
25
+ drawLeft: boolean;
26
+ drawRight: boolean;
27
+ zIndex: number;
28
+ }): import("react").JSX.Element | null;
29
+ /** A step row made draggable within its job, with its menu. */
30
+ export declare function EditableStepRow({ job, index, label, children, }: {
31
+ job: string;
32
+ index: number;
33
+ label: string;
34
+ children: ReactNode;
35
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
36
+ export declare function AddStepButton({ job }: {
37
+ job: string;
38
+ }): import("react").JSX.Element | null;
39
+ /** Connector hit area: a click selects the dependency, shift adds it, a right-click opens its menu. */
40
+ export declare function EdgeHitPath({ d, edge }: {
41
+ d: string;
42
+ edge: EdgeSelection;
43
+ }): import("react").JSX.Element | null;
44
+ /** A selected connector's two ends; dragging one onto another job or node moves that end there. */
45
+ export declare function EdgeEnds({ edge, start, end, zIndex, }: {
46
+ edge: EdgeSelection;
47
+ start: Point;
48
+ end: Point;
49
+ zIndex: number;
50
+ }): import("react").JSX.Element | null;
51
+ /** Shown in place of the graph when the workflow has no jobs yet. */
52
+ /** The canvas of a graph with no jobs, which takes the graph's keys, so undo and paste still work. */
53
+ export declare function EmptyGraphEditor({ containerRef, overlays, height, removeBorder, }: {
54
+ containerRef: (el: HTMLDivElement | null) => void;
55
+ overlays: ReactNode;
56
+ height: string;
57
+ removeBorder?: boolean | undefined;
58
+ }): import("react").JSX.Element;
@@ -0,0 +1,460 @@
1
+ import { BsThreeDots } from "../icons/bs/BsThreeDots.js";
2
+ import { FaPlus } from "../icons/fa/FaPlus.js";
3
+ import { MdDragHandle } from "../icons/md/MdDragHandle.js";
4
+ import { TbAlertTriangle } from "../icons/tb/TbAlertTriangle.js";
5
+ import { TOOLTIP_ID } from "../components/Tooltip.js";
6
+ import { MarqueeBox, place } from "./editorPrimitives.js";
7
+ import { EDITOR_HANDLE_CLASS, NO_SELECTION, labelsOf, problemsIn, sameEdge, sameStep, selectionBounds, useGraphEditor, useSelectedEdges, useUi } from "./graphEditorState.js";
8
+ import cx from "classnames";
9
+ import { useEffect, useRef } from "react";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/graph/editorRows.tsx
12
+ /** Drop indicators, the connection line and the selection, drawn in the graph's own coordinates. */
13
+ function GraphEditorCanvas() {
14
+ const api = useGraphEditor();
15
+ const drag = useUi(api, (state) => state.drag, null);
16
+ const selection = useUi(api, (state) => state.selection, NO_SELECTION);
17
+ const marquee = useUi(api, (state) => state.marquee, null);
18
+ if (!api || !drag && !marquee && selection.length === 0) return null;
19
+ const target = drag?.target ?? null;
20
+ const indicator = target?.kind === "slot" || target?.kind === "steps" && !target.around ? target.indicator : null;
21
+ const outline = target?.kind === "steps" && target.around ? target.indicator : null;
22
+ const refused = target?.kind === "slot" && target.blocked || target?.kind === "steps" && !!target.problem;
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ className: "pointer-events-none absolute inset-0",
25
+ style: { zIndex: 60 },
26
+ children: [
27
+ marquee && /* @__PURE__ */ jsx(MarqueeBox, { box: marquee }),
28
+ !marquee && drag?.payload.kind !== "jobs" && /* @__PURE__ */ jsx(SelectionBox, { api }),
29
+ indicator && /* @__PURE__ */ jsx("div", {
30
+ className: cx("absolute rounded-full", refused ? "bg-(--theme-error)" : "bg-(--theme-element)"),
31
+ style: place(indicator)
32
+ }),
33
+ outline && /* @__PURE__ */ jsx("div", {
34
+ className: cx("absolute rounded-md border-4", refused ? "border-(--theme-error)" : "border-(--theme-element)"),
35
+ style: {
36
+ left: outline.left - 8,
37
+ top: outline.top - 4,
38
+ width: outline.right - outline.left + 16,
39
+ height: outline.bottom - outline.top + 8
40
+ }
41
+ }),
42
+ (target?.kind === "connect" || target?.kind === "merge") && /* @__PURE__ */ jsx("div", {
43
+ className: cx("absolute rounded-md border-4", (target.kind === "connect" ? target.edits : target.valid) ? "border-(--theme-element)" : "border-(--theme-error)"),
44
+ style: {
45
+ left: target.box.left - 8,
46
+ top: target.box.top - 4,
47
+ width: target.box.right - target.box.left + 16,
48
+ height: target.box.bottom - target.box.top + 8
49
+ }
50
+ }),
51
+ drag?.payload.kind === "connect" && drag.origin && drag.content && /* @__PURE__ */ jsx("svg", {
52
+ "aria-hidden": "true",
53
+ overflow: "visible",
54
+ className: "absolute left-0 top-0",
55
+ children: /* @__PURE__ */ jsx("line", {
56
+ x1: drag.origin.x,
57
+ y1: drag.origin.y,
58
+ x2: drag.content.x,
59
+ y2: drag.content.y,
60
+ stroke: target?.kind === "connect" && !target.edits ? "var(--theme-error)" : "var(--theme-element)",
61
+ strokeWidth: 6,
62
+ strokeDasharray: "16 10"
63
+ })
64
+ })
65
+ ]
66
+ });
67
+ }
68
+ /** One outline around the selected jobs, with a grip to move them all and circles to connect them. */
69
+ function SelectionBox({ api }) {
70
+ const t = api.t;
71
+ const grid = api.grid();
72
+ const jobs = api.selected();
73
+ const bounds = grid ? selectionBounds(grid.wrapper, jobs) : null;
74
+ if (!grid || !bounds) return null;
75
+ const anchor = grid.cols.flat(2).find((job) => jobs.includes(job));
76
+ const y = bounds.connectorY;
77
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
78
+ /* @__PURE__ */ jsx("div", {
79
+ className: "absolute rounded-xl border-2 border-dashed border-(--theme-element)",
80
+ style: place(bounds)
81
+ }),
82
+ /* @__PURE__ */ jsx(PortCircle, {
83
+ side: "in",
84
+ at: {
85
+ x: bounds.left,
86
+ y
87
+ },
88
+ draw: true,
89
+ jobs,
90
+ port: "selection",
91
+ hint: t.selectionInHint
92
+ }),
93
+ /* @__PURE__ */ jsx(PortCircle, {
94
+ side: "out",
95
+ at: {
96
+ x: bounds.right,
97
+ y
98
+ },
99
+ draw: true,
100
+ jobs,
101
+ port: "selection",
102
+ hint: t.selectionOutHint
103
+ }),
104
+ /* @__PURE__ */ jsx("button", {
105
+ type: "button",
106
+ "aria-label": t.moveSelectionHint,
107
+ "data-tooltip-id": TOOLTIP_ID,
108
+ "data-tooltip-content": t.moveSelectionHint,
109
+ className: cx(EDITOR_HANDLE_CLASS, "pointer-events-auto absolute flex h-8 w-8 cursor-grab items-center justify-center rounded-full border-4 border-(--theme-element) bg-(--theme-panel-bg)"),
110
+ style: {
111
+ left: bounds.left - 16,
112
+ top: bounds.top - 16
113
+ },
114
+ onPointerDown: (e) => api.startDrag(e, {
115
+ kind: "jobs",
116
+ jobs,
117
+ label: labelsOf(jobs),
118
+ ...anchor ? { anchor } : {}
119
+ }),
120
+ children: /* @__PURE__ */ jsx(MdDragHandle, { className: "h-5 w-5" })
121
+ })
122
+ ] });
123
+ }
124
+ /** A multi-job box's handle for moving all its jobs at once. */
125
+ function BoxGrip({ jobs }) {
126
+ const api = useGraphEditor();
127
+ if (!api) return null;
128
+ const t = api.t;
129
+ return /* @__PURE__ */ jsx("button", {
130
+ type: "button",
131
+ "aria-label": t.moveBoxHint,
132
+ "data-tooltip-id": TOOLTIP_ID,
133
+ "data-tooltip-content": t.moveBoxHint,
134
+ className: cx(EDITOR_HANDLE_CLASS, "absolute -left-[18px] -top-[18px] flex h-8 w-8 cursor-grab items-center justify-center rounded-full border-4 theme-border bg-(--theme-panel-bg)"),
135
+ onPointerDown: (e) => api.startDrag(e, {
136
+ kind: "jobs",
137
+ jobs,
138
+ label: labelsOf(jobs)
139
+ }),
140
+ children: /* @__PURE__ */ jsx(MdDragHandle, { className: "h-5 w-5" })
141
+ });
142
+ }
143
+ var onBadge = (target) => target instanceof Element && !!target.closest("[data-problem-badge]");
144
+ /** A step's problems, or a job's with its steps', listed on hover; a click shows the first one's line. */
145
+ function ProblemBadge({ api, job, step }) {
146
+ const t = api.t;
147
+ const mine = (problems) => step === void 0 ? problems.filter((problem) => problem.job === job) : problemsIn(problems, job, step);
148
+ const messages = useUi(api, (state) => mine(state.problems).map((problem) => problem.message).join("\n"), "");
149
+ const line = useUi(api, (state) => mine(state.problems)[0]?.line ?? 0, 0);
150
+ if (!messages) return null;
151
+ return /* @__PURE__ */ jsx("button", {
152
+ type: "button",
153
+ "data-problem-badge": "",
154
+ "aria-label": t.problemCount(messages.split("\n").length),
155
+ "data-tooltip-id": TOOLTIP_ID,
156
+ "data-tooltip-content": messages,
157
+ className: "cursor-pointer rounded p-0.5 text-(--theme-error) hover:bg-(--theme-muted-panel-bg)",
158
+ onPointerDown: (e) => e.stopPropagation(),
159
+ onClick: (e) => {
160
+ e.stopPropagation();
161
+ api.reveal({ line });
162
+ },
163
+ children: /* @__PURE__ */ jsx(TbAlertTriangle, { className: "h-[0.9em] w-[0.9em]" })
164
+ });
165
+ }
166
+ /** What a job or step row does in the editor: drag, shift-click to select, a menu, its problems. */
167
+ function EditableRow({ api, selected, flagged, data, className, drag, toggle, reveal, openMenu, menuLabel, menuClassName, iconClassName, badges, children }) {
168
+ return /* @__PURE__ */ jsxs("div", {
169
+ role: "none",
170
+ ...data,
171
+ ...selected ? { "data-selected": "" } : {},
172
+ className: cx(EDITOR_HANDLE_CLASS, "-mx-0.5 px-0.5", className, selected && "bg-(--theme-element)/15", flagged ? "ring-2 ring-(--theme-error)" : selected && "ring-2 ring-(--theme-element)"),
173
+ onPointerDown: (e) => {
174
+ if (e.shiftKey) e.preventDefault();
175
+ api.startDrag(e, drag());
176
+ },
177
+ onClickCapture: (e) => {
178
+ if (onBadge(e.target)) return;
179
+ if (e.shiftKey) {
180
+ e.preventDefault();
181
+ e.stopPropagation();
182
+ if (!toggle()) return;
183
+ }
184
+ reveal();
185
+ },
186
+ onContextMenu: (e) => {
187
+ e.preventDefault();
188
+ e.stopPropagation();
189
+ openMenu(e.clientX, e.clientY);
190
+ },
191
+ children: [
192
+ children,
193
+ badges,
194
+ /* @__PURE__ */ jsx("button", {
195
+ type: "button",
196
+ "aria-label": menuLabel,
197
+ className: cx("cursor-pointer rounded theme-muted-text opacity-0 transition-opacity hover:bg-(--theme-muted-panel-bg) focus-visible:opacity-100", menuClassName),
198
+ onPointerDown: (e) => e.stopPropagation(),
199
+ onClick: (e) => {
200
+ e.stopPropagation();
201
+ const r = e.currentTarget.getBoundingClientRect();
202
+ openMenu(r.right, r.bottom);
203
+ },
204
+ children: /* @__PURE__ */ jsx(BsThreeDots, { className: iconClassName })
205
+ })
206
+ ]
207
+ });
208
+ }
209
+ /** A job row made draggable, with its menu and matrix badge; shift-click selects it. */
210
+ function EditableJobRow({ job, label, badge = true, children }) {
211
+ const api = useGraphEditor();
212
+ const selected = useUi(api, (state) => state.selection.includes(job), false);
213
+ const flagged = useUi(api, (state) => problemsIn(state.problems, job).length > 0, false);
214
+ if (!api) return children;
215
+ const t = api.t;
216
+ return /* @__PURE__ */ jsx(EditableRow, {
217
+ api,
218
+ selected,
219
+ flagged,
220
+ data: { "data-dag-job": job },
221
+ className: "group/row relative flex cursor-grab items-center gap-x-2 rounded-md [&>button:first-child]:w-auto [&>button:first-child]:flex-1",
222
+ drag: () => {
223
+ const selection = api.selected();
224
+ return selection.length > 1 && selection.includes(job) ? {
225
+ kind: "jobs",
226
+ jobs: selection,
227
+ anchor: job,
228
+ label: labelsOf(selection)
229
+ } : {
230
+ kind: "jobs",
231
+ jobs: [job],
232
+ label
233
+ };
234
+ },
235
+ toggle: () => {
236
+ api.toggleSelected(job);
237
+ return api.store.get().selection.includes(job);
238
+ },
239
+ reveal: () => api.reveal({ job }),
240
+ openMenu: (x, y) => api.openJobMenu(x, y, job),
241
+ menuLabel: t.jobActions,
242
+ menuClassName: "p-1 group-hover/row:opacity-100",
243
+ iconClassName: "h-[1em] w-[1em]",
244
+ badges: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ProblemBadge, {
245
+ api,
246
+ job
247
+ }), badge && api.isMatrixJob(job) && /* @__PURE__ */ jsx("span", {
248
+ className: "rounded-full border-2 theme-border px-2 text-[1rem] theme-muted-text",
249
+ children: t.matrixBadge
250
+ })] }),
251
+ children
252
+ });
253
+ }
254
+ /** One connector circle: dragging it connects `jobs`, dropping on it connects to them. */
255
+ function PortCircle({ side, at, draw, jobs, port, hint, zIndex }) {
256
+ const api = useGraphEditor();
257
+ const drag = useUi(api, (state) => state.drag, null);
258
+ if (!api) return null;
259
+ const dragging = drag?.payload.kind === "connect" && drag.payload.port === port && drag.payload.side === side;
260
+ return /* @__PURE__ */ jsxs("span", {
261
+ role: "none",
262
+ "data-dag-port": side,
263
+ ...port === "selection" ? { "data-dag-selection": "" } : { "data-dag-node": jobs[0] },
264
+ ...drag ? {} : { "data-tooltip-id": TOOLTIP_ID },
265
+ "data-tooltip-content": hint,
266
+ className: cx(EDITOR_HANDLE_CLASS, "group/port pointer-events-auto absolute flex h-8 w-8 cursor-crosshair items-center justify-center"),
267
+ style: {
268
+ left: at.x - 16,
269
+ top: at.y - 16,
270
+ ...zIndex === void 0 ? {} : { zIndex }
271
+ },
272
+ onPointerDown: (e) => api.startDrag(e, {
273
+ kind: "connect",
274
+ from: jobs,
275
+ side,
276
+ label: labelsOf(jobs),
277
+ port
278
+ }),
279
+ children: [draw && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("span", { className: cx("relative h-4 w-4 rounded-full group-hover/port:bg-(--theme-element)", dragging ? "bg-(--theme-element)" : draw && "bg-(--theme-border)") })]
280
+ });
281
+ }
282
+ /**
283
+ * A node's connector circles, where the graph draws its connector dots. Drag from the
284
+ * right one to add jobs that depend on the node, from the left one to add what it needs.
285
+ */
286
+ function NodePorts({ jobs, left, right, drawLeft, drawRight, zIndex }) {
287
+ const api = useGraphEditor();
288
+ if (!api) return null;
289
+ const t = api.t;
290
+ const port = `node:${jobs[0]}`;
291
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PortCircle, {
292
+ side: "in",
293
+ at: left,
294
+ draw: drawLeft,
295
+ jobs,
296
+ port,
297
+ hint: t.connectInHint,
298
+ zIndex
299
+ }), /* @__PURE__ */ jsx(PortCircle, {
300
+ side: "out",
301
+ at: right,
302
+ draw: drawRight,
303
+ jobs,
304
+ port,
305
+ hint: t.connectOutHint,
306
+ zIndex
307
+ })] });
308
+ }
309
+ /** A step row made draggable within its job, with its menu. */
310
+ function EditableStepRow({ job, index, label, children }) {
311
+ const api = useGraphEditor();
312
+ const selected = useUi(api, (state) => state.steps.some((ref) => ref.job === job && ref.index === index), false);
313
+ const flagged = useUi(api, (state) => problemsIn(state.problems, job, index).length > 0, false);
314
+ if (!api) return children;
315
+ const t = api.t;
316
+ return /* @__PURE__ */ jsx(EditableRow, {
317
+ api,
318
+ selected,
319
+ flagged,
320
+ data: {
321
+ "data-dag-step-job": job,
322
+ "data-dag-step": index
323
+ },
324
+ className: "group/step flex cursor-grab items-center gap-x-1 rounded",
325
+ drag: () => {
326
+ const steps = api.selectedSteps();
327
+ return selected && steps.length > 1 ? {
328
+ kind: "steps",
329
+ steps,
330
+ label: t.stepCount(steps.length)
331
+ } : {
332
+ kind: "steps",
333
+ steps: [{
334
+ job,
335
+ index
336
+ }],
337
+ label
338
+ };
339
+ },
340
+ toggle: () => {
341
+ api.toggleStep({
342
+ job,
343
+ index
344
+ });
345
+ return api.store.get().steps.some((ref) => sameStep(ref, {
346
+ job,
347
+ index
348
+ }));
349
+ },
350
+ reveal: () => api.reveal({
351
+ job,
352
+ step: index
353
+ }),
354
+ openMenu: (x, y) => api.openStepMenu(x, y, job, index),
355
+ menuLabel: t.stepActions,
356
+ menuClassName: "p-0.5 group-hover/step:opacity-100",
357
+ iconClassName: "h-[0.9em] w-[0.9em]",
358
+ badges: /* @__PURE__ */ jsx(ProblemBadge, {
359
+ api,
360
+ job,
361
+ step: index
362
+ }),
363
+ children
364
+ });
365
+ }
366
+ function AddStepButton({ job }) {
367
+ const api = useGraphEditor();
368
+ if (!api) return null;
369
+ const t = api.t;
370
+ return /* @__PURE__ */ jsxs("button", {
371
+ type: "button",
372
+ className: "ml-1.5 flex cursor-pointer items-center gap-x-1.5 text-left theme-muted-text hover:text-(--theme-link)",
373
+ onPointerDown: (e) => e.stopPropagation(),
374
+ onClick: (e) => {
375
+ e.stopPropagation();
376
+ api.addStep(job);
377
+ },
378
+ children: [/* @__PURE__ */ jsx(FaPlus, { className: "h-[0.8em] w-[0.8em]" }), t.addStep]
379
+ });
380
+ }
381
+ /** Connector hit area: a click selects the dependency, shift adds it, a right-click opens its menu. */
382
+ function EdgeHitPath({ d, edge }) {
383
+ const api = useGraphEditor();
384
+ const selected = useSelectedEdges(api).some((other) => sameEdge(other, edge));
385
+ if (!api) return null;
386
+ const t = api.t;
387
+ return /* @__PURE__ */ jsx("path", {
388
+ role: "button",
389
+ tabIndex: 0,
390
+ "aria-label": t.dependencyOf(edge.from, edge.to),
391
+ "aria-pressed": selected,
392
+ "data-dag-edge-from": edge.from,
393
+ "data-dag-edge-to": edge.to,
394
+ d,
395
+ stroke: "transparent",
396
+ strokeWidth: 28,
397
+ fill: "none",
398
+ className: cx(EDITOR_HANDLE_CLASS, "outline-none focus-visible:[stroke:color-mix(in_srgb,var(--theme-element)_35%,transparent)]"),
399
+ style: {
400
+ pointerEvents: "stroke",
401
+ cursor: "pointer"
402
+ },
403
+ onClick: (e) => api.selectEdge(edge, e.shiftKey),
404
+ onKeyDown: (e) => {
405
+ if (e.key === "Enter" || e.key === " ") {
406
+ e.preventDefault();
407
+ api.selectEdge(edge, e.shiftKey);
408
+ }
409
+ },
410
+ onContextMenu: (e) => {
411
+ e.preventDefault();
412
+ api.openEdgeMenu(e.clientX, e.clientY, edge);
413
+ }
414
+ });
415
+ }
416
+ /** A selected connector's two ends; dragging one onto another job or node moves that end there. */
417
+ function EdgeEnds({ edge, start, end, zIndex }) {
418
+ const api = useGraphEditor();
419
+ const drag = useUi(api, (state) => state.drag, null);
420
+ if (!api) return null;
421
+ const t = api.t;
422
+ const inUse = api.edgeInUse(edge);
423
+ const handle = (side, at, fixed) => /* @__PURE__ */ jsx("span", {
424
+ role: "none",
425
+ "data-dag-edge-end": side,
426
+ ...drag ? {} : { "data-tooltip-id": TOOLTIP_ID },
427
+ "data-tooltip-content": inUse ? t.dependencyInUse : t.moveDependencyEnd,
428
+ className: cx(EDITOR_HANDLE_CLASS, "pointer-events-auto absolute flex h-8 w-8 items-center justify-center", inUse ? "cursor-not-allowed" : "cursor-grab"),
429
+ style: {
430
+ left: at.x - 16,
431
+ top: at.y - 16,
432
+ zIndex
433
+ },
434
+ onPointerDown: (e) => {
435
+ if (!inUse) api.startEdgeEnd(e, edge, side, fixed);
436
+ },
437
+ children: /* @__PURE__ */ jsx("span", { className: "h-5 w-5 rounded-full border-4 border-(--theme-element) bg-(--theme-panel-bg)" })
438
+ }, side);
439
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [handle("from", start, end), handle("to", end, start)] });
440
+ }
441
+ /** Shown in place of the graph when the workflow has no jobs yet. */
442
+ /** The canvas of a graph with no jobs, which takes the graph's keys, so undo and paste still work. */
443
+ function EmptyGraphEditor({ containerRef, overlays, height, removeBorder }) {
444
+ const own = useRef(null);
445
+ useEffect(() => {
446
+ if (document.activeElement === document.body) own.current?.focus({ preventScroll: true });
447
+ }, []);
448
+ return /* @__PURE__ */ jsx("div", {
449
+ ref: (el) => {
450
+ own.current = el;
451
+ containerRef(el);
452
+ },
453
+ tabIndex: -1,
454
+ className: cx("panel relative w-full overflow-hidden outline-none", removeBorder ? "border-none" : "border"),
455
+ style: { height },
456
+ children: overlays
457
+ });
458
+ }
459
+ //#endregion
460
+ export { AddStepButton, BoxGrip, EdgeEnds, EdgeHitPath, EditableJobRow, EditableStepRow, EmptyGraphEditor, GraphEditorCanvas, NodePorts };
@@ -241,6 +241,7 @@ export declare const GRAPH_EDITOR_STRINGS: {
241
241
  otherInputs: string;
242
242
  viewForm: string;
243
243
  viewYaml: string;
244
+ fixFieldsFirst: string;
244
245
  yamlSyntax: (message: string) => string;
245
246
  yamlNotMap: string;
246
247
  fields: {
@@ -243,6 +243,7 @@ var GRAPH_EDITOR_STRINGS = {
243
243
  otherInputs: "Other inputs",
244
244
  viewForm: "Form",
245
245
  viewYaml: "YAML",
246
+ fixFieldsFirst: "Fix the fields with errors first, so the YAML shows everything you entered.",
246
247
  yamlSyntax: (message) => `The YAML doesn’t parse: ${message}`,
247
248
  yamlNotMap: "Write the settings as keys and values, such as run: make.",
248
249
  fields: {
@@ -695,5 +696,13 @@ function useGraphEditorStrings() {
695
696
  ...overrides
696
697
  }), [overrides]);
697
698
  }
699
+ /** The input form editor's strings: the host's translations over the English defaults. */
700
+ function useInputsEditorStrings() {
701
+ const overrides = useStrings().inputsEditor;
702
+ return useMemo(() => ({
703
+ ...INPUTS_EDITOR_STRINGS,
704
+ ...overrides
705
+ }), [overrides]);
706
+ }
698
707
  //#endregion
699
- export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, useGraphEditorStrings };
708
+ export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, useGraphEditorStrings, useInputsEditorStrings };