@pipelex/mthds-ui 0.6.3 → 0.6.5

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.
@@ -4,16 +4,19 @@ import {
4
4
  EDGE_TYPE,
5
5
  FOLD_MODE,
6
6
  GRAPH_DIRECTION,
7
+ GRAPH_THEME,
7
8
  MAX_VISIBLE_CONTROLLER_CHILDREN,
8
9
  applyControllers,
9
10
  applyFolds,
10
11
  buildGraph,
11
12
  findCousinControllers,
12
13
  getLayoutedElements,
14
+ getPaletteForTheme,
13
15
  getPipeBlueprint,
14
16
  resolveConceptRef,
15
- stuffDigestFromId
16
- } from "../../chunk-FHRUYFGV.js";
17
+ stuffDigestFromId,
18
+ validateGraphSpec
19
+ } from "../../chunk-ILX53OYM.js";
17
20
  import {
18
21
  __spreadProps,
19
22
  __spreadValues
@@ -186,7 +189,7 @@ function StuffViewer({
186
189
  canEmbedPdf,
187
190
  onOpenExternally
188
191
  }) {
189
- var _a;
192
+ var _a, _b;
190
193
  const [activeTab, setActiveTab] = React.useState("html");
191
194
  const [copied, setCopied] = React.useState(false);
192
195
  const contentRef = React.useRef(null);
@@ -220,12 +223,12 @@ function StuffViewer({
220
223
  const isPdf = effectiveMime === "application/pdf";
221
224
  const isImage = (_a = effectiveMime == null ? void 0 : effectiveMime.startsWith("image/")) != null ? _a : false;
222
225
  const htmlTabLabel = getHtmlTabLabel(effectiveMime != null ? effectiveMime : stuff.contentType);
223
- const jsonString = React.useMemo(() => {
224
- if (stuff.data == null) return null;
226
+ const { jsonString, jsonError } = React.useMemo(() => {
227
+ if (stuff.data == null) return { jsonString: null, jsonError: null };
225
228
  try {
226
- return JSON.stringify(stuff.data, null, 2);
227
- } catch (e) {
228
- return "[Unable to serialize data]";
229
+ return { jsonString: JSON.stringify(stuff.data, null, 2), jsonError: null };
230
+ } catch (err) {
231
+ return { jsonString: null, jsonError: err instanceof Error ? err.message : String(err) };
229
232
  }
230
233
  }, [stuff.data]);
231
234
  React.useEffect(() => {
@@ -248,7 +251,14 @@ function StuffViewer({
248
251
  ] })
249
252
  ] });
250
253
  }
254
+ function renderJsonError(detail) {
255
+ return /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-error", role: "alert", children: [
256
+ /* @__PURE__ */ jsx("div", { className: "stuff-viewer-error-title", children: "Unable to serialize data" }),
257
+ /* @__PURE__ */ jsx("div", { className: "stuff-viewer-error-detail", children: detail })
258
+ ] });
259
+ }
251
260
  function renderContent() {
261
+ var _a2;
252
262
  if (activeTab === "html") {
253
263
  if (isPdf) {
254
264
  const canEmbed = canEmbedPdf !== false;
@@ -278,7 +288,7 @@ function StuffViewer({
278
288
  }
279
289
  if (isImage) {
280
290
  if (inlineUrl) {
281
- return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-image", children: /* @__PURE__ */ jsx("img", { src: inlineUrl, alt: stuff.name || "Image content" }) });
291
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-image", children: /* @__PURE__ */ jsx("img", { src: inlineUrl, alt: (_a2 = stuff.name) != null ? _a2 : "(unnamed stuff)" }) });
282
292
  }
283
293
  return renderMediaFallback("Image");
284
294
  }
@@ -300,6 +310,7 @@ function StuffViewer({
300
310
  }
301
311
  );
302
312
  }
313
+ if (jsonError) return renderJsonError(jsonError);
303
314
  return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No content available" });
304
315
  }
305
316
  if (activeTab === "json") {
@@ -312,6 +323,7 @@ function StuffViewer({
312
323
  }
313
324
  );
314
325
  }
326
+ if (jsonError) return renderJsonError(jsonError);
315
327
  return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No JSON data available" });
316
328
  }
317
329
  if (stuff.dataText) {
@@ -332,6 +344,7 @@ function StuffViewer({
332
344
  }
333
345
  );
334
346
  }
347
+ if (jsonError) return renderJsonError(jsonError);
335
348
  return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No text data available" });
336
349
  }
337
350
  function handleCopy() {
@@ -389,7 +402,7 @@ function StuffViewer({
389
402
  const rootClass = ["stuff-viewer", className].filter(Boolean).join(" ");
390
403
  return /* @__PURE__ */ jsxs("div", { className: rootClass, children: [
391
404
  /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-header", children: [
392
- /* @__PURE__ */ jsx("h3", { className: "stuff-viewer-title", children: stuff.name || "Data" }),
405
+ /* @__PURE__ */ jsx("h3", { className: "stuff-viewer-title", children: (_b = stuff.name) != null ? _b : "(unnamed stuff)" }),
393
406
  stuff.concept && /* @__PURE__ */ jsx("p", { className: "stuff-viewer-subtitle", children: stuff.concept })
394
407
  ] }),
395
408
  /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-toolbar", children: [
@@ -1134,15 +1147,16 @@ var STATUS_COLORS = {
1134
1147
  failed: "#FF5555",
1135
1148
  running: "#8BE9FD",
1136
1149
  scheduled: "#6272a4",
1137
- skipped: "#6272a4"
1150
+ skipped: "#6272a4",
1151
+ canceled: "#6272a4"
1138
1152
  };
1139
1153
  function PipeDetailPanel({ node, spec, onConceptClick }) {
1140
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1141
- const pipeType = (_a = node.pipe_type) != null ? _a : "PipeFunc";
1154
+ var _a, _b, _c, _d, _e, _f, _g;
1155
+ const pipeType = node.pipe_type;
1142
1156
  const isController = CONTROLLER_TYPES.has(pipeType);
1143
- const badge = (_b = PIPE_TYPE_BADGES[pipeType]) != null ? _b : pipeType;
1144
- const status = (_c = node.status) != null ? _c : "scheduled";
1145
- const statusColor = (_d = STATUS_COLORS[status]) != null ? _d : "#6272a4";
1157
+ const badge = PIPE_TYPE_BADGES[pipeType];
1158
+ const status = node.status;
1159
+ const statusColor = (_a = STATUS_COLORS[status]) != null ? _a : "#6272a4";
1146
1160
  const blueprint = React5.useMemo(() => {
1147
1161
  var _a2, _b2;
1148
1162
  if (!node.pipe_code || !spec.pipe_registry) return void 0;
@@ -1154,9 +1168,9 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1154
1168
  }
1155
1169
  return void 0;
1156
1170
  }, [node.pipe_code, spec]);
1157
- const inputs = (_f = (_e = node.io) == null ? void 0 : _e.inputs) != null ? _f : [];
1158
- const outputs = (_h = (_g = node.io) == null ? void 0 : _g.outputs) != null ? _h : [];
1159
- const description = (_i = blueprint == null ? void 0 : blueprint.description) != null ? _i : node.description;
1171
+ const inputs = (_c = (_b = node.io) == null ? void 0 : _b.inputs) != null ? _c : [];
1172
+ const outputs = (_e = (_d = node.io) == null ? void 0 : _d.outputs) != null ? _e : [];
1173
+ const description = (_f = blueprint == null ? void 0 : blueprint.description) != null ? _f : node.description;
1160
1174
  return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1161
1175
  /* @__PURE__ */ jsxs13("div", { className: "detail-sticky-header", children: [
1162
1176
  /* @__PURE__ */ jsxs13("div", { className: "detail-header", children: [
@@ -1171,53 +1185,47 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1171
1185
  "span",
1172
1186
  {
1173
1187
  className: `detail-pipe-code ${isController ? "detail-pipe-code--controller" : ""}`,
1174
- children: (_j = node.pipe_code) != null ? _j : "unknown"
1188
+ children: node.pipe_code
1175
1189
  }
1176
1190
  )
1177
1191
  ] }),
1178
1192
  /* @__PURE__ */ jsxs13("div", { className: "detail-status", children: [
1179
1193
  /* @__PURE__ */ jsx13("span", { className: "detail-status-dot", style: { background: statusColor } }),
1180
1194
  /* @__PURE__ */ jsx13("span", { className: "detail-status-label", style: { color: statusColor }, children: status }),
1181
- ((_k = node.timing) == null ? void 0 : _k.duration) != null && /* @__PURE__ */ jsx13("span", { className: "detail-duration", children: formatDuration(node.timing.duration) })
1195
+ ((_g = node.timing) == null ? void 0 : _g.duration) != null && /* @__PURE__ */ jsx13("span", { className: "detail-duration", children: formatDuration(node.timing.duration) })
1182
1196
  ] }),
1183
1197
  description && /* @__PURE__ */ jsx13("div", { className: "detail-description", children: description }),
1184
1198
  inputs.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1185
1199
  /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Inputs" }),
1186
- /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: inputs.map((input, idx) => {
1187
- var _a2;
1188
- return /* @__PURE__ */ jsxs13(
1189
- "div",
1190
- {
1191
- className: "detail-io-pill",
1192
- style: { cursor: input.concept && onConceptClick ? "pointer" : void 0 },
1193
- onClick: () => input.concept && (onConceptClick == null ? void 0 : onConceptClick(input.concept)),
1194
- children: [
1195
- /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: (_a2 = input.name) != null ? _a2 : "unnamed" }),
1196
- input.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: input.concept })
1197
- ]
1198
- },
1199
- idx
1200
- );
1201
- }) })
1200
+ /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: inputs.map((input, idx) => /* @__PURE__ */ jsxs13(
1201
+ "div",
1202
+ {
1203
+ className: "detail-io-pill",
1204
+ style: { cursor: input.concept && onConceptClick ? "pointer" : void 0 },
1205
+ onClick: () => input.concept && (onConceptClick == null ? void 0 : onConceptClick(input.concept)),
1206
+ children: [
1207
+ /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: input.name }),
1208
+ input.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: input.concept })
1209
+ ]
1210
+ },
1211
+ idx
1212
+ )) })
1202
1213
  ] }),
1203
1214
  outputs.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1204
1215
  /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Output" }),
1205
- /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: outputs.map((output, idx) => {
1206
- var _a2;
1207
- return /* @__PURE__ */ jsxs13(
1208
- "div",
1209
- {
1210
- className: "detail-io-pill",
1211
- style: { cursor: output.concept && onConceptClick ? "pointer" : void 0 },
1212
- onClick: () => output.concept && (onConceptClick == null ? void 0 : onConceptClick(output.concept)),
1213
- children: [
1214
- /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: (_a2 = output.name) != null ? _a2 : "unnamed" }),
1215
- output.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: output.concept })
1216
- ]
1217
- },
1218
- idx
1219
- );
1220
- }) })
1216
+ /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: outputs.map((output, idx) => /* @__PURE__ */ jsxs13(
1217
+ "div",
1218
+ {
1219
+ className: "detail-io-pill",
1220
+ style: { cursor: output.concept && onConceptClick ? "pointer" : void 0 },
1221
+ onClick: () => output.concept && (onConceptClick == null ? void 0 : onConceptClick(output.concept)),
1222
+ children: [
1223
+ /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: output.name }),
1224
+ output.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: output.concept })
1225
+ ]
1226
+ },
1227
+ idx
1228
+ )) })
1221
1229
  ] }),
1222
1230
  (inputs.length > 0 || outputs.length > 0) && /* @__PURE__ */ jsx13("div", { style: { borderTop: "1px solid rgba(255,255,255,0.06)", margin: "4px 0" } })
1223
1231
  ] }),
@@ -1390,9 +1398,9 @@ function extractType(schema) {
1390
1398
  if (schema.allOf) return "all";
1391
1399
  if (schema.$ref) {
1392
1400
  const ref = String(schema.$ref);
1393
- return (_a = ref.split("/").pop()) != null ? _a : "ref";
1401
+ return (_a = ref.split("/").pop()) != null ? _a : "(unresolved type)";
1394
1402
  }
1395
- return "unknown";
1403
+ return "(unresolved type)";
1396
1404
  }
1397
1405
  function toStuffViewerData(ioData) {
1398
1406
  var _a;
@@ -1539,7 +1547,7 @@ function hydrateLabels(nodes) {
1539
1547
  }
1540
1548
 
1541
1549
  // src/graph/react/viewer/GraphToolbar.tsx
1542
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1550
+ import { Fragment as Fragment11, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1543
1551
  var ARROW_RIGHT_ICON = /* @__PURE__ */ jsxs16(
1544
1552
  "svg",
1545
1553
  {
@@ -1691,6 +1699,44 @@ var EXPAND_ALL_ICON = /* @__PURE__ */ jsxs16(
1691
1699
  ]
1692
1700
  }
1693
1701
  );
1702
+ var SUN_ICON = /* @__PURE__ */ jsxs16(
1703
+ "svg",
1704
+ {
1705
+ viewBox: "0 0 24 24",
1706
+ width: "14",
1707
+ height: "14",
1708
+ fill: "none",
1709
+ stroke: "currentColor",
1710
+ strokeWidth: "2",
1711
+ strokeLinecap: "round",
1712
+ strokeLinejoin: "round",
1713
+ children: [
1714
+ /* @__PURE__ */ jsx16("circle", { cx: "12", cy: "12", r: "4" }),
1715
+ /* @__PURE__ */ jsx16("line", { x1: "12", y1: "2", x2: "12", y2: "4" }),
1716
+ /* @__PURE__ */ jsx16("line", { x1: "12", y1: "20", x2: "12", y2: "22" }),
1717
+ /* @__PURE__ */ jsx16("line", { x1: "4.93", y1: "4.93", x2: "6.34", y2: "6.34" }),
1718
+ /* @__PURE__ */ jsx16("line", { x1: "17.66", y1: "17.66", x2: "19.07", y2: "19.07" }),
1719
+ /* @__PURE__ */ jsx16("line", { x1: "2", y1: "12", x2: "4", y2: "12" }),
1720
+ /* @__PURE__ */ jsx16("line", { x1: "20", y1: "12", x2: "22", y2: "12" }),
1721
+ /* @__PURE__ */ jsx16("line", { x1: "4.93", y1: "19.07", x2: "6.34", y2: "17.66" }),
1722
+ /* @__PURE__ */ jsx16("line", { x1: "17.66", y1: "6.34", x2: "19.07", y2: "4.93" })
1723
+ ]
1724
+ }
1725
+ );
1726
+ var MOON_ICON = /* @__PURE__ */ jsx16(
1727
+ "svg",
1728
+ {
1729
+ viewBox: "0 0 24 24",
1730
+ width: "14",
1731
+ height: "14",
1732
+ fill: "none",
1733
+ stroke: "currentColor",
1734
+ strokeWidth: "2",
1735
+ strokeLinecap: "round",
1736
+ strokeLinejoin: "round",
1737
+ children: /* @__PURE__ */ jsx16("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
1738
+ }
1739
+ );
1694
1740
  function GraphToolbar({
1695
1741
  direction,
1696
1742
  onDirectionChange,
@@ -1703,8 +1749,12 @@ function GraphToolbar({
1703
1749
  onExpandAll,
1704
1750
  foldAllDisabled = false,
1705
1751
  expandAllDisabled = false,
1752
+ theme,
1753
+ onThemeChange,
1706
1754
  rightOffset = 0
1707
1755
  }) {
1756
+ const themeToggleEnabled = theme !== void 0 && onThemeChange !== void 0;
1757
+ const themeLabel = theme === GRAPH_THEME.LIGHT ? "Switch to dark theme" : "Switch to light theme";
1708
1758
  const isVertical = direction === GRAPH_DIRECTION.TB || direction === GRAPH_DIRECTION.BT;
1709
1759
  const directionLabel = isVertical ? "Switch to horizontal layout" : "Switch to vertical layout";
1710
1760
  const controllersLabel = showControllers ? "Hide pipe controllers" : "Show pipe controllers \u2014 groups pipes by their controlling pipe";
@@ -1792,7 +1842,21 @@ function GraphToolbar({
1792
1842
  "aria-label": "Fit view",
1793
1843
  children: FIT_VIEW_ICON
1794
1844
  }
1795
- )
1845
+ ),
1846
+ themeToggleEnabled && /* @__PURE__ */ jsxs16(Fragment11, { children: [
1847
+ /* @__PURE__ */ jsx16("div", { className: "graph-toolbar-separator" }),
1848
+ /* @__PURE__ */ jsx16(
1849
+ "button",
1850
+ {
1851
+ type: "button",
1852
+ className: "graph-toolbar-btn",
1853
+ onClick: () => onThemeChange(theme === GRAPH_THEME.LIGHT ? GRAPH_THEME.DARK : GRAPH_THEME.LIGHT),
1854
+ title: themeLabel,
1855
+ "aria-label": themeLabel,
1856
+ children: theme === GRAPH_THEME.LIGHT ? MOON_ICON : SUN_ICON
1857
+ }
1858
+ )
1859
+ ] })
1796
1860
  ] });
1797
1861
  }
1798
1862
 
@@ -1888,16 +1952,16 @@ var STATUS_CONFIG = {
1888
1952
  failed: { color: "#FF5555", label: "Failed" },
1889
1953
  running: { color: "#8BE9FD", label: "Running" },
1890
1954
  scheduled: { color: "#6272a4", label: "Scheduled" },
1891
- skipped: { color: "#6272a4", label: "Skipped" }
1955
+ skipped: { color: "#6272a4", label: "Skipped" },
1956
+ canceled: { color: "#6272a4", label: "Canceled" }
1892
1957
  };
1893
1958
  var MAX_VISIBLE_INPUTS = 4;
1894
1959
  function getBadge(pipeType) {
1895
1960
  return PIPE_TYPE_BADGES2[pipeType];
1896
1961
  }
1897
1962
  function PipeCardBase({ data, children }) {
1898
- var _a;
1899
1963
  const badge = getBadge(data.pipeType);
1900
- const statusConfig = (_a = STATUS_CONFIG[data.status]) != null ? _a : STATUS_CONFIG.scheduled;
1964
+ const statusConfig = STATUS_CONFIG[data.status];
1901
1965
  const isRunning = data.status === "running";
1902
1966
  const isController = isControllerType(data.pipeType);
1903
1967
  const [inputsExpanded, setInputsExpanded] = useState2(false);
@@ -1934,9 +1998,9 @@ function PipeCardBase({ data, children }) {
1934
1998
  title: "Expand controller (alt/option: only this one)",
1935
1999
  "aria-label": "Expand controller",
1936
2000
  onClick: (e) => {
1937
- var _a2;
2001
+ var _a;
1938
2002
  e.stopPropagation();
1939
- (_a2 = data.onExpand) == null ? void 0 : _a2.call(data, { soloMode: e.altKey });
2003
+ (_a = data.onExpand) == null ? void 0 : _a.call(data, { soloMode: e.altKey });
1940
2004
  },
1941
2005
  children: "\u2922"
1942
2006
  }
@@ -2008,7 +2072,7 @@ function getPipeCardComponent(pipeType) {
2008
2072
  }
2009
2073
 
2010
2074
  // src/graph/react/nodes/pipe/PipeCardNode.tsx
2011
- import { Fragment as Fragment11, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2075
+ import { Fragment as Fragment12, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2012
2076
  function PipeCardNode({ data }) {
2013
2077
  var _a;
2014
2078
  const Card = (_a = getPipeCardComponent(data.pipeType)) != null ? _a : PipeCardBase;
@@ -2021,7 +2085,7 @@ function PipeCardRFNode({
2021
2085
  }) {
2022
2086
  const payload = data.pipeCardData;
2023
2087
  if (!payload) return null;
2024
- return /* @__PURE__ */ jsxs19(Fragment11, { children: [
2088
+ return /* @__PURE__ */ jsxs19(Fragment12, { children: [
2025
2089
  /* @__PURE__ */ jsx19(Handle, { type: "target", position: targetPosition }),
2026
2090
  /* @__PURE__ */ jsx19(PipeCardNode, { data: payload }),
2027
2091
  /* @__PURE__ */ jsx19(Handle, { type: "source", position: sourcePosition })
@@ -2029,7 +2093,7 @@ function PipeCardRFNode({
2029
2093
  }
2030
2094
 
2031
2095
  // src/graph/react/viewer/GraphViewer.tsx
2032
- import { Fragment as Fragment12, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2096
+ import { Fragment as Fragment13, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2033
2097
  var nodeTypes = __spreadProps(__spreadValues({}, controllerNodeTypes), {
2034
2098
  pipeCard: PipeCardRFNode
2035
2099
  });
@@ -2041,7 +2105,7 @@ function StuffNodeDetail({
2041
2105
  onOpenExternally
2042
2106
  }) {
2043
2107
  const conceptInfo = stuffData.concept && graphspec ? resolveConceptRef(graphspec, stuffData.concept) : void 0;
2044
- return /* @__PURE__ */ jsx20(Fragment12, { children: conceptInfo ? /* @__PURE__ */ jsx20(
2108
+ return /* @__PURE__ */ jsx20(Fragment13, { children: conceptInfo ? /* @__PURE__ */ jsx20(
2045
2109
  ConceptDetailPanel,
2046
2110
  {
2047
2111
  concept: conceptInfo,
@@ -2067,6 +2131,10 @@ function seedFoldedControllers(mode, controllerIds) {
2067
2131
  if (mode === FOLD_MODE.FOLDED) return new Set(controllerIds);
2068
2132
  return /* @__PURE__ */ new Set();
2069
2133
  }
2134
+ function resolveExternalTheme(themeProp, configTheme) {
2135
+ var _a, _b;
2136
+ return (_b = (_a = themeProp != null ? themeProp : configTheme) != null ? _a : DEFAULT_GRAPH_CONFIG.theme) != null ? _b : GRAPH_THEME.DARK;
2137
+ }
2070
2138
  function cloneCachedNodes(nodes) {
2071
2139
  return nodes.map((n) => __spreadProps(__spreadValues({}, n), {
2072
2140
  position: __spreadValues({}, n.position),
@@ -2093,12 +2161,15 @@ function applyStatusOverrides(nodes, statusMap) {
2093
2161
  function GraphViewer(props) {
2094
2162
  var _a, _b, _c;
2095
2163
  const {
2096
- graphspec,
2164
+ graphspec: graphspecProp,
2097
2165
  config = DEFAULT_GRAPH_CONFIG,
2098
2166
  initialDirection,
2099
2167
  initialShowControllers,
2100
2168
  initialFoldMode,
2101
2169
  hideToolbar = false,
2170
+ theme: themeProp,
2171
+ showThemeToggle = true,
2172
+ onThemeChange,
2102
2173
  onNavigateToPipe,
2103
2174
  onStuffNodeClick,
2104
2175
  onReactFlowInit,
@@ -2110,19 +2181,41 @@ function GraphViewer(props) {
2110
2181
  canEmbedPdf,
2111
2182
  onOpenExternally
2112
2183
  } = props;
2184
+ const graphspec = React7.useMemo(
2185
+ () => graphspecProp === null ? null : validateGraphSpec(graphspecProp),
2186
+ [graphspecProp]
2187
+ );
2113
2188
  const [direction, setDirection] = React7.useState(
2114
2189
  () => {
2115
2190
  var _a2, _b2;
2116
2191
  return (_b2 = (_a2 = initialDirection != null ? initialDirection : config.direction) != null ? _a2 : DEFAULT_GRAPH_CONFIG.direction) != null ? _b2 : GRAPH_DIRECTION.TB;
2117
2192
  }
2118
2193
  );
2119
- const [showControllers, setShowControllers] = React7.useState(
2120
- () => {
2121
- var _a2, _b2;
2122
- return (_b2 = (_a2 = initialShowControllers != null ? initialShowControllers : config.showControllers) != null ? _a2 : DEFAULT_GRAPH_CONFIG.showControllers) != null ? _b2 : false;
2194
+ const externalTheme = resolveExternalTheme(themeProp, config.theme);
2195
+ const [theme, setTheme] = React7.useState(externalTheme);
2196
+ const prevExternalThemeRef = React7.useRef(externalTheme);
2197
+ React7.useEffect(() => {
2198
+ if (externalTheme !== prevExternalThemeRef.current) {
2199
+ prevExternalThemeRef.current = externalTheme;
2200
+ setTheme(externalTheme);
2123
2201
  }
2124
- );
2202
+ }, [externalTheme]);
2203
+ const onThemeChangeRef = React7.useRef(onThemeChange);
2204
+ onThemeChangeRef.current = onThemeChange;
2205
+ const prevReportedThemeRef = React7.useRef(theme);
2206
+ React7.useEffect(() => {
2207
+ var _a2;
2208
+ if (theme !== prevReportedThemeRef.current) {
2209
+ prevReportedThemeRef.current = theme;
2210
+ (_a2 = onThemeChangeRef.current) == null ? void 0 : _a2.call(onThemeChangeRef, theme);
2211
+ }
2212
+ }, [theme]);
2125
2213
  const effectiveFoldMode = (_b = (_a = initialFoldMode != null ? initialFoldMode : config.foldMode) != null ? _a : DEFAULT_GRAPH_CONFIG.foldMode) != null ? _b : FOLD_MODE.EXPANDED;
2214
+ const [showControllers, setShowControllers] = React7.useState(() => {
2215
+ var _a2, _b2;
2216
+ if (effectiveFoldMode === FOLD_MODE.FOLDED) return true;
2217
+ return (_b2 = (_a2 = initialShowControllers != null ? initialShowControllers : config.showControllers) != null ? _a2 : DEFAULT_GRAPH_CONFIG.showControllers) != null ? _b2 : false;
2218
+ });
2126
2219
  const foldModeRef = React7.useRef(effectiveFoldMode);
2127
2220
  foldModeRef.current = effectiveFoldMode;
2128
2221
  const containerRef = React7.useRef(null);
@@ -2138,11 +2231,11 @@ function GraphViewer(props) {
2138
2231
  setConceptOverride(null);
2139
2232
  }, [graphspec]);
2140
2233
  React7.useEffect(() => {
2141
- var _a2;
2142
2234
  const el = containerRef.current;
2143
2235
  if (!el) return;
2144
- const palette = (_a2 = config.paletteColors) != null ? _a2 : DEFAULT_GRAPH_CONFIG.paletteColors;
2145
- if (!palette) return;
2236
+ const themePalette = getPaletteForTheme(theme);
2237
+ const overrides = config.paletteColors;
2238
+ const palette = overrides ? __spreadValues(__spreadValues({}, themePalette), overrides) : themePalette;
2146
2239
  for (const [cssVar, value] of Object.entries(palette)) {
2147
2240
  el.style.setProperty(cssVar, value);
2148
2241
  }
@@ -2151,7 +2244,7 @@ function GraphViewer(props) {
2151
2244
  el.style.removeProperty(cssVar);
2152
2245
  }
2153
2246
  };
2154
- }, [config.paletteColors]);
2247
+ }, [config.paletteColors, theme]);
2155
2248
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
2156
2249
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
2157
2250
  const reactFlowRef = React7.useRef(null);
@@ -2305,6 +2398,10 @@ function GraphViewer(props) {
2305
2398
  foldedRef.current = seedSet;
2306
2399
  skipNextFoldEffectRef.current = seedSet.size > 0;
2307
2400
  prevFoldSizeRef.current = seedSet.size;
2401
+ if (seedSet.size > 0 && !showControllersRef.current) {
2402
+ setShowControllers(true);
2403
+ showControllersRef.current = true;
2404
+ }
2308
2405
  const folded = seedSet.size > 0 && analysis ? applyFolds(
2309
2406
  { nodes: graphData.nodes, edges: graphData.edges },
2310
2407
  analysis,
@@ -2563,93 +2660,102 @@ function GraphViewer(props) {
2563
2660
  expandAllDisabled: foldedControllers.size === 0
2564
2661
  };
2565
2662
  }, [showControllers, allControllerIds, foldedControllers]);
2566
- return /* @__PURE__ */ jsxs20("div", { ref: containerRef, className: "react-flow-container", children: [
2567
- /* @__PURE__ */ jsx20(
2568
- ReactFlow,
2569
- {
2570
- nodes,
2571
- edges,
2572
- nodeTypes,
2573
- onNodesChange,
2574
- onEdgesChange,
2575
- onNodeClick,
2576
- onPaneClick: handlePaneClick,
2577
- onInit,
2578
- fitView: true,
2579
- fitViewOptions: { padding: 0.1 },
2580
- defaultEdgeOptions: { type: edgeType },
2581
- panOnScroll: true,
2582
- minZoom: 0.1,
2583
- proOptions: { hideAttribution: true },
2584
- panActivationKeyCode: null,
2585
- children: /* @__PURE__ */ jsx20(
2586
- Background,
2663
+ return /* @__PURE__ */ jsxs20(
2664
+ "div",
2665
+ {
2666
+ ref: containerRef,
2667
+ className: `react-flow-container react-flow-container--theme-${theme}`,
2668
+ children: [
2669
+ /* @__PURE__ */ jsx20(
2670
+ ReactFlow,
2671
+ {
2672
+ nodes,
2673
+ edges,
2674
+ nodeTypes,
2675
+ onNodesChange,
2676
+ onEdgesChange,
2677
+ onNodeClick,
2678
+ onPaneClick: handlePaneClick,
2679
+ onInit,
2680
+ fitView: true,
2681
+ fitViewOptions: { padding: 0.1 },
2682
+ defaultEdgeOptions: { type: edgeType },
2683
+ panOnScroll: true,
2684
+ minZoom: 0.1,
2685
+ proOptions: { hideAttribution: true },
2686
+ panActivationKeyCode: null,
2687
+ children: /* @__PURE__ */ jsx20(
2688
+ Background,
2689
+ {
2690
+ variant: BackgroundVariant.Dots,
2691
+ gap: 20,
2692
+ size: 1,
2693
+ color: "var(--color-bg-dots)"
2694
+ }
2695
+ )
2696
+ }
2697
+ ),
2698
+ /* @__PURE__ */ jsxs20(
2699
+ DetailPanel,
2700
+ {
2701
+ isOpen: detailOpen,
2702
+ onClose: handlePaneClick,
2703
+ width: panelWidth,
2704
+ isDragging: isPanelDragging,
2705
+ onResizeHandleMouseDown: onResizeMouseDown,
2706
+ children: [
2707
+ conceptOverride ? /* @__PURE__ */ jsx20(ConceptDetailPanel, { concept: conceptOverride }) : selectedSpecNode && graphspec ? /* @__PURE__ */ jsx20(
2708
+ PipeDetailPanel,
2709
+ {
2710
+ node: selectedSpecNode,
2711
+ spec: graphspec,
2712
+ onConceptClick: handleConceptClick
2713
+ }
2714
+ ) : (detailSelection == null ? void 0 : detailSelection.stuffData) ? /* @__PURE__ */ jsx20(
2715
+ StuffNodeDetail,
2716
+ {
2717
+ stuffData: detailSelection.stuffData,
2718
+ graphspec,
2719
+ resolveStorageUrl,
2720
+ canEmbedPdf,
2721
+ onOpenExternally
2722
+ }
2723
+ ) : null,
2724
+ renderDetailExtra && detailSelection && !conceptOverride && renderDetailExtra(detailSelection.nodeId, detailSelection.nodeData)
2725
+ ]
2726
+ }
2727
+ ),
2728
+ !hideToolbar && /* @__PURE__ */ jsx20(
2729
+ GraphToolbar,
2587
2730
  {
2588
- variant: BackgroundVariant.Dots,
2589
- gap: 20,
2590
- size: 1,
2591
- color: "var(--color-bg-dots)"
2731
+ direction,
2732
+ onDirectionChange: setDirection,
2733
+ showControllers,
2734
+ onShowControllersChange: setShowControllers,
2735
+ onZoomIn: () => {
2736
+ var _a2;
2737
+ return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.zoomIn();
2738
+ },
2739
+ onZoomOut: () => {
2740
+ var _a2;
2741
+ return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.zoomOut();
2742
+ },
2743
+ onFitView: () => {
2744
+ var _a2;
2745
+ return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.fitView({ padding: 0.1 });
2746
+ },
2747
+ onFoldAll: foldAllProps.onFoldAll,
2748
+ onExpandAll: foldAllProps.onExpandAll,
2749
+ foldAllDisabled: foldAllProps.foldAllDisabled,
2750
+ expandAllDisabled: foldAllProps.expandAllDisabled,
2751
+ theme: showThemeToggle ? theme : void 0,
2752
+ onThemeChange: showThemeToggle ? setTheme : void 0,
2753
+ rightOffset: detailOpen ? panelWidth : 0
2592
2754
  }
2593
2755
  )
2594
- }
2595
- ),
2596
- /* @__PURE__ */ jsxs20(
2597
- DetailPanel,
2598
- {
2599
- isOpen: detailOpen,
2600
- onClose: handlePaneClick,
2601
- width: panelWidth,
2602
- isDragging: isPanelDragging,
2603
- onResizeHandleMouseDown: onResizeMouseDown,
2604
- children: [
2605
- conceptOverride ? /* @__PURE__ */ jsx20(ConceptDetailPanel, { concept: conceptOverride }) : selectedSpecNode && graphspec ? /* @__PURE__ */ jsx20(
2606
- PipeDetailPanel,
2607
- {
2608
- node: selectedSpecNode,
2609
- spec: graphspec,
2610
- onConceptClick: handleConceptClick
2611
- }
2612
- ) : (detailSelection == null ? void 0 : detailSelection.stuffData) ? /* @__PURE__ */ jsx20(
2613
- StuffNodeDetail,
2614
- {
2615
- stuffData: detailSelection.stuffData,
2616
- graphspec,
2617
- resolveStorageUrl,
2618
- canEmbedPdf,
2619
- onOpenExternally
2620
- }
2621
- ) : null,
2622
- renderDetailExtra && detailSelection && !conceptOverride && renderDetailExtra(detailSelection.nodeId, detailSelection.nodeData)
2623
- ]
2624
- }
2625
- ),
2626
- !hideToolbar && /* @__PURE__ */ jsx20(
2627
- GraphToolbar,
2628
- {
2629
- direction,
2630
- onDirectionChange: setDirection,
2631
- showControllers,
2632
- onShowControllersChange: setShowControllers,
2633
- onZoomIn: () => {
2634
- var _a2;
2635
- return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.zoomIn();
2636
- },
2637
- onZoomOut: () => {
2638
- var _a2;
2639
- return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.zoomOut();
2640
- },
2641
- onFitView: () => {
2642
- var _a2;
2643
- return (_a2 = reactFlowRef.current) == null ? void 0 : _a2.fitView({ padding: 0.1 });
2644
- },
2645
- onFoldAll: foldAllProps.onFoldAll,
2646
- onExpandAll: foldAllProps.onExpandAll,
2647
- foldAllDisabled: foldAllProps.foldAllDisabled,
2648
- expandAllDisabled: foldAllProps.expandAllDisabled,
2649
- rightOffset: detailOpen ? panelWidth : 0
2650
- }
2651
- )
2652
- ] });
2756
+ ]
2757
+ }
2758
+ );
2653
2759
  }
2654
2760
  export {
2655
2761
  ConceptDetailPanel,