@parallelworks/ui 0.34.0 → 0.35.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 (84) hide show
  1. package/README.md +201 -0
  2. package/dist/ai/components/ChatEmptyState.js +71 -86
  3. package/dist/ai/components/ChatInput.d.ts +11 -3
  4. package/dist/ai/components/ChatInput.js +19 -11
  5. package/dist/ai/components/ChatSidebar.js +12 -130
  6. package/dist/ai/components/ChatStage.d.ts +11 -0
  7. package/dist/ai/components/ChatStage.js +26 -0
  8. package/dist/ai/components/ComposerChrome.d.ts +3 -0
  9. package/dist/ai/components/ComposerChrome.js +21 -2
  10. package/dist/ai/components/conversationActions.d.ts +23 -0
  11. package/dist/ai/components/conversationActions.js +108 -0
  12. package/dist/ai/index.d.ts +4 -3
  13. package/dist/ai/index.js +6 -5
  14. package/dist/ai/strings.d.ts +1 -0
  15. package/dist/ai/strings.js +2 -1
  16. package/dist/ai/ui/ConversationSidebar.d.ts +3 -2
  17. package/dist/ai/ui/ConversationSidebar.js +2 -1
  18. package/dist/ai/ui/sidebar.d.ts +6 -0
  19. package/dist/ai/ui/sidebar.js +10 -1
  20. package/dist/components/Dropdown.js +3 -1
  21. package/dist/components/FilterPill.js +1 -0
  22. package/dist/components/Provider.d.ts +14 -0
  23. package/dist/components/Provider.js +14 -3
  24. package/dist/components/SegmentedControl.d.ts +24 -0
  25. package/dist/components/SegmentedControl.js +69 -0
  26. package/dist/editing.d.ts +32 -0
  27. package/dist/editor/FormCodePanel.js +1 -1
  28. package/dist/engine.d.ts +3 -0
  29. package/dist/form/FieldWrapper.js +1 -1
  30. package/dist/form/Fields/CheckboxGroupField.d.ts +3 -3
  31. package/dist/form/Fields/CheckboxGroupField.js +10 -4
  32. package/dist/form/Fields/RadioField.d.ts +5 -3
  33. package/dist/form/Fields/RadioField.js +25 -17
  34. package/dist/form/Fields/fieldOption.d.ts +12 -0
  35. package/dist/form/Fields/fieldOption.js +17 -0
  36. package/dist/form/Form.d.ts +4 -2
  37. package/dist/form/Form.js +170 -68
  38. package/dist/form/FormLayout.d.ts +7 -0
  39. package/dist/form/FormLayout.js +144 -0
  40. package/dist/form/SpecialTypes/FormikCustomDropdown.js +1 -1
  41. package/dist/form/SpecialTypes/FormikCustomInput.js +7 -0
  42. package/dist/form/Wizard/WizardContainer.d.ts +1 -1
  43. package/dist/form/Wizard/WizardContainer.js +244 -49
  44. package/dist/form/Wizard/WizardNavigation.js +4 -4
  45. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  46. package/dist/form/Wizard/WizardStepContent.js +19 -10
  47. package/dist/form/Wizard/WizardStepIndicator.js +35 -7
  48. package/dist/form/Wizard/types.d.ts +19 -6
  49. package/dist/form/Wizard/useWizardState.d.ts +1 -0
  50. package/dist/form/Wizard/useWizardState.js +5 -1
  51. package/dist/form/Wizard/utils.d.ts +7 -0
  52. package/dist/form/Wizard/utils.js +18 -1
  53. package/dist/form/index.d.ts +2 -0
  54. package/dist/form/index.js +4 -2
  55. package/dist/form/layout.d.ts +50 -0
  56. package/dist/form/layout.js +125 -0
  57. package/dist/form/layoutCSS.d.ts +10 -0
  58. package/dist/form/layoutCSS.js +234 -0
  59. package/dist/form/lib.d.ts +2 -0
  60. package/dist/form/lib.js +17 -2
  61. package/dist/form/types/fieldTypes.d.ts +8 -23
  62. package/dist/graph/DependencyGraph.d.ts +20 -1
  63. package/dist/graph/DependencyGraph.js +437 -142
  64. package/dist/graph/MatrixGroup.d.ts +3 -1
  65. package/dist/graph/MatrixGroup.js +31 -28
  66. package/dist/graph/TreeView.js +6 -6
  67. package/dist/graph/gridSpacing.d.ts +7 -0
  68. package/dist/graph/gridSpacing.js +10 -0
  69. package/dist/graph/util.d.ts +2 -0
  70. package/dist/graph/util.js +2 -2
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +2 -1
  73. package/dist/list/ListViewControls.d.ts +15 -0
  74. package/dist/list/ListViewControls.js +9 -3
  75. package/dist/list/index.d.ts +1 -1
  76. package/dist/list/index.js +2 -2
  77. package/dist/styles/base.css +3 -1
  78. package/dist/styles/theme.css +5 -0
  79. package/dist/styles.css +1 -1
  80. package/dist/theme/index.d.ts +2 -0
  81. package/dist/theme/index.js +3 -2
  82. package/package.json +4 -2
  83. package/dist/ai/components/ComposerFrame.d.ts +0 -16
  84. package/dist/ai/components/ComposerFrame.js +0 -33
@@ -2,14 +2,21 @@ import { useRunFile, useSlots, useStrings, useWorkflowEngine } from "../componen
2
2
  import { FaArrowLeft } from "../icons/fa/FaArrowLeft.js";
3
3
  import { FaArrowRight } from "../icons/fa/FaArrowRight.js";
4
4
  import { IoDocumentTextOutline } from "../icons/io5/IoDocumentTextOutline.js";
5
+ import { LuChevronLeft } from "../icons/lu/LuChevronLeft.js";
6
+ import { LuChevronRight } from "../icons/lu/LuChevronRight.js";
7
+ import { LuZoomIn } from "../icons/lu/LuZoomIn.js";
8
+ import { LuZoomOut } from "../icons/lu/LuZoomOut.js";
5
9
  import { TooltipInfo } from "../components/Tooltip.js";
6
10
  import { BareModal, modalPanelClasses } from "../components/BareModal.js";
11
+ import { IconButton } from "../components/IconButton.js";
7
12
  import { Indicator } from "../components/Indicator.js";
8
13
  import Loader from "../components/Loader.js";
9
14
  import { LogViewer } from "../logviewer/LogViewer.js";
10
15
  import { toAbsHumanDuration } from "../duration.js";
16
+ import { initializeValues } from "../form/lib.js";
11
17
  import { AnnotationBanner } from "./AnnotationBanner.js";
12
18
  import { Collapse } from "./Collapse.js";
19
+ import { emptyColumnsStyle, emptyRowsStyle } from "./gridSpacing.js";
13
20
  import { Reveal } from "./Reveal.js";
14
21
  import { jobLabel, toggled } from "./util.js";
15
22
  import { Joblist } from "./JobSummary.js";
@@ -39,6 +46,65 @@ function ViewToggle({ viewMode, setViewMode }) {
39
46
  })]
40
47
  });
41
48
  }
49
+ var VIEW_BAR = "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";
50
+ /** The graph's view buttons, the same wherever a graph is drawn. */
51
+ function GraphViewControls({ history, expand, onReset, onZoom, zoomedAll }) {
52
+ const { dag } = useStrings();
53
+ const chip = "flex h-7 cursor-pointer items-center whitespace-nowrap rounded px-2 text-xs theme-hover";
54
+ return /* @__PURE__ */ jsxs("div", {
55
+ className: VIEW_BAR,
56
+ children: [
57
+ history && /* @__PURE__ */ jsxs(Fragment$1, { children: [
58
+ /* @__PURE__ */ jsx(IconButton, {
59
+ icon: /* @__PURE__ */ jsx(LuChevronLeft, { className: "h-4 w-4" }),
60
+ label: dag.previousGraph,
61
+ size: "sm",
62
+ variant: "ghost",
63
+ disabled: !history.back,
64
+ onClick: history.back
65
+ }),
66
+ /* @__PURE__ */ jsx(IconButton, {
67
+ icon: /* @__PURE__ */ jsx(LuChevronRight, { className: "h-4 w-4" }),
68
+ label: dag.nextGraph,
69
+ size: "sm",
70
+ variant: "ghost",
71
+ disabled: !history.forward,
72
+ onClick: history.forward
73
+ }),
74
+ /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-5 w-px bg-(--theme-border)" })
75
+ ] }),
76
+ expand && /* @__PURE__ */ jsx("button", {
77
+ type: "button",
78
+ className: chip,
79
+ onClick: expand.onClick,
80
+ children: expand.label
81
+ }),
82
+ /* @__PURE__ */ jsx("button", {
83
+ type: "button",
84
+ className: chip,
85
+ onClick: onReset,
86
+ children: dag.resetView
87
+ }),
88
+ /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-5 w-px bg-(--theme-border)" }),
89
+ /* @__PURE__ */ jsx(IconButton, {
90
+ icon: /* @__PURE__ */ jsx(LuZoomOut, { className: "h-4 w-4" }),
91
+ label: dag.zoomOut,
92
+ size: "sm",
93
+ variant: "ghost",
94
+ disabled: zoomedAll === "out",
95
+ onClick: () => onZoom(-1)
96
+ }),
97
+ /* @__PURE__ */ jsx(IconButton, {
98
+ icon: /* @__PURE__ */ jsx(LuZoomIn, { className: "h-4 w-4" }),
99
+ label: dag.zoomIn,
100
+ size: "sm",
101
+ variant: "ghost",
102
+ disabled: zoomedAll === "in",
103
+ onClick: () => onZoom(1)
104
+ })
105
+ ]
106
+ });
107
+ }
42
108
  var addCleanupSteps = ({ jobs }) => {
43
109
  const clonedJobs = structuredClone(jobs);
44
110
  for (const job in jobs) {
@@ -87,8 +153,12 @@ var addCleanupSteps = ({ jobs }) => {
87
153
  return clonedJobs;
88
154
  };
89
155
  var PREVIEW_PADDING = 24;
156
+ function outerNodes(content) {
157
+ const top = content.querySelectorAll(":scope > div > div > div > div[id^=\"node_\"]");
158
+ return top.length > 0 ? top : content.querySelectorAll("[id^=\"node_\"]");
159
+ }
90
160
  function measurePreviewHeight(content, viewRef) {
91
- const nodes = content?.querySelectorAll("[id^=\"node_\"]");
161
+ const nodes = content ? outerNodes(content) : void 0;
92
162
  const transform = content?.parentElement ? getComputedStyle(content.parentElement).transform : "none";
93
163
  if (!content || !nodes?.length || transform === "none") return null;
94
164
  if (!viewRef.current) {
@@ -127,20 +197,62 @@ function parsePrefixToPath(prefix) {
127
197
  }
128
198
  return path;
129
199
  }
200
+ function unitsOnly(columns, together) {
201
+ return columns.map((col) => {
202
+ const boxes = [];
203
+ const rows = [];
204
+ let last = -1;
205
+ col.boxes.forEach((box, i) => {
206
+ const groups = [];
207
+ for (const job of box) {
208
+ const group = groups.find((g) => together(g[0] ?? job, job));
209
+ if (group) group.push(job);
210
+ else groups.push([job]);
211
+ }
212
+ const main = groups.reduce((a, b) => b.length > a.length ? b : a);
213
+ let row = Math.max(col.rows[i] ?? i, last + 1);
214
+ for (const group of [main, ...groups.filter((g) => g !== main)]) {
215
+ boxes.push(group);
216
+ rows.push(row);
217
+ last = row;
218
+ row += 1;
219
+ }
220
+ });
221
+ return {
222
+ ...col,
223
+ boxes,
224
+ rows
225
+ };
226
+ });
227
+ }
228
+ function withLoneMatrixSlots({ layoutPosition, mapLayout }, layout, jobs, matrixGroups) {
229
+ const lone = Object.entries(jobs).flatMap(([name, job]) => {
230
+ const original = job._matrix?.originaljob;
231
+ const slot = original && !matrixGroups[original] ? layoutPosition(layout, original) : void 0;
232
+ return slot && !layoutPosition(layout, name) ? [{
233
+ name,
234
+ slot
235
+ }] : [];
236
+ });
237
+ if (lone.length === 0) return layout;
238
+ const next = mapLayout(layout, (position) => position);
239
+ for (const { name, slot } of lone) next[name] = slot;
240
+ return next;
241
+ }
130
242
  function displayJobsOf(engine, jobs, matrixGroups) {
131
243
  const visibleJobs = structuredClone(jobs);
132
244
  for (const jobName of Object.keys(jobs)) if (jobs[jobName]?.if === false) delete visibleJobs[jobName];
133
245
  engine.collapseMatrixGroups(matrixGroups, visibleJobs);
134
246
  return visibleJobs;
135
247
  }
136
- function computeGraphLayout(engine, jobs, matrixGroups) {
248
+ function computeGraphLayout(engine, jobs, matrixGroups, layout) {
137
249
  const visibleJobs = displayJobsOf(engine, jobs, matrixGroups);
138
250
  const allDeps = {};
139
251
  for (const jobName of Object.keys(visibleJobs)) allDeps[jobName] = engine.jobDependencies(jobName, visibleJobs, allDeps);
140
252
  const filteredDeps = {};
141
253
  for (const jobName of Object.keys(visibleJobs)) {
142
254
  const needs = visibleJobs[jobName]?.needs;
143
- const oldNeeds = Array.isArray(needs) ? needs : [];
255
+ const oldNeeds = Array.isArray(needs) ? [...new Set(needs.map((need) => typeof need === "string" && engine.editing ? engine.editing.needTarget(need) : need))] : [];
144
256
  filteredDeps[jobName] = oldNeeds.filter((dep) => {
145
257
  return !oldNeeds.some((other) => {
146
258
  if (other === dep) return false;
@@ -172,8 +284,21 @@ function computeGraphLayout(engine, jobs, matrixGroups) {
172
284
  }
173
285
  if (toAppend.length > 0) deps.push(toAppend);
174
286
  } while (prevLen !== unfinished.size);
175
- const finalDeps = [];
176
- deps.forEach((col) => {
287
+ const together = (head, jobName) => {
288
+ const depsA = depended_on[head];
289
+ const depsB = depended_on[jobName];
290
+ const depsA2 = filteredDeps[head];
291
+ const depsB2 = filteredDeps[jobName];
292
+ return (!depsA && !depsB || !!depsA && !!depsB && depsA.length === depsB.length) && (!depsA2 && !depsB2 || !!depsA2 && !!depsB2 && depsA2.length === depsB2.length) && (!depsA || depsA.every((dep, i) => dep === depsB?.[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2?.[i]));
293
+ };
294
+ const alone = (job) => !!visibleJobs[job]?._matrixGroup;
295
+ const editing = engine.editing;
296
+ const laidOut = layout && editing?.hasLayout(layout) ? unitsOnly(editing.boxesFromLayout(Object.keys(visibleJobs), filteredDeps, withLoneMatrixSlots(editing, layout, visibleJobs, matrixGroups), {
297
+ together,
298
+ alone
299
+ }), together) : null;
300
+ const finalDeps = laidOut ? laidOut.map((col) => col.boxes) : [];
301
+ if (!laidOut) deps.forEach((col) => {
177
302
  const toAppend = [];
178
303
  while (col.length > 0) {
179
304
  const head = col[0];
@@ -181,18 +306,9 @@ function computeGraphLayout(engine, jobs, matrixGroups) {
181
306
  const grouped = [head];
182
307
  const toRm = [0];
183
308
  col.slice(1).forEach((jobName, j) => {
184
- if (visibleJobs[head]?._matrixGroup || visibleJobs[jobName]?._matrixGroup) return;
185
- const depsA = depended_on[head];
186
- const depsB = depended_on[jobName];
187
- const depsA2 = filteredDeps[head];
188
- const depsB2 = filteredDeps[jobName];
189
- if (!depsA && !depsB || depsA && depsB && depsA.length === depsB.length) {
190
- if (!depsA2 && !depsB2 || depsA2 && depsB2 && depsA2.length === depsB2.length) {
191
- if ((!depsA || depsA.every((dep, i) => dep === depsB?.[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2?.[i]))) {
192
- grouped.push(jobName);
193
- toRm.push(j + 1);
194
- }
195
- }
309
+ if (!alone(head) && !alone(jobName) && together(head, jobName)) {
310
+ grouped.push(jobName);
311
+ toRm.push(j + 1);
196
312
  }
197
313
  });
198
314
  col.splice(0, col.length, ...col.filter((_, i) => !toRm.includes(i)));
@@ -200,7 +316,10 @@ function computeGraphLayout(engine, jobs, matrixGroups) {
200
316
  }
201
317
  finalDeps.push(toAppend);
202
318
  });
203
- if (finalDeps[0]?.length === 0) finalDeps.shift();
319
+ if (finalDeps[0]?.length === 0) {
320
+ finalDeps.shift();
321
+ laidOut?.shift();
322
+ }
204
323
  const ancestorDists = {};
205
324
  const descendantDists = {};
206
325
  const jobNames = Object.keys(visibleJobs);
@@ -233,6 +352,10 @@ function computeGraphLayout(engine, jobs, matrixGroups) {
233
352
  }
234
353
  return {
235
354
  dependencyCols: finalDeps,
355
+ /** Each box's row; above a box, rows the layout leaves empty stay as space. */
356
+ rowSlots: laidOut ? laidOut.map((col) => col.rows) : finalDeps.map((col) => col.map((_, i) => i)),
357
+ /** Each column's number; between two drawn columns, numbers skipped stay as space. */
358
+ colSlots: laidOut ? laidOut.map((col) => col.column) : finalDeps.map((_, i) => i),
236
359
  directDeps: filteredDeps,
237
360
  displayJobs: visibleJobs,
238
361
  ancestorDists,
@@ -279,9 +402,28 @@ function SubworkflowWell({ expanded, onSettled, className, children }) {
279
402
  }
280
403
  var SUBGRAPH_Z_STEP = 100;
281
404
  var CONNECTOR_COLOR = "var(--theme-border)";
282
- var HIGHLIGHT_COLOR = "#3B82F6";
405
+ var HIGHLIGHT_COLOR = "var(--theme-element)";
283
406
  var FIT_ANIMATION_MS = 300;
284
- function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflows, toggleSubworkflow, openInNewGraph, setSublogOpen, invalidateConnectors, requestFit, stepsOpen, toggleSteps, matrixOpen, toggleMatrix, onReady }) {
407
+ function sizesSettled(els) {
408
+ return new Promise((resolve) => {
409
+ let frame = 0;
410
+ const observer = new ResizeObserver(() => {
411
+ cancelAnimationFrame(frame);
412
+ frame = requestAnimationFrame(() => {
413
+ frame = requestAnimationFrame(() => {
414
+ observer.disconnect();
415
+ resolve();
416
+ });
417
+ });
418
+ });
419
+ for (const el of els) observer.observe(el);
420
+ });
421
+ }
422
+ function sizingTransitions(el) {
423
+ return el.getAnimations({ subtree: true }).filter((animation) => animation instanceof CSSTransition && (animation.transitionProperty === "grid-template-rows" || animation.transitionProperty === "width"));
424
+ }
425
+ var ZOOM_STEP = .25;
426
+ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflows, toggleSubworkflow, openInNewGraph, setSublogOpen, invalidateConnectors, requestFit, stepsOpen, toggleSteps, matrixOpen, toggleMatrix, onReady, layout }) {
285
427
  const engine = useWorkflowEngine();
286
428
  const wrapperRef = useRef(null);
287
429
  const zBase = depth * SUBGRAPH_Z_STEP;
@@ -290,10 +432,16 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
290
432
  const maxHoveredDistRef = useRef(0);
291
433
  const retractPathLensRef = useRef(/* @__PURE__ */ new Map());
292
434
  const matrixGroups = useMemo(() => engine.matrixGroups(jobs), [engine, jobs]);
293
- const { dependencyCols, directDeps, displayJobs, ancestorDists, descendantDists } = useMemo(() => computeGraphLayout(engine, jobs, matrixGroups), [
435
+ const placed = useMemo(() => layout ?? (depth > 0 ? positionsOf(jobs) : void 0), [
436
+ layout,
437
+ depth,
438
+ jobs
439
+ ]);
440
+ const { dependencyCols, rowSlots, colSlots, directDeps, displayJobs, ancestorDists, descendantDists } = useMemo(() => computeGraphLayout(engine, jobs, matrixGroups, placed), [
294
441
  engine,
295
442
  jobs,
296
- matrixGroups
443
+ matrixGroups,
444
+ placed
297
445
  ]);
298
446
  const hoveredDistances = useMemo(() => {
299
447
  if (!hoveredJob) return null;
@@ -410,56 +558,66 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
410
558
  });
411
559
  return /* @__PURE__ */ jsx("div", {
412
560
  className: "flex relative",
413
- children: dependencyCols.map((col) => {
414
- return /* @__PURE__ */ jsx("div", { children: col.map((jobNames) => {
415
- const first = jobNames[0];
416
- if (first === void 0) return null;
417
- const matrixGroup = displayJobs[first]?._matrixGroup;
418
- if (matrixGroup) return /* @__PURE__ */ jsx(MatrixGroupNode, {
419
- matrixGroup,
420
- jobNames,
421
- idPrefix: pathPrefix,
422
- isExpanded: matrixOpen.has(pathPrefix + matrixGroup.originaljob),
423
- onToggle: () => toggleMatrix(pathPrefix + matrixGroup.originaljob),
424
- jobs,
425
- activeDists,
426
- onMouseEnter: () => setHoveredJob(first),
427
- onMouseLeave: () => setHoveredJob(null),
428
- hoveredRelated,
429
- animT,
430
- zBase,
431
- handlers,
432
- preview
433
- }, `node_${first}`);
434
- return /* @__PURE__ */ jsxs("div", {
435
- id: `node_${pathPrefix}${first}`,
436
- role: "none",
437
- className: "relative m-24",
438
- style: { zIndex: (activeDists?.has(first) ? 25 : 1) + zBase },
439
- onMouseEnter: () => setHoveredJob(first),
440
- onMouseLeave: () => setHoveredJob(null),
441
- onFocus: () => setHoveredJob(first),
442
- onBlur: () => setHoveredJob(null),
443
- children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("div", {
444
- className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
561
+ children: dependencyCols.map((col, c) => {
562
+ return /* @__PURE__ */ jsx("div", {
563
+ style: emptyColumnsStyle((colSlots[c] ?? c) - (c === 0 ? 0 : (colSlots[c - 1] ?? c - 1) + 1)),
564
+ children: col.map((jobNames, i) => {
565
+ const first = jobNames[0];
566
+ if (first === void 0) return null;
567
+ const matrixGroup = displayJobs[first]?._matrixGroup;
568
+ const rowsAbove = (rowSlots[c]?.[i] ?? i) - (i === 0 ? 0 : (rowSlots[c]?.[i - 1] ?? i - 1) + 1);
569
+ if (matrixGroup) return /* @__PURE__ */ jsx(MatrixGroupNode, {
570
+ matrixGroup,
571
+ jobNames,
572
+ idPrefix: pathPrefix,
573
+ isExpanded: matrixOpen.has(pathPrefix + matrixGroup.originaljob),
574
+ onToggle: () => toggleMatrix(pathPrefix + matrixGroup.originaljob),
575
+ jobs,
576
+ activeDists,
577
+ onMouseEnter: () => setHoveredJob(first),
578
+ onMouseLeave: () => setHoveredJob(null),
579
+ hoveredRelated,
580
+ animT,
581
+ zBase,
582
+ handlers,
583
+ preview,
584
+ rowsAbove
585
+ }, `node_${first}`);
586
+ return /* @__PURE__ */ jsxs("div", {
587
+ id: `node_${pathPrefix}${first}`,
588
+ role: "none",
589
+ className: "relative m-24",
445
590
  style: {
446
- opacity: hoveredRelated && !hoveredRelated.has(first) ? .5 : 1,
447
- transition: `opacity ${animT}s`
591
+ zIndex: (activeDists?.has(first) ? 25 : 1) + zBase,
592
+ ...emptyRowsStyle(rowsAbove)
448
593
  },
449
- children: /* @__PURE__ */ jsx(Joblist, {
450
- jobs: displayJobs,
451
- jobNames,
452
- ...handlers,
453
- growWidth: true,
454
- preview
455
- })
456
- })]
457
- }, `node_${first}`);
458
- }) }, col[0]?.[0]);
594
+ onMouseEnter: () => setHoveredJob(first),
595
+ onMouseLeave: () => setHoveredJob(null),
596
+ onFocus: () => setHoveredJob(first),
597
+ onBlur: () => setHoveredJob(null),
598
+ children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("div", {
599
+ className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
600
+ style: {
601
+ opacity: hoveredRelated && !hoveredRelated.has(first) ? .5 : 1,
602
+ transition: `opacity ${animT}s`
603
+ },
604
+ children: /* @__PURE__ */ jsx(Joblist, {
605
+ jobs: displayJobs,
606
+ jobNames,
607
+ ...handlers,
608
+ growWidth: true,
609
+ preview
610
+ })
611
+ })]
612
+ }, `node_${first}`);
613
+ })
614
+ }, col[0]?.[0]);
459
615
  })
460
616
  });
461
617
  }, [
462
618
  dependencyCols,
619
+ rowSlots,
620
+ colSlots,
463
621
  displayJobs,
464
622
  jobs,
465
623
  stepsOpen,
@@ -492,17 +650,23 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
492
650
  const gid = (name) => document.getElementById(`node_${pathPrefix}${name}`);
493
651
  const ox = (el) => offsetWithin(el, wrapper).x;
494
652
  const nodeConnectorY = (el) => offsetWithin(el, wrapper).y + 37;
653
+ const depsOf = (head) => directDeps[head] ?? [];
495
654
  let colGap = 32;
496
655
  if (dependencyCols.length >= 2) {
497
656
  const col0 = gid(dependencyCols[0]?.[0]?.[0] ?? "");
498
657
  const col1 = gid(dependencyCols[1]?.[0]?.[0] ?? "");
499
658
  if (col0 && col1) colGap = ox(col1) - (ox(col0) + col0.offsetWidth);
500
659
  }
660
+ if (colSlots.some((column, c) => column !== c)) for (let c = 1; c < dependencyCols.length; c++) {
661
+ const left = gid(dependencyCols[c - 1]?.[0]?.[0] ?? "");
662
+ const right = gid(dependencyCols[c]?.[0]?.[0] ?? "");
663
+ if (left && right) colGap = Math.min(colGap, ox(right) - (ox(left) + left.offsetWidth));
664
+ }
501
665
  let minAdy = Number.POSITIVE_INFINITY;
502
666
  for (const col of dependencyCols) for (const jobNames of col) {
503
667
  const first = jobNames[0];
504
668
  if (first === void 0) continue;
505
- for (const dep of directDeps[first] ?? []) {
669
+ for (const dep of depsOf(first)) {
506
670
  const s = gid(dep);
507
671
  const e = gid(first);
508
672
  if (!s || !e) continue;
@@ -512,7 +676,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
512
676
  }
513
677
  const MAX_ARC_R = 16;
514
678
  const arcR = Math.min(minAdy === Number.POSITIVE_INFINITY ? MAX_ARC_R : minAdy / 4, colGap / 2, MAX_ARC_R);
515
- return dependencyCols.map((col) => /* @__PURE__ */ jsx(React.Fragment, { children: col.map(([first]) => first === void 0 ? null : /* @__PURE__ */ jsx(React.Fragment, { children: (directDeps[first] ?? []).map((dep) => {
679
+ return dependencyCols.map((col) => /* @__PURE__ */ jsx(React.Fragment, { children: col.map(([first]) => first === void 0 ? null : /* @__PURE__ */ jsx(React.Fragment, { children: depsOf(first).map((dep) => {
516
680
  const isHighlighted = hoveredRelated?.has(first) && hoveredRelated?.has(dep);
517
681
  const depDist = activeDists?.get(dep) ?? 0;
518
682
  const targetDist = activeDists?.get(first) ?? 0;
@@ -706,7 +870,7 @@ function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expand
706
870
  const seenMatrixGroups = /* @__PURE__ */ new Set();
707
871
  const orderedKeys = Object.keys(jobs).flatMap((jobName) => {
708
872
  const originaljob = jobs[jobName]?._matrix?.originaljob;
709
- if (!originaljob) return [jobName];
873
+ if (!originaljob || !displayJobs[originaljob]?._matrixGroup) return [jobName];
710
874
  if (seenMatrixGroups.has(originaljob)) return [];
711
875
  seenMatrixGroups.add(originaljob);
712
876
  return [originaljob];
@@ -754,7 +918,7 @@ function walkJobPath(root, path) {
754
918
  }
755
919
  return isJobRecord(cursor) ? cursor : {};
756
920
  }
757
- function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode }) {
921
+ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode, layout, yamlJobs, fixedHeight }) {
758
922
  const engine = useWorkflowEngine();
759
923
  const animT = .125;
760
924
  const minScale = .2;
@@ -812,7 +976,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
812
976
  const previewViewRef = useRef(null);
813
977
  useEffect(() => {
814
978
  const container = containerRef.current;
815
- if (!preview || !container) return;
979
+ if (!preview || !container || fixedHeight) return;
816
980
  let raf = 0;
817
981
  let attempts = 0;
818
982
  const measure = () => {
@@ -837,7 +1001,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
837
1001
  observer.disconnect();
838
1002
  cancelAnimationFrame(raf);
839
1003
  };
840
- }, [preview]);
1004
+ }, [preview, fixedHeight]);
841
1005
  const { dag: dagStrings } = useStrings();
842
1006
  const slots = useSlots();
843
1007
  const [executedJobsPath, setExecutedJobsPath] = useState([[]]);
@@ -853,6 +1017,14 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
853
1017
  }, [isRunFinished]);
854
1018
  const currentPath = executedJobsPath[executedJobsPathIdx] ?? [];
855
1019
  const jobs = walkJobPath(rootJobs, currentPath);
1020
+ const root = currentPath.length === 0;
1021
+ const placed = useMemo(() => root && engine.editing?.hasLayout(layout) ? layout : positionsOf(root ? yamlJobs ?? jobs : jobs), [
1022
+ engine,
1023
+ root,
1024
+ layout,
1025
+ yamlJobs,
1026
+ jobs
1027
+ ]);
856
1028
  const resetGraph = useCallback(() => {
857
1029
  setSublogOpen("");
858
1030
  setStepsOpen(/* @__PURE__ */ new Set());
@@ -910,8 +1082,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
910
1082
  const content = graphContentRef.current;
911
1083
  const transform = transformRef.current;
912
1084
  if (!container || !content || !transform) return;
913
- const topNodes = content.querySelectorAll(":scope > div > div > div > div[id^=\"node_\"]");
914
- const nodes = topNodes.length > 0 ? topNodes : content.querySelectorAll("[id^=\"node_\"]");
1085
+ const nodes = outerNodes(content);
915
1086
  if (nodes.length === 0) return;
916
1087
  let minX = Number.POSITIVE_INFINITY;
917
1088
  let minY = Number.POSITIVE_INFINITY;
@@ -947,7 +1118,45 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
947
1118
  invalidateConnectors();
948
1119
  });
949
1120
  }, [invalidateConnectors]);
950
- const animatedFit = useCallback(() => fitGraph(FIT_ANIMATION_MS), [fitGraph]);
1121
+ const fitWhenSettled = useCallback(async () => {
1122
+ const container = containerRef.current;
1123
+ const content = graphContentRef.current;
1124
+ if (!container || !content) return;
1125
+ let running = sizingTransitions(content);
1126
+ while (running.length > 0) {
1127
+ await Promise.allSettled(running.map((transition) => transition.finished));
1128
+ running = sizingTransitions(content);
1129
+ }
1130
+ const panelHeight = preview && !fixedHeight ? measurePreviewHeight(content, previewViewRef) : null;
1131
+ if (panelHeight !== null) flushSync(() => setPreviewHeight(panelHeight));
1132
+ await sizesSettled([
1133
+ container,
1134
+ content,
1135
+ ...outerNodes(content)
1136
+ ]);
1137
+ invalidateConnectors();
1138
+ fitGraph(FIT_ANIMATION_MS);
1139
+ }, [
1140
+ fitGraph,
1141
+ invalidateConnectors,
1142
+ preview,
1143
+ fixedHeight
1144
+ ]);
1145
+ const [zoomedAll, setZoomedAll] = useState("in");
1146
+ const onTransform = useCallback((_, { scale }) => {
1147
+ setZoomedAll(scale >= .599 ? "in" : scale <= .201 ? "out" : null);
1148
+ invalidateConnectors();
1149
+ }, [invalidateConnectors]);
1150
+ const zoomBy = useCallback((step) => {
1151
+ const container = containerRef.current;
1152
+ const transform = transformRef.current;
1153
+ if (!container || !transform) return;
1154
+ const { positionX, positionY, scale } = transform.instance.state;
1155
+ const next = Math.min(maxScale, Math.max(minScale, scale * Math.exp(step)));
1156
+ const x = container.clientWidth / 2;
1157
+ const y = container.clientHeight / 2;
1158
+ transform.setTransform(x - (x - positionX) / scale * next, y - (y - positionY) / scale * next, next, FIT_ANIMATION_MS, "easeOut");
1159
+ }, []);
951
1160
  useEffect(() => {
952
1161
  if (preview) return;
953
1162
  const raf = requestAnimationFrame(() => fitGraph());
@@ -977,20 +1186,26 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
977
1186
  ]);
978
1187
  const hasExpandable = expansionKeys.subworkflowKeys.length > 0 || expansionKeys.matrixKeys.length > 0;
979
1188
  const allExpanded = hasExpandable && expansionKeys.subworkflowKeys.every((k) => expandedSubworkflows.has(k)) && expansionKeys.matrixKeys.every((k) => matrixOpen.has(k));
1189
+ const resetView = () => {
1190
+ if (currentPath.length > 0) {
1191
+ resetGraph();
1192
+ setExecutedJobsPath([[], ...executedJobsPath.slice(executedJobsPathIdx, 10)]);
1193
+ setExecutedJobsPathIdx(0);
1194
+ } else fitGraph(FIT_ANIMATION_MS);
1195
+ };
980
1196
  const toggleExpandAll = () => {
981
- if (allExpanded) {
982
- setExpandedSubworkflows(/* @__PURE__ */ new Set());
983
- setStepsOpen(/* @__PURE__ */ new Set());
984
- setMatrixOpen(/* @__PURE__ */ new Set());
985
- } else {
986
- setExpandedSubworkflows(new Set(expansionKeys.subworkflowKeys));
987
- setStepsOpen(new Set(expansionKeys.jobKeys));
988
- setMatrixOpen(new Set(expansionKeys.matrixKeys));
989
- }
990
- requestAnimationFrame(() => {
991
- invalidateConnectors();
992
- fitGraph();
1197
+ flushSync(() => {
1198
+ if (allExpanded) {
1199
+ setExpandedSubworkflows(/* @__PURE__ */ new Set());
1200
+ setStepsOpen(/* @__PURE__ */ new Set());
1201
+ setMatrixOpen(/* @__PURE__ */ new Set());
1202
+ } else {
1203
+ setExpandedSubworkflows(new Set(expansionKeys.subworkflowKeys));
1204
+ setStepsOpen(new Set(expansionKeys.jobKeys));
1205
+ setMatrixOpen(new Set(expansionKeys.matrixKeys));
1206
+ }
993
1207
  });
1208
+ fitWhenSettled();
994
1209
  };
995
1210
  const nextStep = useCallback((backward) => {
996
1211
  const sublogSplit = sublogOpen.split("/");
@@ -1196,8 +1411,8 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1196
1411
  ref: setContainerElement,
1197
1412
  className: cx("panel w-full overflow-hidden relative", removeBorder ? "border-none" : "border"),
1198
1413
  style: {
1199
- height: containerHeight !== null ? `${containerHeight}px` : previewHeight !== null ? `${Math.min(previewHeight, windowSize.h * .62)}px` : "62vh",
1200
- minHeight: previewHeight !== null ? 0 : 160
1414
+ height: fixedHeight ?? (containerHeight !== null ? `${containerHeight}px` : previewHeight !== null ? `${Math.min(previewHeight, windowSize.h * .62)}px` : "62vh"),
1415
+ minHeight: previewHeight !== null && !fixedHeight ? 0 : 160
1201
1416
  },
1202
1417
  children: /* @__PURE__ */ jsx(TransformWrapper, {
1203
1418
  ref: transformRef,
@@ -1211,56 +1426,30 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1211
1426
  },
1212
1427
  onPanning: invalidateConnectors,
1213
1428
  onPinch: invalidateConnectors,
1214
- onTransform: invalidateConnectors,
1429
+ onTransform,
1215
1430
  onWheel: invalidateConnectors,
1216
1431
  onZoom: invalidateConnectors,
1217
- children: () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
1218
- className: "absolute z-10 top-2 right-2 flex flex-col items-end gap-y-1",
1219
- children: [
1220
- hasExpandable && /* @__PURE__ */ jsx("button", {
1221
- type: "button",
1222
- className: "btn btn-info cursor-pointer h-6",
1223
- onClick: toggleExpandAll,
1224
- children: allExpanded ? dagStrings.collapseAll : dagStrings.expandAll
1225
- }),
1226
- /* @__PURE__ */ jsx("button", {
1227
- type: "button",
1228
- className: "btn btn-info w-21 cursor-pointer h-6",
1229
- "aria-label": "Reset view",
1230
- onClick: () => {
1231
- if (currentPath.length > 0) {
1232
- resetGraph();
1233
- setExecutedJobsPath([[], ...executedJobsPath.slice(executedJobsPathIdx, 10)]);
1234
- setExecutedJobsPathIdx(0);
1235
- } else fitGraph(FIT_ANIMATION_MS);
1236
- },
1237
- children: "Reset View"
1238
- }),
1239
- executedJobsPath.length > 1 && /* @__PURE__ */ jsxs("div", {
1240
- className: "flex",
1241
- children: [/* @__PURE__ */ jsx("button", {
1242
- type: "button",
1243
- onClick: () => {
1244
- if (executedJobsPath.length > executedJobsPathIdx + 1) {
1245
- resetGraph();
1246
- setExecutedJobsPathIdx(executedJobsPathIdx + 1);
1247
- }
1248
- },
1249
- className: "btn btn-info mr-1 cursor-pointer w-10 h-6",
1250
- children: "<"
1251
- }), /* @__PURE__ */ jsx("button", {
1252
- type: "button",
1253
- onClick: () => {
1254
- if (executedJobsPathIdx - 1 >= 0) {
1255
- resetGraph();
1256
- setExecutedJobsPathIdx(executedJobsPathIdx - 1);
1257
- }
1258
- },
1259
- className: "btn btn-info cursor-pointer w-10 h-6",
1260
- children: ">"
1261
- })]
1262
- })
1263
- ]
1432
+ children: () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
1433
+ className: "absolute z-10 top-2 right-2 flex max-w-[calc(100%-1rem)] flex-col items-end gap-y-1",
1434
+ children: /* @__PURE__ */ jsx(GraphViewControls, {
1435
+ history: executedJobsPath.length > 1 ? {
1436
+ back: executedJobsPathIdx + 1 < executedJobsPath.length ? () => {
1437
+ resetGraph();
1438
+ setExecutedJobsPathIdx(executedJobsPathIdx + 1);
1439
+ } : void 0,
1440
+ forward: executedJobsPathIdx > 0 ? () => {
1441
+ resetGraph();
1442
+ setExecutedJobsPathIdx(executedJobsPathIdx - 1);
1443
+ } : void 0
1444
+ } : void 0,
1445
+ expand: hasExpandable ? {
1446
+ label: allExpanded ? dagStrings.collapseAll : dagStrings.expandAll,
1447
+ onClick: toggleExpandAll
1448
+ } : void 0,
1449
+ onReset: resetView,
1450
+ onZoom: (direction) => zoomBy(direction * ZOOM_STEP),
1451
+ zoomedAll
1452
+ })
1264
1453
  }), /* @__PURE__ */ jsx(TransformComponent, { children: /* @__PURE__ */ jsx("div", {
1265
1454
  ref: graphContentRef,
1266
1455
  className: "flex relative mr-[8000px]",
@@ -1276,12 +1465,13 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1276
1465
  openInNewGraph,
1277
1466
  setSublogOpen,
1278
1467
  invalidateConnectors,
1279
- requestFit: animatedFit,
1468
+ requestFit: fitWhenSettled,
1280
1469
  stepsOpen,
1281
1470
  toggleSteps,
1282
1471
  matrixOpen,
1283
1472
  toggleMatrix,
1284
- onReady: fitGraph
1473
+ onReady: fitGraph,
1474
+ layout: placed
1285
1475
  }, `sg-${executedJobsPathIdx}-${pathAppend}`)
1286
1476
  }) })] })
1287
1477
  })
@@ -1334,17 +1524,122 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1334
1524
  ]
1335
1525
  });
1336
1526
  }
1527
+ var isSlot = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
1528
+ /** Each job's place on the graph from its `position`; none when no job has one. */
1529
+ function positionsOf(jobs) {
1530
+ const layout = Object.create(null);
1531
+ let any = false;
1532
+ for (const [name, job] of Object.entries(jobs)) {
1533
+ const { position, _matrix } = isJobRecord(job) ? job : {};
1534
+ const { column, row } = isJobRecord(position) ? position : {};
1535
+ const original = isJobRecord(_matrix) ? _matrix["originaljob"] : void 0;
1536
+ const key = typeof original === "string" && original ? original : name;
1537
+ if (isSlot(column) && isSlot(row) && !(key in layout)) {
1538
+ layout[key] = {
1539
+ column,
1540
+ row
1541
+ };
1542
+ any = true;
1543
+ }
1544
+ }
1545
+ return any ? layout : void 0;
1546
+ }
1547
+ /** Jobs as a run evaluates them before it starts: each `strategy`, and a `needs` expression, from the inputs' defaults. */
1548
+ function withRunExpressions(engine, jobs, workflowInputs) {
1549
+ let values;
1550
+ const evaluate = (obj) => {
1551
+ values ??= defaultInputValues(engine, workflowInputs);
1552
+ return engine.evaluate({
1553
+ inputs: values,
1554
+ obj
1555
+ });
1556
+ };
1557
+ const out = {};
1558
+ for (const [name, job] of Object.entries(jobs)) {
1559
+ const { strategy, needs } = job;
1560
+ const next = { ...job };
1561
+ if (engine.editing && isJobRecord(strategy) && engine.inputDependencies(strategy).hasExpressions) next["strategy"] = evaluate(strategy);
1562
+ if (typeof needs === "string" && engine.inputDependencies(needs).hasExpressions) {
1563
+ const evaluated = evaluate({ needs })["needs"];
1564
+ if (Array.isArray(evaluated) && evaluated.every((need) => typeof need === "string")) next["needs"] = evaluated;
1565
+ }
1566
+ out[name] = next;
1567
+ }
1568
+ return out;
1569
+ }
1570
+ /** The input values a run started without any gets: each one's default. */
1571
+ function defaultInputValues(engine, workflowInputs) {
1572
+ try {
1573
+ const fields = prefilled(engine.convertInputs(workflowInputs ?? {}));
1574
+ const initial = initializeValues(fields) ?? {};
1575
+ return engine.evaluate({
1576
+ inputs: initial,
1577
+ obj: initial
1578
+ });
1579
+ } catch {
1580
+ return {};
1581
+ }
1582
+ }
1583
+ function prefilled(value) {
1584
+ if (Array.isArray(value)) return value.map(prefilled);
1585
+ if (!isJobRecord(value)) return value;
1586
+ const field = Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, prefilled(entry)]));
1587
+ return "type" in field && "default" in field ? {
1588
+ ...field,
1589
+ prefillDefault: true
1590
+ } : field;
1591
+ }
1337
1592
  function DependencyGraphPreview(inputs) {
1593
+ const engine = useWorkflowEngine();
1594
+ const { editing } = engine;
1338
1595
  const ymlJobs = inputs.yml?.["jobs"];
1596
+ const jobs = isJobRecord(ymlJobs) ? ymlJobs : {};
1597
+ const evaluates = useMemo(() => Object.values(jobs).some((job) => {
1598
+ const { strategy, needs } = job;
1599
+ return engine.inputDependencies({
1600
+ strategy: editing ? strategy : void 0,
1601
+ needs
1602
+ }).hasExpressions;
1603
+ }), [
1604
+ engine,
1605
+ editing,
1606
+ jobs
1607
+ ]);
1608
+ const [ready, setReady] = useState(() => engine.isReady());
1609
+ useEffect(() => {
1610
+ if (!evaluates) return;
1611
+ engine.init();
1612
+ const unsubscribe = engine.onReady(setReady);
1613
+ setReady(engine.isReady());
1614
+ return unsubscribe;
1615
+ }, [engine, evaluates]);
1616
+ const on = inputs.yml?.["on"];
1617
+ const execute = isJobRecord(on) ? on["execute"] : void 0;
1618
+ const executeInputs = isJobRecord(execute) ? execute["inputs"] : void 0;
1619
+ const workflowInputs = isJobRecord(executeInputs) ? executeInputs : void 0;
1620
+ const executedJobs = useMemo(() => {
1621
+ const withCleanup = addCleanupSteps({ jobs });
1622
+ const evaluated = ready ? withRunExpressions(engine, withCleanup, workflowInputs) : withCleanup;
1623
+ return editing ? editing.expandMatrixJobs(evaluated) : evaluated;
1624
+ }, [
1625
+ engine,
1626
+ editing,
1627
+ ready,
1628
+ jobs,
1629
+ workflowInputs
1630
+ ]);
1339
1631
  return /* @__PURE__ */ jsx(DependencyGraph, {
1340
1632
  run: {
1341
- executedJobs: addCleanupSteps({ jobs: isJobRecord(ymlJobs) ? ymlJobs : {} }),
1633
+ executedJobs,
1342
1634
  number: 0,
1343
1635
  workflowName: ""
1344
1636
  },
1345
1637
  preview: true,
1346
- removeBorder: inputs.removeBorder
1638
+ removeBorder: inputs.removeBorder,
1639
+ layout: inputs.layout,
1640
+ yamlJobs: jobs,
1641
+ fixedHeight: inputs.height
1347
1642
  });
1348
1643
  }
1349
1644
  //#endregion
1350
- export { DependencyGraphPreview, addCleanupSteps, DependencyGraph as default };
1645
+ export { DependencyGraphPreview, addCleanupSteps, computeGraphLayout, DependencyGraph as default };