@flanksource/clicky-ui 0.3.52 → 0.3.54

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 (75) hide show
  1. package/dist/components/clipboard.cjs +76 -11
  2. package/dist/components/clipboard.cjs.map +1 -1
  3. package/dist/components/clipboard.d.ts +37 -6
  4. package/dist/components/clipboard.d.ts.map +1 -1
  5. package/dist/components/clipboard.js +75 -12
  6. package/dist/components/clipboard.js.map +1 -1
  7. package/dist/components.cjs +2 -0
  8. package/dist/components.d.ts +1 -1
  9. package/dist/components.d.ts.map +1 -1
  10. package/dist/components.js +2 -2
  11. package/dist/data/MatrixTreeTable.cjs +94 -0
  12. package/dist/data/MatrixTreeTable.cjs.map +1 -0
  13. package/dist/data/MatrixTreeTable.d.ts +40 -0
  14. package/dist/data/MatrixTreeTable.d.ts.map +1 -0
  15. package/dist/data/MatrixTreeTable.js +92 -0
  16. package/dist/data/MatrixTreeTable.js.map +1 -0
  17. package/dist/data/TaskManager.cjs +39 -32
  18. package/dist/data/TaskManager.cjs.map +1 -1
  19. package/dist/data/TaskManager.d.ts +6 -1
  20. package/dist/data/TaskManager.d.ts.map +1 -1
  21. package/dist/data/TaskManager.js +39 -32
  22. package/dist/data/TaskManager.js.map +1 -1
  23. package/dist/data/TreeDataTable.cjs +52 -0
  24. package/dist/data/TreeDataTable.cjs.map +1 -0
  25. package/dist/data/TreeDataTable.d.ts +17 -0
  26. package/dist/data/TreeDataTable.d.ts.map +1 -0
  27. package/dist/data/TreeDataTable.js +52 -0
  28. package/dist/data/TreeDataTable.js.map +1 -0
  29. package/dist/data/clicky-export/ClickyExportDialog.cjs +4 -3
  30. package/dist/data/clicky-export/ClickyExportDialog.cjs.map +1 -1
  31. package/dist/data/clicky-export/ClickyExportDialog.d.ts +4 -1
  32. package/dist/data/clicky-export/ClickyExportDialog.d.ts.map +1 -1
  33. package/dist/data/clicky-export/ClickyExportDialog.js +4 -3
  34. package/dist/data/clicky-export/ClickyExportDialog.js.map +1 -1
  35. package/dist/data/test-runner/MatrixMarks.cjs +212 -0
  36. package/dist/data/test-runner/MatrixMarks.cjs.map +1 -0
  37. package/dist/data/test-runner/MatrixMarks.d.ts +76 -0
  38. package/dist/data/test-runner/MatrixMarks.d.ts.map +1 -0
  39. package/dist/data/test-runner/MatrixMarks.js +205 -0
  40. package/dist/data/test-runner/MatrixMarks.js.map +1 -0
  41. package/dist/data/test-runner/TestDetailPanel.cjs +3 -0
  42. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  43. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  44. package/dist/data/test-runner/TestDetailPanel.js +3 -0
  45. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  46. package/dist/data/test-runner/index.d.ts +2 -0
  47. package/dist/data/test-runner/index.d.ts.map +1 -1
  48. package/dist/data/test-runner/matrix-marks.cjs +119 -0
  49. package/dist/data/test-runner/matrix-marks.cjs.map +1 -0
  50. package/dist/data/test-runner/matrix-marks.d.ts +154 -0
  51. package/dist/data/test-runner/matrix-marks.d.ts.map +1 -0
  52. package/dist/data/test-runner/matrix-marks.js +113 -0
  53. package/dist/data/test-runner/matrix-marks.js.map +1 -0
  54. package/dist/data/version-info.cjs +3 -3
  55. package/dist/data/version-info.js +3 -3
  56. package/dist/data.cjs +27 -0
  57. package/dist/data.d.ts +3 -0
  58. package/dist/data.d.ts.map +1 -1
  59. package/dist/data.js +6 -1
  60. package/dist/hooks/use-task-run.cjs +22 -9
  61. package/dist/hooks/use-task-run.cjs.map +1 -1
  62. package/dist/hooks/use-task-run.d.ts +2 -0
  63. package/dist/hooks/use-task-run.d.ts.map +1 -1
  64. package/dist/hooks/use-task-run.js +22 -9
  65. package/dist/hooks/use-task-run.js.map +1 -1
  66. package/dist/index.cjs +29 -0
  67. package/dist/index.js +7 -2
  68. package/dist/lib/tree-rows.cjs +63 -0
  69. package/dist/lib/tree-rows.cjs.map +1 -0
  70. package/dist/lib/tree-rows.d.ts +36 -0
  71. package/dist/lib/tree-rows.d.ts.map +1 -0
  72. package/dist/lib/tree-rows.js +60 -0
  73. package/dist/lib/tree-rows.js.map +1 -0
  74. package/dist/styles.css +1 -1
  75. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ const require_DataTable = require("./DataTable.cjs");
2
+ const require_tree_rows = require("../lib/tree-rows.cjs");
3
+ const require_MatrixTreeTable = require("./MatrixTreeTable.cjs");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ // src/data/TreeDataTable.tsx
6
+ /**
7
+ * A DataTable whose rows form a tree. Parent and leaf nodes share the columns;
8
+ * the tree column indents each row by depth and gives parents a chevron. Tree
9
+ * order is the row order, so sorting and grouping are not offered.
10
+ */
11
+ function TreeDataTable({ roots, getChildren, getKey, getAriaLabel, treeColumn, columns, expanded, onExpandedChange, defaultExpanded, ...table }) {
12
+ if (!columns.some((column) => column.key === treeColumn)) throw new Error(`TreeDataTable treeColumn "${treeColumn}" is not one of its columns`);
13
+ const { rows, toggle } = require_tree_rows.useTreeRows(roots, {
14
+ getChildren,
15
+ getKey
16
+ }, {
17
+ ...expanded !== void 0 ? { expanded } : {},
18
+ ...onExpandedChange ? { onExpandedChange } : {},
19
+ ...defaultExpanded !== void 0 ? { defaultExpanded } : {}
20
+ });
21
+ const byNode = new Map(rows.map((row) => [row.node, row]));
22
+ const treeColumns = columns.map((column) => {
23
+ const { sortable: _sortable, ...rest } = column;
24
+ if (column.key !== treeColumn) return rest;
25
+ return {
26
+ ...rest,
27
+ render: (value, node) => {
28
+ const row = byNode.get(node);
29
+ const content = column.render ? column.render(value, node) : String(value ?? "");
30
+ return row.hasChildren ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_MatrixTreeTable.TreeRowLead, {
31
+ depth: row.depth,
32
+ open: row.open,
33
+ label: getAriaLabel(node),
34
+ onToggle: () => toggle(row),
35
+ children: content
36
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_MatrixTreeTable.TreeRowLead, {
37
+ depth: row.depth,
38
+ children: content
39
+ });
40
+ }
41
+ };
42
+ });
43
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_DataTable.DataTable, {
44
+ ...table,
45
+ columns: treeColumns,
46
+ data: rows.map((row) => row.node),
47
+ getRowId: (node) => byNode.get(node).key
48
+ });
49
+ }
50
+ exports.TreeDataTable = TreeDataTable;
51
+
52
+ //# sourceMappingURL=TreeDataTable.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeDataTable.cjs","names":["useTreeRows","TreeRowLead","DataTable"],"sources":["../../src/data/TreeDataTable.tsx"],"sourcesContent":["import { useTreeRows, type TreeExpansionProps, type TreeRow, type TreeRowsAccessors } from \"../lib/tree-rows\";\nimport { DataTable, type DataTableColumn, type DataTableProps } from \"./DataTable\";\nimport { TreeRowLead } from \"./MatrixTreeTable\";\n\nexport type TreeDataTableProps<T extends Record<string, unknown>> = Omit<\n DataTableProps<T>,\n \"data\" | \"columns\" | \"getRowId\" | \"grouping\" | \"groupingModes\" | \"groupingMode\" | \"defaultGroupingMode\" | \"onGroupingModeChange\" | \"defaultSort\" | \"sort\" | \"onSortChange\"\n> &\n TreeRowsAccessors<T> &\n TreeExpansionProps<T> & {\n roots: T[];\n columns: DataTableColumn<T>[];\n /** Key of the column that carries the indent and the expand/collapse chevron. */\n treeColumn: string;\n /** Names a parent node in its expand/collapse button. */\n getAriaLabel: (node: T) => string;\n };\n\n/**\n * A DataTable whose rows form a tree. Parent and leaf nodes share the columns;\n * the tree column indents each row by depth and gives parents a chevron. Tree\n * order is the row order, so sorting and grouping are not offered.\n */\nexport function TreeDataTable<T extends Record<string, unknown>>({\n roots,\n getChildren,\n getKey,\n getAriaLabel,\n treeColumn,\n columns,\n expanded,\n onExpandedChange,\n defaultExpanded,\n ...table\n}: TreeDataTableProps<T>) {\n if (!columns.some((column) => column.key === treeColumn)) {\n throw new Error(`TreeDataTable treeColumn \"${treeColumn}\" is not one of its columns`);\n }\n const { rows, toggle } = useTreeRows(roots, { getChildren, getKey }, {\n ...(expanded !== undefined ? { expanded } : {}),\n ...(onExpandedChange ? { onExpandedChange } : {}),\n ...(defaultExpanded !== undefined ? { defaultExpanded } : {}),\n });\n const byNode = new Map<T, TreeRow<T>>(rows.map((row) => [row.node, row]));\n const treeColumns = columns.map((column): DataTableColumn<T> => {\n const { sortable: _sortable, ...rest } = column;\n if (column.key !== treeColumn) return rest;\n return {\n ...rest,\n render: (value, node) => {\n const row = byNode.get(node)!;\n const content = column.render ? column.render(value, node) : String(value ?? \"\");\n return row.hasChildren\n ? <TreeRowLead depth={row.depth} open={row.open} label={getAriaLabel(node)} onToggle={() => toggle(row)}>{content}</TreeRowLead>\n : <TreeRowLead depth={row.depth}>{content}</TreeRowLead>;\n },\n };\n });\n return <DataTable<T> {...table} columns={treeColumns} data={rows.map((row) => row.node)} getRowId={(node) => byNode.get(node)!.key} />;\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,cAAiD,EAC/D,OACA,aACA,QACA,cACA,YACA,SACA,UACA,kBACA,iBACA,GAAG,SACqB;CACxB,IAAI,CAAC,QAAQ,MAAM,WAAW,OAAO,QAAQ,UAAU,GACrD,MAAM,IAAI,MAAM,6BAA6B,WAAW,4BAA4B;CAEtF,MAAM,EAAE,MAAM,WAAWA,kBAAAA,YAAY,OAAO;EAAE;EAAa;CAAO,GAAG;EACnE,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;EAC/C,GAAI,oBAAoB,KAAA,IAAY,EAAE,gBAAgB,IAAI,CAAC;CAC7D,CAAC;CACD,MAAM,SAAS,IAAI,IAAmB,KAAK,KAAK,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;CACxE,MAAM,cAAc,QAAQ,KAAK,WAA+B;EAC9D,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;EACzC,IAAI,OAAO,QAAQ,YAAY,OAAO;EACtC,OAAO;GACL,GAAG;GACH,SAAS,OAAO,SAAS;IACvB,MAAM,MAAM,OAAO,IAAI,IAAI;IAC3B,MAAM,UAAU,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,IAAI,OAAO,SAAS,EAAE;IAC/E,OAAO,IAAI,cACP,iBAAA,GAAA,kBAAA,IAAA,CAACC,wBAAAA,aAAD;KAAa,OAAO,IAAI;KAAO,MAAM,IAAI;KAAM,OAAO,aAAa,IAAI;KAAG,gBAAgB,OAAO,GAAG;KAAI,UAAA;IAAqB,CAAA,IAC7H,iBAAA,GAAA,kBAAA,IAAA,CAACA,wBAAAA,aAAD;KAAa,OAAO,IAAI;KAAQ,UAAA;IAAqB,CAAA;GAC3D;EACF;CACF,CAAC;CACD,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAACC,kBAAAA,WAAD;EAAc,GAAI;EAAO,SAAS;EAAa,MAAM,KAAK,KAAK,QAAQ,IAAI,IAAI;EAAG,WAAW,SAAS,OAAO,IAAI,IAAI,CAAC,CAAE;CAAM,CAAA;AACvI"}
@@ -0,0 +1,17 @@
1
+ import { TreeExpansionProps, TreeRowsAccessors } from '../lib/tree-rows.js';
2
+ import { DataTableColumn, DataTableProps } from './DataTable.js';
3
+ export type TreeDataTableProps<T extends Record<string, unknown>> = Omit<DataTableProps<T>, "data" | "columns" | "getRowId" | "grouping" | "groupingModes" | "groupingMode" | "defaultGroupingMode" | "onGroupingModeChange" | "defaultSort" | "sort" | "onSortChange"> & TreeRowsAccessors<T> & TreeExpansionProps<T> & {
4
+ roots: T[];
5
+ columns: DataTableColumn<T>[];
6
+ /** Key of the column that carries the indent and the expand/collapse chevron. */
7
+ treeColumn: string;
8
+ /** Names a parent node in its expand/collapse button. */
9
+ getAriaLabel: (node: T) => string;
10
+ };
11
+ /**
12
+ * A DataTable whose rows form a tree. Parent and leaf nodes share the columns;
13
+ * the tree column indents each row by depth and gives parents a chevron. Tree
14
+ * order is the row order, so sorting and grouping are not offered.
15
+ */
16
+ export declare function TreeDataTable<T extends Record<string, unknown>>({ roots, getChildren, getKey, getAriaLabel, treeColumn, columns, expanded, onExpandedChange, defaultExpanded, ...table }: TreeDataTableProps<T>): import("react").JSX.Element;
17
+ //# sourceMappingURL=TreeDataTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeDataTable.d.ts","sourceRoot":"","sources":["../../src/data/TreeDataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,kBAAkB,EAAgB,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAa,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAGnF,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACtE,cAAc,CAAC,CAAC,CAAC,EACjB,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,eAAe,GAAG,cAAc,GAAG,qBAAqB,GAAG,sBAAsB,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,CAC3K,GACC,iBAAiB,CAAC,CAAC,CAAC,GACpB,kBAAkB,CAAC,CAAC,CAAC,GAAG;IACtB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CACnC,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/D,KAAK,EACL,WAAW,EACX,MAAM,EACN,YAAY,EACZ,UAAU,EACV,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,GAAG,KAAK,EACT,EAAE,kBAAkB,CAAC,CAAC,CAAC,+BAyBvB"}
@@ -0,0 +1,52 @@
1
+ import { DataTable } from "./DataTable.js";
2
+ import { useTreeRows } from "../lib/tree-rows.js";
3
+ import { TreeRowLead } from "./MatrixTreeTable.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ // src/data/TreeDataTable.tsx
6
+ /**
7
+ * A DataTable whose rows form a tree. Parent and leaf nodes share the columns;
8
+ * the tree column indents each row by depth and gives parents a chevron. Tree
9
+ * order is the row order, so sorting and grouping are not offered.
10
+ */
11
+ function TreeDataTable({ roots, getChildren, getKey, getAriaLabel, treeColumn, columns, expanded, onExpandedChange, defaultExpanded, ...table }) {
12
+ if (!columns.some((column) => column.key === treeColumn)) throw new Error(`TreeDataTable treeColumn "${treeColumn}" is not one of its columns`);
13
+ const { rows, toggle } = useTreeRows(roots, {
14
+ getChildren,
15
+ getKey
16
+ }, {
17
+ ...expanded !== void 0 ? { expanded } : {},
18
+ ...onExpandedChange ? { onExpandedChange } : {},
19
+ ...defaultExpanded !== void 0 ? { defaultExpanded } : {}
20
+ });
21
+ const byNode = new Map(rows.map((row) => [row.node, row]));
22
+ const treeColumns = columns.map((column) => {
23
+ const { sortable: _sortable, ...rest } = column;
24
+ if (column.key !== treeColumn) return rest;
25
+ return {
26
+ ...rest,
27
+ render: (value, node) => {
28
+ const row = byNode.get(node);
29
+ const content = column.render ? column.render(value, node) : String(value ?? "");
30
+ return row.hasChildren ? /* @__PURE__ */ jsx(TreeRowLead, {
31
+ depth: row.depth,
32
+ open: row.open,
33
+ label: getAriaLabel(node),
34
+ onToggle: () => toggle(row),
35
+ children: content
36
+ }) : /* @__PURE__ */ jsx(TreeRowLead, {
37
+ depth: row.depth,
38
+ children: content
39
+ });
40
+ }
41
+ };
42
+ });
43
+ return /* @__PURE__ */ jsx(DataTable, {
44
+ ...table,
45
+ columns: treeColumns,
46
+ data: rows.map((row) => row.node),
47
+ getRowId: (node) => byNode.get(node).key
48
+ });
49
+ }
50
+ export { TreeDataTable };
51
+
52
+ //# sourceMappingURL=TreeDataTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeDataTable.js","names":[],"sources":["../../src/data/TreeDataTable.tsx"],"sourcesContent":["import { useTreeRows, type TreeExpansionProps, type TreeRow, type TreeRowsAccessors } from \"../lib/tree-rows\";\nimport { DataTable, type DataTableColumn, type DataTableProps } from \"./DataTable\";\nimport { TreeRowLead } from \"./MatrixTreeTable\";\n\nexport type TreeDataTableProps<T extends Record<string, unknown>> = Omit<\n DataTableProps<T>,\n \"data\" | \"columns\" | \"getRowId\" | \"grouping\" | \"groupingModes\" | \"groupingMode\" | \"defaultGroupingMode\" | \"onGroupingModeChange\" | \"defaultSort\" | \"sort\" | \"onSortChange\"\n> &\n TreeRowsAccessors<T> &\n TreeExpansionProps<T> & {\n roots: T[];\n columns: DataTableColumn<T>[];\n /** Key of the column that carries the indent and the expand/collapse chevron. */\n treeColumn: string;\n /** Names a parent node in its expand/collapse button. */\n getAriaLabel: (node: T) => string;\n };\n\n/**\n * A DataTable whose rows form a tree. Parent and leaf nodes share the columns;\n * the tree column indents each row by depth and gives parents a chevron. Tree\n * order is the row order, so sorting and grouping are not offered.\n */\nexport function TreeDataTable<T extends Record<string, unknown>>({\n roots,\n getChildren,\n getKey,\n getAriaLabel,\n treeColumn,\n columns,\n expanded,\n onExpandedChange,\n defaultExpanded,\n ...table\n}: TreeDataTableProps<T>) {\n if (!columns.some((column) => column.key === treeColumn)) {\n throw new Error(`TreeDataTable treeColumn \"${treeColumn}\" is not one of its columns`);\n }\n const { rows, toggle } = useTreeRows(roots, { getChildren, getKey }, {\n ...(expanded !== undefined ? { expanded } : {}),\n ...(onExpandedChange ? { onExpandedChange } : {}),\n ...(defaultExpanded !== undefined ? { defaultExpanded } : {}),\n });\n const byNode = new Map<T, TreeRow<T>>(rows.map((row) => [row.node, row]));\n const treeColumns = columns.map((column): DataTableColumn<T> => {\n const { sortable: _sortable, ...rest } = column;\n if (column.key !== treeColumn) return rest;\n return {\n ...rest,\n render: (value, node) => {\n const row = byNode.get(node)!;\n const content = column.render ? column.render(value, node) : String(value ?? \"\");\n return row.hasChildren\n ? <TreeRowLead depth={row.depth} open={row.open} label={getAriaLabel(node)} onToggle={() => toggle(row)}>{content}</TreeRowLead>\n : <TreeRowLead depth={row.depth}>{content}</TreeRowLead>;\n },\n };\n });\n return <DataTable<T> {...table} columns={treeColumns} data={rows.map((row) => row.node)} getRowId={(node) => byNode.get(node)!.key} />;\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,cAAiD,EAC/D,OACA,aACA,QACA,cACA,YACA,SACA,UACA,kBACA,iBACA,GAAG,SACqB;CACxB,IAAI,CAAC,QAAQ,MAAM,WAAW,OAAO,QAAQ,UAAU,GACrD,MAAM,IAAI,MAAM,6BAA6B,WAAW,4BAA4B;CAEtF,MAAM,EAAE,MAAM,WAAW,YAAY,OAAO;EAAE;EAAa;CAAO,GAAG;EACnE,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;EAC/C,GAAI,oBAAoB,KAAA,IAAY,EAAE,gBAAgB,IAAI,CAAC;CAC7D,CAAC;CACD,MAAM,SAAS,IAAI,IAAmB,KAAK,KAAK,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;CACxE,MAAM,cAAc,QAAQ,KAAK,WAA+B;EAC9D,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;EACzC,IAAI,OAAO,QAAQ,YAAY,OAAO;EACtC,OAAO;GACL,GAAG;GACH,SAAS,OAAO,SAAS;IACvB,MAAM,MAAM,OAAO,IAAI,IAAI;IAC3B,MAAM,UAAU,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,IAAI,OAAO,SAAS,EAAE;IAC/E,OAAO,IAAI,cACP,oBAAC,aAAD;KAAa,OAAO,IAAI;KAAO,MAAM,IAAI;KAAM,OAAO,aAAa,IAAI;KAAG,gBAAgB,OAAO,GAAG;KAAI,UAAA;IAAqB,CAAA,IAC7H,oBAAC,aAAD;KAAa,OAAO,IAAI;KAAQ,UAAA;IAAqB,CAAA;GAC3D;EACF;CACF,CAAC;CACD,OAAO,oBAAC,WAAD;EAAc,GAAI;EAAO,SAAS;EAAa,MAAM,KAAK,KAAK,QAAQ,IAAI,IAAI;EAAG,WAAW,SAAS,OAAO,IAAI,IAAI,CAAC,CAAE;CAAM,CAAA;AACvI"}
@@ -12,7 +12,7 @@ let react_jsx_runtime = require("react/jsx-runtime");
12
12
  * every range — which pushed everything else in that menu out of reach and
13
13
  * still truncated the one thing worth reading: what each range costs.
14
14
  */
15
- function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes, onDownload }) {
15
+ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes, onDownload, pendingContent, allowCloseWhilePending = false }) {
16
16
  const [format, setFormat] = (0, react.useState)(formats[0]?.format);
17
17
  const [scope, setScope] = (0, react.useState)(scopes[0]?.scope);
18
18
  const [pending, setPending] = (0, react.useState)(false);
@@ -48,8 +48,8 @@ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes,
48
48
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.Button, {
49
49
  variant: "outline",
50
50
  onClick: onClose,
51
- disabled: pending,
52
- children: "Cancel"
51
+ disabled: pending && !allowCloseWhilePending,
52
+ children: pending && allowCloseWhilePending ? "Continue in background" : "Cancel"
53
53
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.Button, {
54
54
  onClick: () => void download(),
55
55
  loading: pending,
@@ -60,6 +60,7 @@ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes,
60
60
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
61
61
  className: "space-y-4",
62
62
  children: [
63
+ pending && pendingContent,
63
64
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
64
65
  role: "alert",
65
66
  className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
@@ -1 +1 @@
1
- {"version":3,"file":"ClickyExportDialog.cjs","names":["useState","Modal","Button","cn","Icon"],"sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { Modal } from \"../../overlay/Modal\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\n\n/** One format the endpoint advertises, already resolved for display. */\nexport type ClickyExportFormatOption = {\n /** Value handed back to `onDownload`. */\n format: string;\n label: string;\n /** What the format is for. */\n description?: string | undefined;\n icon?: StaticIconComponent | undefined;\n iconClassName?: string | undefined;\n};\n\n/** One result range the endpoint advertises. */\nexport type ClickyExportScopeOption = {\n /** Value handed back to `onDownload`; undefined for a legacy endpoint. */\n scope: string | undefined;\n label: string;\n /**\n * What this range costs or caps — \"Limited to 1,000 rows\", \"Streams rows as\n * they are read\". Per format, because a PDF's ceiling is not a CSV's.\n */\n note?: ((format: string) => string | undefined) | undefined;\n};\n\nexport type ClickyExportDialogProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n formats: ClickyExportFormatOption[];\n scopes: ClickyExportScopeOption[];\n /** Runs the export. Rejecting shows the reason without closing the dialog. */\n onDownload: (request: {\n format: string;\n scope: string | undefined;\n }) => Promise<void> | void;\n};\n\n/**\n * ClickyExportDialog picks a format and a range, then downloads.\n *\n * It exists because the same choice as a menu was 16 rows — every format times\n * every range — which pushed everything else in that menu out of reach and\n * still truncated the one thing worth reading: what each range costs.\n */\nexport function ClickyExportDialog({\n open,\n onClose,\n title = \"Export\",\n formats,\n scopes,\n onDownload,\n}: ClickyExportDialogProps) {\n const [format, setFormat] = useState(formats[0]?.format);\n const [scope, setScope] = useState(scopes[0]?.scope);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState(\"\");\n\n // Reopening starts from a clean slate: the error belongs to the attempt that\n // produced it, not to the next one.\n useEffect(() => {\n if (open) setError(\"\");\n }, [open]);\n\n const activeScope = scopes.find((option) => option.scope === scope);\n\n const download = async () => {\n if (!format) return;\n setPending(true);\n setError(\"\");\n try {\n await onDownload({ format, scope });\n onClose();\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : \"The export failed\");\n } finally {\n setPending(false);\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n size=\"lg\"\n closeOnEsc\n footer={\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button\n onClick={() => void download()}\n loading={pending}\n disabled={!format}\n >\n Download\n </Button>\n </div>\n }\n >\n <div className=\"space-y-4\">\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive\"\n >\n {error}\n </div>\n )}\n\n {scopes.length > 1 ? (\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Rows\n </legend>\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((option) => (\n <label\n key={option.scope ?? \"default\"}\n className={cn(\n \"cursor-pointer rounded-md border px-3 py-1.5 text-sm transition-colors\",\n option.scope === scope\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-input hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-scope\"\n // Named explicitly: the input is visually hidden behind its\n // own styled label, so nothing else gives it a name.\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.scope === scope}\n onChange={() => setScope(option.scope)}\n />\n {option.label}\n </label>\n ))}\n </div>\n </fieldset>\n ) : (\n scopes[0]?.label && (\n <p className=\"text-sm text-muted-foreground\">{scopes[0].label}</p>\n )\n )}\n\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Format\n </legend>\n <div className=\"grid gap-1 sm:grid-cols-2\">\n {formats.map((option) => {\n const note = activeScope?.note?.(option.format);\n return (\n <label\n key={option.format}\n className={cn(\n \"flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-left transition-colors\",\n option.format === format\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-transparent hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-format\"\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.format === format}\n onChange={() => setFormat(option.format)}\n />\n {option.icon && (\n <Icon\n icon={option.icon}\n className={cn(\n \"mt-0.5 shrink-0 text-sm\",\n option.iconClassName ?? \"text-muted-foreground\",\n )}\n />\n )}\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {option.label}\n </span>\n {option.description && (\n <span className=\"mt-0.5 block text-xs text-muted-foreground\">\n {option.description}\n </span>\n )}\n {note && (\n <span className=\"mt-0.5 block text-xs text-amber-700 [[data-theme=dark]_&]:text-amber-400\">\n {note}\n </span>\n )}\n </span>\n </label>\n );\n })}\n </div>\n </fieldset>\n\n </div>\n </Modal>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClickyExportDialog.cjs","names":["useState","Modal","Button","cn","Icon"],"sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { Modal } from \"../../overlay/Modal\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\n\n/** One format the endpoint advertises, already resolved for display. */\nexport type ClickyExportFormatOption = {\n /** Value handed back to `onDownload`. */\n format: string;\n label: string;\n /** What the format is for. */\n description?: string | undefined;\n icon?: StaticIconComponent | undefined;\n iconClassName?: string | undefined;\n};\n\n/** One result range the endpoint advertises. */\nexport type ClickyExportScopeOption = {\n /** Value handed back to `onDownload`; undefined for a legacy endpoint. */\n scope: string | undefined;\n label: string;\n /**\n * What this range costs or caps — \"Limited to 1,000 rows\", \"Streams rows as\n * they are read\". Per format, because a PDF's ceiling is not a CSV's.\n */\n note?: ((format: string) => string | undefined) | undefined;\n};\n\nexport type ClickyExportDialogProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n formats: ClickyExportFormatOption[];\n scopes: ClickyExportScopeOption[];\n pendingContent?: ReactNode;\n allowCloseWhilePending?: boolean;\n /** Runs the export. Rejecting shows the reason without closing the dialog. */\n onDownload: (request: {\n format: string;\n scope: string | undefined;\n }) => Promise<void> | void;\n};\n\n/**\n * ClickyExportDialog picks a format and a range, then downloads.\n *\n * It exists because the same choice as a menu was 16 rows — every format times\n * every range — which pushed everything else in that menu out of reach and\n * still truncated the one thing worth reading: what each range costs.\n */\nexport function ClickyExportDialog({\n open,\n onClose,\n title = \"Export\",\n formats,\n scopes,\n onDownload,\n pendingContent,\n allowCloseWhilePending = false,\n}: ClickyExportDialogProps) {\n const [format, setFormat] = useState(formats[0]?.format);\n const [scope, setScope] = useState(scopes[0]?.scope);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState(\"\");\n\n // Reopening starts from a clean slate: the error belongs to the attempt that\n // produced it, not to the next one.\n useEffect(() => {\n if (open) setError(\"\");\n }, [open]);\n\n const activeScope = scopes.find((option) => option.scope === scope);\n\n const download = async () => {\n if (!format) return;\n setPending(true);\n setError(\"\");\n try {\n await onDownload({ format, scope });\n onClose();\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : \"The export failed\");\n } finally {\n setPending(false);\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n size=\"lg\"\n closeOnEsc\n footer={\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"outline\" onClick={onClose} disabled={pending && !allowCloseWhilePending}>\n {pending && allowCloseWhilePending ? \"Continue in background\" : \"Cancel\"}\n </Button>\n <Button\n onClick={() => void download()}\n loading={pending}\n disabled={!format}\n >\n Download\n </Button>\n </div>\n }\n >\n <div className=\"space-y-4\">\n {pending && pendingContent}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive\"\n >\n {error}\n </div>\n )}\n\n {scopes.length > 1 ? (\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Rows\n </legend>\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((option) => (\n <label\n key={option.scope ?? \"default\"}\n className={cn(\n \"cursor-pointer rounded-md border px-3 py-1.5 text-sm transition-colors\",\n option.scope === scope\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-input hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-scope\"\n // Named explicitly: the input is visually hidden behind its\n // own styled label, so nothing else gives it a name.\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.scope === scope}\n onChange={() => setScope(option.scope)}\n />\n {option.label}\n </label>\n ))}\n </div>\n </fieldset>\n ) : (\n scopes[0]?.label && (\n <p className=\"text-sm text-muted-foreground\">{scopes[0].label}</p>\n )\n )}\n\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Format\n </legend>\n <div className=\"grid gap-1 sm:grid-cols-2\">\n {formats.map((option) => {\n const note = activeScope?.note?.(option.format);\n return (\n <label\n key={option.format}\n className={cn(\n \"flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-left transition-colors\",\n option.format === format\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-transparent hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-format\"\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.format === format}\n onChange={() => setFormat(option.format)}\n />\n {option.icon && (\n <Icon\n icon={option.icon}\n className={cn(\n \"mt-0.5 shrink-0 text-sm\",\n option.iconClassName ?? \"text-muted-foreground\",\n )}\n />\n )}\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {option.label}\n </span>\n {option.description && (\n <span className=\"mt-0.5 block text-xs text-muted-foreground\">\n {option.description}\n </span>\n )}\n {note && (\n <span className=\"mt-0.5 block text-xs text-amber-700 [[data-theme=dark]_&]:text-amber-400\">\n {note}\n </span>\n )}\n </span>\n </label>\n );\n })}\n </div>\n </fieldset>\n\n </div>\n </Modal>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from 'react';
1
2
  import { StaticIconComponent } from '../Icon.js';
2
3
  /** One format the endpoint advertises, already resolved for display. */
3
4
  export type ClickyExportFormatOption = {
@@ -26,6 +27,8 @@ export type ClickyExportDialogProps = {
26
27
  title?: string;
27
28
  formats: ClickyExportFormatOption[];
28
29
  scopes: ClickyExportScopeOption[];
30
+ pendingContent?: ReactNode;
31
+ allowCloseWhilePending?: boolean;
29
32
  /** Runs the export. Rejecting shows the reason without closing the dialog. */
30
33
  onDownload: (request: {
31
34
  format: string;
@@ -39,5 +42,5 @@ export type ClickyExportDialogProps = {
39
42
  * every range — which pushed everything else in that menu out of reach and
40
43
  * still truncated the one thing worth reading: what each range costs.
41
44
  */
42
- export declare function ClickyExportDialog({ open, onClose, title, formats, scopes, onDownload, }: ClickyExportDialogProps): import("react").JSX.Element;
45
+ export declare function ClickyExportDialog({ open, onClose, title, formats, scopes, onDownload, pendingContent, allowCloseWhilePending, }: ClickyExportDialogProps): import("react").JSX.Element;
43
46
  //# sourceMappingURL=ClickyExportDialog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClickyExportDialog.d.ts","sourceRoot":"","sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGzD,wEAAwE;AACxE,MAAM,MAAM,wBAAwB,GAAG;IACrC,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF,gDAAgD;AAChD,MAAM,MAAM,uBAAuB,GAAG;IACpC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAClC,8EAA8E;IAC9E,UAAU,EAAE,CAAC,OAAO,EAAE;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,OAAO,EACP,KAAgB,EAChB,OAAO,EACP,MAAM,EACN,UAAU,GACX,EAAE,uBAAuB,+BA2JzB"}
1
+ {"version":3,"file":"ClickyExportDialog.d.ts","sourceRoot":"","sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGzD,wEAAwE;AACxE,MAAM,MAAM,wBAAwB,GAAG;IACrC,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF,gDAAgD;AAChD,MAAM,MAAM,uBAAuB,GAAG;IACpC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,8EAA8E;IAC9E,UAAU,EAAE,CAAC,OAAO,EAAE;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,OAAO,EACP,KAAgB,EAChB,OAAO,EACP,MAAM,EACN,UAAU,EACV,cAAc,EACd,sBAA8B,GAC/B,EAAE,uBAAuB,+BA4JzB"}
@@ -12,7 +12,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
12
  * every range — which pushed everything else in that menu out of reach and
13
13
  * still truncated the one thing worth reading: what each range costs.
14
14
  */
15
- function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes, onDownload }) {
15
+ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes, onDownload, pendingContent, allowCloseWhilePending = false }) {
16
16
  const [format, setFormat] = useState(formats[0]?.format);
17
17
  const [scope, setScope] = useState(scopes[0]?.scope);
18
18
  const [pending, setPending] = useState(false);
@@ -48,8 +48,8 @@ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes,
48
48
  children: [/* @__PURE__ */ jsx(Button, {
49
49
  variant: "outline",
50
50
  onClick: onClose,
51
- disabled: pending,
52
- children: "Cancel"
51
+ disabled: pending && !allowCloseWhilePending,
52
+ children: pending && allowCloseWhilePending ? "Continue in background" : "Cancel"
53
53
  }), /* @__PURE__ */ jsx(Button, {
54
54
  onClick: () => void download(),
55
55
  loading: pending,
@@ -60,6 +60,7 @@ function ClickyExportDialog({ open, onClose, title = "Export", formats, scopes,
60
60
  children: /* @__PURE__ */ jsxs("div", {
61
61
  className: "space-y-4",
62
62
  children: [
63
+ pending && pendingContent,
63
64
  error && /* @__PURE__ */ jsx("div", {
64
65
  role: "alert",
65
66
  className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
@@ -1 +1 @@
1
- {"version":3,"file":"ClickyExportDialog.js","names":[],"sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { Modal } from \"../../overlay/Modal\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\n\n/** One format the endpoint advertises, already resolved for display. */\nexport type ClickyExportFormatOption = {\n /** Value handed back to `onDownload`. */\n format: string;\n label: string;\n /** What the format is for. */\n description?: string | undefined;\n icon?: StaticIconComponent | undefined;\n iconClassName?: string | undefined;\n};\n\n/** One result range the endpoint advertises. */\nexport type ClickyExportScopeOption = {\n /** Value handed back to `onDownload`; undefined for a legacy endpoint. */\n scope: string | undefined;\n label: string;\n /**\n * What this range costs or caps — \"Limited to 1,000 rows\", \"Streams rows as\n * they are read\". Per format, because a PDF's ceiling is not a CSV's.\n */\n note?: ((format: string) => string | undefined) | undefined;\n};\n\nexport type ClickyExportDialogProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n formats: ClickyExportFormatOption[];\n scopes: ClickyExportScopeOption[];\n /** Runs the export. Rejecting shows the reason without closing the dialog. */\n onDownload: (request: {\n format: string;\n scope: string | undefined;\n }) => Promise<void> | void;\n};\n\n/**\n * ClickyExportDialog picks a format and a range, then downloads.\n *\n * It exists because the same choice as a menu was 16 rows — every format times\n * every range — which pushed everything else in that menu out of reach and\n * still truncated the one thing worth reading: what each range costs.\n */\nexport function ClickyExportDialog({\n open,\n onClose,\n title = \"Export\",\n formats,\n scopes,\n onDownload,\n}: ClickyExportDialogProps) {\n const [format, setFormat] = useState(formats[0]?.format);\n const [scope, setScope] = useState(scopes[0]?.scope);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState(\"\");\n\n // Reopening starts from a clean slate: the error belongs to the attempt that\n // produced it, not to the next one.\n useEffect(() => {\n if (open) setError(\"\");\n }, [open]);\n\n const activeScope = scopes.find((option) => option.scope === scope);\n\n const download = async () => {\n if (!format) return;\n setPending(true);\n setError(\"\");\n try {\n await onDownload({ format, scope });\n onClose();\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : \"The export failed\");\n } finally {\n setPending(false);\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n size=\"lg\"\n closeOnEsc\n footer={\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button\n onClick={() => void download()}\n loading={pending}\n disabled={!format}\n >\n Download\n </Button>\n </div>\n }\n >\n <div className=\"space-y-4\">\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive\"\n >\n {error}\n </div>\n )}\n\n {scopes.length > 1 ? (\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Rows\n </legend>\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((option) => (\n <label\n key={option.scope ?? \"default\"}\n className={cn(\n \"cursor-pointer rounded-md border px-3 py-1.5 text-sm transition-colors\",\n option.scope === scope\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-input hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-scope\"\n // Named explicitly: the input is visually hidden behind its\n // own styled label, so nothing else gives it a name.\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.scope === scope}\n onChange={() => setScope(option.scope)}\n />\n {option.label}\n </label>\n ))}\n </div>\n </fieldset>\n ) : (\n scopes[0]?.label && (\n <p className=\"text-sm text-muted-foreground\">{scopes[0].label}</p>\n )\n )}\n\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Format\n </legend>\n <div className=\"grid gap-1 sm:grid-cols-2\">\n {formats.map((option) => {\n const note = activeScope?.note?.(option.format);\n return (\n <label\n key={option.format}\n className={cn(\n \"flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-left transition-colors\",\n option.format === format\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-transparent hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-format\"\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.format === format}\n onChange={() => setFormat(option.format)}\n />\n {option.icon && (\n <Icon\n icon={option.icon}\n className={cn(\n \"mt-0.5 shrink-0 text-sm\",\n option.iconClassName ?? \"text-muted-foreground\",\n )}\n />\n )}\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {option.label}\n </span>\n {option.description && (\n <span className=\"mt-0.5 block text-xs text-muted-foreground\">\n {option.description}\n </span>\n )}\n {note && (\n <span className=\"mt-0.5 block text-xs text-amber-700 [[data-theme=dark]_&]:text-amber-400\">\n {note}\n </span>\n )}\n </span>\n </label>\n );\n })}\n </div>\n </fieldset>\n\n </div>\n </Modal>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClickyExportDialog.js","names":[],"sources":["../../../src/data/clicky-export/ClickyExportDialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { Modal } from \"../../overlay/Modal\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\n\n/** One format the endpoint advertises, already resolved for display. */\nexport type ClickyExportFormatOption = {\n /** Value handed back to `onDownload`. */\n format: string;\n label: string;\n /** What the format is for. */\n description?: string | undefined;\n icon?: StaticIconComponent | undefined;\n iconClassName?: string | undefined;\n};\n\n/** One result range the endpoint advertises. */\nexport type ClickyExportScopeOption = {\n /** Value handed back to `onDownload`; undefined for a legacy endpoint. */\n scope: string | undefined;\n label: string;\n /**\n * What this range costs or caps — \"Limited to 1,000 rows\", \"Streams rows as\n * they are read\". Per format, because a PDF's ceiling is not a CSV's.\n */\n note?: ((format: string) => string | undefined) | undefined;\n};\n\nexport type ClickyExportDialogProps = {\n open: boolean;\n onClose: () => void;\n title?: string;\n formats: ClickyExportFormatOption[];\n scopes: ClickyExportScopeOption[];\n pendingContent?: ReactNode;\n allowCloseWhilePending?: boolean;\n /** Runs the export. Rejecting shows the reason without closing the dialog. */\n onDownload: (request: {\n format: string;\n scope: string | undefined;\n }) => Promise<void> | void;\n};\n\n/**\n * ClickyExportDialog picks a format and a range, then downloads.\n *\n * It exists because the same choice as a menu was 16 rows — every format times\n * every range — which pushed everything else in that menu out of reach and\n * still truncated the one thing worth reading: what each range costs.\n */\nexport function ClickyExportDialog({\n open,\n onClose,\n title = \"Export\",\n formats,\n scopes,\n onDownload,\n pendingContent,\n allowCloseWhilePending = false,\n}: ClickyExportDialogProps) {\n const [format, setFormat] = useState(formats[0]?.format);\n const [scope, setScope] = useState(scopes[0]?.scope);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState(\"\");\n\n // Reopening starts from a clean slate: the error belongs to the attempt that\n // produced it, not to the next one.\n useEffect(() => {\n if (open) setError(\"\");\n }, [open]);\n\n const activeScope = scopes.find((option) => option.scope === scope);\n\n const download = async () => {\n if (!format) return;\n setPending(true);\n setError(\"\");\n try {\n await onDownload({ format, scope });\n onClose();\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : \"The export failed\");\n } finally {\n setPending(false);\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={title}\n size=\"lg\"\n closeOnEsc\n footer={\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"outline\" onClick={onClose} disabled={pending && !allowCloseWhilePending}>\n {pending && allowCloseWhilePending ? \"Continue in background\" : \"Cancel\"}\n </Button>\n <Button\n onClick={() => void download()}\n loading={pending}\n disabled={!format}\n >\n Download\n </Button>\n </div>\n }\n >\n <div className=\"space-y-4\">\n {pending && pendingContent}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive\"\n >\n {error}\n </div>\n )}\n\n {scopes.length > 1 ? (\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Rows\n </legend>\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((option) => (\n <label\n key={option.scope ?? \"default\"}\n className={cn(\n \"cursor-pointer rounded-md border px-3 py-1.5 text-sm transition-colors\",\n option.scope === scope\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-input hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-scope\"\n // Named explicitly: the input is visually hidden behind its\n // own styled label, so nothing else gives it a name.\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.scope === scope}\n onChange={() => setScope(option.scope)}\n />\n {option.label}\n </label>\n ))}\n </div>\n </fieldset>\n ) : (\n scopes[0]?.label && (\n <p className=\"text-sm text-muted-foreground\">{scopes[0].label}</p>\n )\n )}\n\n <fieldset>\n <legend className=\"mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n Format\n </legend>\n <div className=\"grid gap-1 sm:grid-cols-2\">\n {formats.map((option) => {\n const note = activeScope?.note?.(option.format);\n return (\n <label\n key={option.format}\n className={cn(\n \"flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-left transition-colors\",\n option.format === format\n ? \"border-foreground/20 bg-accent text-accent-foreground\"\n : \"border-transparent hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n <input\n type=\"radio\"\n name=\"clicky-export-format\"\n aria-label={option.label}\n className=\"sr-only\"\n checked={option.format === format}\n onChange={() => setFormat(option.format)}\n />\n {option.icon && (\n <Icon\n icon={option.icon}\n className={cn(\n \"mt-0.5 shrink-0 text-sm\",\n option.iconClassName ?? \"text-muted-foreground\",\n )}\n />\n )}\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {option.label}\n </span>\n {option.description && (\n <span className=\"mt-0.5 block text-xs text-muted-foreground\">\n {option.description}\n </span>\n )}\n {note && (\n <span className=\"mt-0.5 block text-xs text-amber-700 [[data-theme=dark]_&]:text-amber-400\">\n {note}\n </span>\n )}\n </span>\n </label>\n );\n })}\n </div>\n </fieldset>\n\n </div>\n </Modal>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ const require_utils = require("../../lib/utils.cjs");
2
+ const require_UiServer = require("../../icons/components/UiServer.cjs");
3
+ const require_UiClock = require("../../icons/components/UiClock.cjs");
4
+ const require_ProgressBar = require("../ProgressBar.cjs");
5
+ const require_matrix_marks = require("./matrix-marks.cjs");
6
+ let react_jsx_runtime = require("react/jsx-runtime");
7
+ // src/data/test-runner/MatrixMarks.tsx
8
+ function MatrixMark({ mark, label, className, static: isStatic = false }) {
9
+ const token = require_matrix_marks.MATRIX_MARKS[mark];
10
+ const Glyph = token.icon;
11
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
12
+ ...label === "" ? {} : { title: label ?? token.label },
13
+ className: require_utils.cn("size-3.5 shrink-0", token.tone, token.spin && !isStatic && "animate-spin", className),
14
+ "data-mark": mark
15
+ });
16
+ }
17
+ /** Outcome counts as glyph + number, in matrix order. */
18
+ function MatrixMix({ counts, unit, always = [], static: isStatic = false }) {
19
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20
+ role: "group",
21
+ "aria-label": `${unit} by outcome`,
22
+ className: "flex shrink-0 items-center gap-2 text-xs tabular-nums",
23
+ "data-mix": unit,
24
+ children: require_matrix_marks.MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {
25
+ const text = `${counts[key] ?? 0} ${require_matrix_marks.MATRIX_MARKS[key].label} ${unit}`;
26
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
27
+ role: "img",
28
+ "aria-label": text,
29
+ title: text,
30
+ className: require_utils.cn("inline-flex items-center gap-0.5", require_matrix_marks.MATRIX_MARKS[key].tone),
31
+ "data-mix-outcome": key,
32
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixMark, {
33
+ mark: key,
34
+ label: "",
35
+ static: isStatic
36
+ }), counts[key] ?? 0]
37
+ }, key);
38
+ })
39
+ });
40
+ }
41
+ function MatrixElapsed({ ms, running, clock = false }) {
42
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
43
+ className: require_utils.cn("inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums", running ? "text-blue-700 dark:text-blue-300" : "text-muted-foreground"),
44
+ title: running ? "elapsed so far" : "total duration",
45
+ "data-duration": "",
46
+ children: [clock && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_UiClock.UiClock, {
47
+ className: "size-3",
48
+ "aria-hidden": "true"
49
+ }), require_matrix_marks.formatMatrixDuration(ms)]
50
+ });
51
+ }
52
+ /**
53
+ * A plan with no run in an environment: pending while queued, error when it
54
+ * never started, hollow when not part of that run.
55
+ */
56
+ function EmptyMatrixCell({ outcome, detail, title, bare = false }) {
57
+ const neverStarted = detail === "never started";
58
+ if (!neverStarted && outcome !== "queued" && outcome !== "absent") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);
59
+ const mark = neverStarted ? "error" : outcome === "queued" ? "pending" : "absent";
60
+ const label = neverStarted ? "never started" : require_matrix_marks.MATRIX_MARKS[mark].label;
61
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
62
+ title,
63
+ className: "flex h-5 min-w-0 items-center gap-1.5 text-xs",
64
+ "data-empty-cell": mark,
65
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixMark, {
66
+ mark,
67
+ label
68
+ }), !bare && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
69
+ className: require_matrix_marks.MATRIX_MARKS[mark].tone,
70
+ children: label
71
+ })]
72
+ });
73
+ }
74
+ function MatrixLegend({ static: isStatic = false }) {
75
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
76
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground",
77
+ "data-batch-legend": "",
78
+ children: [require_matrix_marks.MATRIX_ORDER.map((key) => {
79
+ const token = require_matrix_marks.MATRIX_MARKS[key];
80
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
81
+ className: "inline-flex items-center gap-1",
82
+ "data-legend-mark": key,
83
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixMark, {
84
+ mark: key,
85
+ label: "",
86
+ static: isStatic
87
+ }), token.legend ?? token.label]
88
+ }, key);
89
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
90
+ className: "inline-flex items-center gap-1",
91
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_UiClock.UiClock, {
92
+ className: "size-3",
93
+ "aria-hidden": "true"
94
+ }), "elapsed · total duration"]
95
+ })]
96
+ });
97
+ }
98
+ /** A context name, always in the same chip, so an environment never reads as a plan or a status. */
99
+ function EnvChip({ context, className }) {
100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
101
+ className: require_utils.cn("inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground", className),
102
+ "data-env-chip": context,
103
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_UiServer.UiServer, {
104
+ className: "size-3 text-muted-foreground",
105
+ "aria-hidden": "true"
106
+ }), context]
107
+ });
108
+ }
109
+ var HEADER_GRID = "grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5";
110
+ var HEADER_ROW = "flex h-5 min-w-0 items-center gap-2";
111
+ /**
112
+ * One environment's column head in three fixed rows: its chip, the plans'
113
+ * progress with the elapsed time, and the plan verdict naming its units.
114
+ */
115
+ function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }) {
116
+ const done = counts.passed + counts.warned + counts.failed + counts.error;
117
+ const chip = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnvChip, { context });
118
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
119
+ className: HEADER_GRID,
120
+ "data-env-header": context,
121
+ children: [
122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
123
+ className: HEADER_ROW,
124
+ children: [renderChip && !isStatic ? renderChip(chip) : chip, aside]
125
+ }),
126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
127
+ className: HEADER_ROW,
128
+ title: `${done} of ${counts.total} plans finished`,
129
+ "data-env-progress": "",
130
+ children: [
131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
132
+ className: "min-w-0 flex-1",
133
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ProgressBar.ProgressBar, {
134
+ total: counts.total,
135
+ height: "h-1.5",
136
+ segments: require_matrix_marks.MATRIX_PLAN_BAR.map((key) => ({
137
+ count: counts[key],
138
+ color: require_matrix_marks.matrixBarClass(key, isStatic),
139
+ label: `${require_matrix_marks.MATRIX_MARKS[key].label} plans`
140
+ }))
141
+ })
142
+ }),
143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
144
+ className: "shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground",
145
+ children: [
146
+ done,
147
+ "/",
148
+ counts.total,
149
+ " plans"
150
+ ]
151
+ }),
152
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixElapsed, {
153
+ ms: durationMs,
154
+ running,
155
+ clock: true
156
+ })
157
+ ]
158
+ }),
159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
160
+ className: HEADER_ROW,
161
+ "data-env-verdict": "",
162
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixMix, {
163
+ counts: {
164
+ passed: counts.passed,
165
+ warned: counts.warned,
166
+ running: counts.running,
167
+ pending: counts.queued,
168
+ failed: counts.failed,
169
+ error: counts.error
170
+ },
171
+ unit: "plans",
172
+ always: ["failed"],
173
+ static: isStatic
174
+ })
175
+ })
176
+ ]
177
+ });
178
+ }
179
+ /** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */
180
+ function MatrixUnavailableEnvHeader({ context }) {
181
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
182
+ className: HEADER_GRID,
183
+ "data-env-header": context,
184
+ children: [
185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
186
+ className: HEADER_ROW,
187
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnvChip, { context })
188
+ }),
189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
190
+ className: `${HEADER_ROW} text-[11px] font-medium ${require_matrix_marks.MATRIX_MARKS.error.tone}`,
191
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixMark, {
192
+ mark: "error",
193
+ label: ""
194
+ }), "did not load"]
195
+ }),
196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
197
+ className: HEADER_ROW,
198
+ "aria-hidden": "true"
199
+ })
200
+ ]
201
+ });
202
+ }
203
+ exports.EmptyMatrixCell = EmptyMatrixCell;
204
+ exports.EnvChip = EnvChip;
205
+ exports.MatrixElapsed = MatrixElapsed;
206
+ exports.MatrixEnvHeader = MatrixEnvHeader;
207
+ exports.MatrixLegend = MatrixLegend;
208
+ exports.MatrixMark = MatrixMark;
209
+ exports.MatrixMix = MatrixMix;
210
+ exports.MatrixUnavailableEnvHeader = MatrixUnavailableEnvHeader;
211
+
212
+ //# sourceMappingURL=MatrixMarks.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MatrixMarks.cjs","names":["MATRIX_MARKS","cn","MATRIX_ORDER","UiClock","formatMatrixDuration","UiServer","ProgressBar","MATRIX_PLAN_BAR","matrixBarClass"],"sources":["../../../src/data/test-runner/MatrixMarks.tsx"],"sourcesContent":["// Renders the matrix-marks.ts vocabulary: one outcome's glyph, outcome mixes,\n// durations, empty cells, the legend, environment chips and the environment\n// column header. Props-only, so a live web matrix and a static print template\n// draw the same marks; `static` drops animation and links for print.\n\nimport type { ReactNode } from \"react\";\nimport { UiClock, UiServer } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport { ProgressBar } from \"../ProgressBar\";\nimport {\n MATRIX_MARKS,\n MATRIX_ORDER,\n MATRIX_PLAN_BAR,\n formatMatrixDuration,\n matrixBarClass,\n type MatrixCounts,\n type MatrixEnvCounts,\n type MatrixMarkKey,\n type MatrixMarkToken,\n} from \"./matrix-marks\";\n\nexport type MatrixMarkProps = {\n mark: MatrixMarkKey;\n /** Hover title; \"\" leaves the glyph untitled where the surrounding element already names it. */\n label?: string;\n className?: string;\n /** Print: never spins. */\n static?: boolean;\n};\n\nexport function MatrixMark({ mark, label, className, static: isStatic = false }: MatrixMarkProps) {\n const token: MatrixMarkToken = MATRIX_MARKS[mark];\n const Glyph = token.icon;\n return (\n <Glyph\n {...(label === \"\" ? {} : { title: label ?? token.label })}\n className={cn(\"size-3.5 shrink-0\", token.tone, token.spin && !isStatic && \"animate-spin\", className)}\n data-mark={mark}\n />\n );\n}\n\nexport type MatrixMixProps = {\n counts: MatrixCounts;\n unit: \"steps\" | \"plans\";\n /** Outcomes drawn even at zero; every other zero count is dropped. */\n always?: readonly MatrixMarkKey[];\n static?: boolean;\n};\n\n/** Outcome counts as glyph + number, in matrix order. */\nexport function MatrixMix({ counts, unit, always = [], static: isStatic = false }: MatrixMixProps) {\n return (\n <span role=\"group\" aria-label={`${unit} by outcome`} className=\"flex shrink-0 items-center gap-2 text-xs tabular-nums\" data-mix={unit}>\n {MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {\n const text = `${counts[key] ?? 0} ${MATRIX_MARKS[key].label} ${unit}`;\n return (\n <span key={key} role=\"img\" aria-label={text} title={text} className={cn(\"inline-flex items-center gap-0.5\", MATRIX_MARKS[key].tone)} data-mix-outcome={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {counts[key] ?? 0}\n </span>\n );\n })}\n </span>\n );\n}\n\nexport type MatrixElapsedProps = {\n ms: number;\n /** Still counting: drawn blue and titled \"elapsed so far\". */\n running: boolean;\n /** Prefix a clock glyph. */\n clock?: boolean;\n};\n\nexport function MatrixElapsed({ ms, running, clock = false }: MatrixElapsedProps) {\n return (\n <span\n className={cn(\"inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums\", running ? \"text-blue-700 dark:text-blue-300\" : \"text-muted-foreground\")}\n title={running ? \"elapsed so far\" : \"total duration\"}\n data-duration=\"\"\n >\n {clock && <UiClock className=\"size-3\" aria-hidden=\"true\" />}\n {formatMatrixDuration(ms)}\n </span>\n );\n}\n\nexport type EmptyMatrixCellProps = {\n /** The cell outcome: \"queued\", \"absent\", or \"error\" with detail \"never started\". Anything else throws. */\n outcome: string;\n /** Why a cell has no run: \"never started\". */\n detail?: string;\n /** Hover title of the cell. */\n title: string;\n /** The glyph alone (sub-step rows), its meaning on hover. */\n bare?: boolean;\n};\n\n/**\n * A plan with no run in an environment: pending while queued, error when it\n * never started, hollow when not part of that run.\n */\nexport function EmptyMatrixCell({ outcome, detail, title, bare = false }: EmptyMatrixCellProps) {\n const neverStarted = detail === \"never started\";\n if (!neverStarted && outcome !== \"queued\" && outcome !== \"absent\") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);\n const mark: MatrixMarkKey = neverStarted ? \"error\" : outcome === \"queued\" ? \"pending\" : \"absent\";\n const label = neverStarted ? \"never started\" : MATRIX_MARKS[mark].label;\n return (\n <span title={title} className=\"flex h-5 min-w-0 items-center gap-1.5 text-xs\" data-empty-cell={mark}>\n <MatrixMark mark={mark} label={label} />\n {!bare && <span className={MATRIX_MARKS[mark].tone}>{label}</span>}\n </span>\n );\n}\n\nexport type MatrixLegendProps = { static?: boolean };\n\nexport function MatrixLegend({ static: isStatic = false }: MatrixLegendProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground\" data-batch-legend=\"\">\n {MATRIX_ORDER.map((key) => {\n const token: MatrixMarkToken = MATRIX_MARKS[key];\n return (\n <span key={key} className=\"inline-flex items-center gap-1\" data-legend-mark={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {token.legend ?? token.label}\n </span>\n );\n })}\n <span className=\"inline-flex items-center gap-1\">\n <UiClock className=\"size-3\" aria-hidden=\"true\" />\n elapsed · total duration\n </span>\n </div>\n );\n}\n\nexport type EnvChipProps = { context: string; className?: string };\n\n/** A context name, always in the same chip, so an environment never reads as a plan or a status. */\nexport function EnvChip({ context, className }: EnvChipProps) {\n return (\n <span\n className={cn(\"inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground\", className)}\n data-env-chip={context}\n >\n <UiServer className=\"size-3 text-muted-foreground\" aria-hidden=\"true\" />\n {context}\n </span>\n );\n}\n\nconst HEADER_GRID = \"grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5\";\nconst HEADER_ROW = \"flex h-5 min-w-0 items-center gap-2\";\n\nexport type MatrixEnvHeaderProps = {\n context: string;\n counts: MatrixEnvCounts;\n durationMs: number;\n /** The environment's run is still running: its elapsed time is still counting. */\n running: boolean;\n /** Wraps the chip, e.g. in the host router's link to the member run. Never called when static. */\n renderChip?: (chip: ReactNode) => ReactNode;\n /** Drawn beside the chip, e.g. how often the member was retried. */\n aside?: ReactNode;\n static?: boolean;\n};\n\n/**\n * One environment's column head in three fixed rows: its chip, the plans'\n * progress with the elapsed time, and the plan verdict naming its units.\n */\nexport function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }: MatrixEnvHeaderProps) {\n const done = counts.passed + counts.warned + counts.failed + counts.error;\n const chip = <EnvChip context={context} />;\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n {renderChip && !isStatic ? renderChip(chip) : chip}\n {aside}\n </div>\n <div className={HEADER_ROW} title={`${done} of ${counts.total} plans finished`} data-env-progress=\"\">\n <span className=\"min-w-0 flex-1\">\n <ProgressBar\n total={counts.total}\n height=\"h-1.5\"\n segments={MATRIX_PLAN_BAR.map((key) => ({ count: counts[key], color: matrixBarClass(key, isStatic), label: `${MATRIX_MARKS[key].label} plans` }))}\n />\n </span>\n <span className=\"shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground\">\n {done}/{counts.total} plans\n </span>\n <MatrixElapsed ms={durationMs} running={running} clock />\n </div>\n <div className={HEADER_ROW} data-env-verdict=\"\">\n <MatrixMix\n counts={{ passed: counts.passed, warned: counts.warned, running: counts.running, pending: counts.queued, failed: counts.failed, error: counts.error }}\n unit=\"plans\"\n always={[\"failed\"]}\n static={isStatic}\n />\n </div>\n </div>\n );\n}\n\nexport type MatrixUnavailableEnvHeaderProps = { context: string };\n\n/** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */\nexport function MatrixUnavailableEnvHeader({ context }: MatrixUnavailableEnvHeaderProps) {\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n <EnvChip context={context} />\n </div>\n <div className={`${HEADER_ROW} text-[11px] font-medium ${MATRIX_MARKS.error.tone}`}>\n <MatrixMark mark=\"error\" label=\"\" />\n did not load\n </div>\n <div className={HEADER_ROW} aria-hidden=\"true\" />\n </div>\n );\n}\n"],"mappings":"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"}