@flanksource/clicky-ui 0.2.4 → 0.2.6

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 (189) hide show
  1. package/dist/components/Combobox.cjs +291 -0
  2. package/dist/components/Combobox.cjs.map +1 -0
  3. package/dist/components/Combobox.d.ts +59 -0
  4. package/dist/components/Combobox.d.ts.map +1 -0
  5. package/dist/components/Combobox.js +291 -0
  6. package/dist/components/Combobox.js.map +1 -0
  7. package/dist/components/FilterBar.cjs +153 -124
  8. package/dist/components/FilterBar.cjs.map +1 -1
  9. package/dist/components/FilterBar.d.ts +25 -1
  10. package/dist/components/FilterBar.d.ts.map +1 -1
  11. package/dist/components/FilterBar.js +153 -124
  12. package/dist/components/FilterBar.js.map +1 -1
  13. package/dist/components/FormatOptionsDropdown.cjs +62 -0
  14. package/dist/components/FormatOptionsDropdown.cjs.map +1 -0
  15. package/dist/components/FormatOptionsDropdown.d.ts +37 -0
  16. package/dist/components/FormatOptionsDropdown.d.ts.map +1 -0
  17. package/dist/components/FormatOptionsDropdown.js +62 -0
  18. package/dist/components/FormatOptionsDropdown.js.map +1 -0
  19. package/dist/components/SplitButton.cjs +87 -0
  20. package/dist/components/SplitButton.cjs.map +1 -0
  21. package/dist/components/SplitButton.d.ts +32 -0
  22. package/dist/components/SplitButton.d.ts.map +1 -0
  23. package/dist/components/SplitButton.js +87 -0
  24. package/dist/components/SplitButton.js.map +1 -0
  25. package/dist/components/icon-menu-picker.cjs +1 -1
  26. package/dist/components/icon-menu-picker.cjs.map +1 -1
  27. package/dist/components/icon-menu-picker.js +1 -1
  28. package/dist/components/icon-menu-picker.js.map +1 -1
  29. package/dist/components.cjs +7 -0
  30. package/dist/components.cjs.map +1 -1
  31. package/dist/components.d.ts +3 -0
  32. package/dist/components.d.ts.map +1 -1
  33. package/dist/components.js +7 -0
  34. package/dist/components.js.map +1 -1
  35. package/dist/data/Badge.d.ts +1 -1
  36. package/dist/data/ProgressBar.cjs +4 -2
  37. package/dist/data/ProgressBar.cjs.map +1 -1
  38. package/dist/data/ProgressBar.d.ts +1 -1
  39. package/dist/data/ProgressBar.d.ts.map +1 -1
  40. package/dist/data/ProgressBar.js +4 -2
  41. package/dist/data/ProgressBar.js.map +1 -1
  42. package/dist/data/StatusBreakdown.cjs +80 -0
  43. package/dist/data/StatusBreakdown.cjs.map +1 -0
  44. package/dist/data/StatusBreakdown.d.ts +36 -0
  45. package/dist/data/StatusBreakdown.d.ts.map +1 -0
  46. package/dist/data/StatusBreakdown.js +80 -0
  47. package/dist/data/StatusBreakdown.js.map +1 -0
  48. package/dist/data/TaskManager.cjs +162 -0
  49. package/dist/data/TaskManager.cjs.map +1 -0
  50. package/dist/data/TaskManager.d.ts +21 -0
  51. package/dist/data/TaskManager.d.ts.map +1 -0
  52. package/dist/data/TaskManager.js +162 -0
  53. package/dist/data/TaskManager.js.map +1 -0
  54. package/dist/data/TaskProgress.cjs +125 -0
  55. package/dist/data/TaskProgress.cjs.map +1 -0
  56. package/dist/data/TaskProgress.d.ts +20 -0
  57. package/dist/data/TaskProgress.d.ts.map +1 -0
  58. package/dist/data/TaskProgress.js +125 -0
  59. package/dist/data/TaskProgress.js.map +1 -0
  60. package/dist/data/TaskSnapshot.cjs +23 -0
  61. package/dist/data/TaskSnapshot.cjs.map +1 -0
  62. package/dist/data/TaskSnapshot.d.ts +49 -0
  63. package/dist/data/TaskSnapshot.d.ts.map +1 -0
  64. package/dist/data/TaskSnapshot.js +23 -0
  65. package/dist/data/TaskSnapshot.js.map +1 -0
  66. package/dist/data/TimeseriesGauge.cjs +144 -0
  67. package/dist/data/TimeseriesGauge.cjs.map +1 -0
  68. package/dist/data/TimeseriesGauge.d.ts +56 -0
  69. package/dist/data/TimeseriesGauge.d.ts.map +1 -0
  70. package/dist/data/TimeseriesGauge.js +144 -0
  71. package/dist/data/TimeseriesGauge.js.map +1 -0
  72. package/dist/data/TimeseriesPanel.axes.cjs +39 -0
  73. package/dist/data/TimeseriesPanel.axes.cjs.map +1 -0
  74. package/dist/data/TimeseriesPanel.axes.d.ts +28 -0
  75. package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -0
  76. package/dist/data/TimeseriesPanel.axes.js +39 -0
  77. package/dist/data/TimeseriesPanel.axes.js.map +1 -0
  78. package/dist/data/TimeseriesPanel.cjs +324 -0
  79. package/dist/data/TimeseriesPanel.cjs.map +1 -0
  80. package/dist/data/TimeseriesPanel.d.ts +141 -0
  81. package/dist/data/TimeseriesPanel.d.ts.map +1 -0
  82. package/dist/data/TimeseriesPanel.js +324 -0
  83. package/dist/data/TimeseriesPanel.js.map +1 -0
  84. package/dist/data/Tree.cjs.map +1 -1
  85. package/dist/data/Tree.d.ts.map +1 -1
  86. package/dist/data/Tree.js.map +1 -1
  87. package/dist/data/TreeNode.cjs +44 -7
  88. package/dist/data/TreeNode.cjs.map +1 -1
  89. package/dist/data/TreeNode.d.ts +19 -1
  90. package/dist/data/TreeNode.d.ts.map +1 -1
  91. package/dist/data/TreeNode.js +44 -7
  92. package/dist/data/TreeNode.js.map +1 -1
  93. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +66 -44
  94. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
  95. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts +21 -1
  96. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
  97. package/dist/data/diagnostics/DiagnosticsDetailPanel.js +65 -43
  98. package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
  99. package/dist/data/diagnostics/DiagnosticsTree.cjs +2 -1
  100. package/dist/data/diagnostics/DiagnosticsTree.cjs.map +1 -1
  101. package/dist/data/diagnostics/DiagnosticsTree.js +2 -1
  102. package/dist/data/diagnostics/DiagnosticsTree.js.map +1 -1
  103. package/dist/data/diagnostics/FrameSourceWindow.cjs +37 -0
  104. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -0
  105. package/dist/data/diagnostics/FrameSourceWindow.d.ts +15 -0
  106. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -0
  107. package/dist/data/diagnostics/FrameSourceWindow.js +37 -0
  108. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -0
  109. package/dist/data/diagnostics/JvmStackTrace.cjs +25 -12
  110. package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
  111. package/dist/data/diagnostics/JvmStackTrace.d.ts +8 -1
  112. package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
  113. package/dist/data/diagnostics/JvmStackTrace.js +26 -13
  114. package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
  115. package/dist/data/diagnostics/ThreadCard.cjs +9 -2
  116. package/dist/data/diagnostics/ThreadCard.cjs.map +1 -1
  117. package/dist/data/diagnostics/ThreadCard.d.ts +3 -1
  118. package/dist/data/diagnostics/ThreadCard.d.ts.map +1 -1
  119. package/dist/data/diagnostics/ThreadCard.js +9 -2
  120. package/dist/data/diagnostics/ThreadCard.js.map +1 -1
  121. package/dist/data/diagnostics/jvm-stacktrace.cjs +10 -1
  122. package/dist/data/diagnostics/jvm-stacktrace.cjs.map +1 -1
  123. package/dist/data/diagnostics/jvm-stacktrace.d.ts.map +1 -1
  124. package/dist/data/diagnostics/jvm-stacktrace.js +10 -1
  125. package/dist/data/diagnostics/jvm-stacktrace.js.map +1 -1
  126. package/dist/data/diagnostics/utils.cjs +0 -12
  127. package/dist/data/diagnostics/utils.cjs.map +1 -1
  128. package/dist/data/diagnostics/utils.d.ts +1 -1
  129. package/dist/data/diagnostics/utils.d.ts.map +1 -1
  130. package/dist/data/diagnostics/utils.js +0 -12
  131. package/dist/data/diagnostics/utils.js.map +1 -1
  132. package/dist/data/task-status.cjs +108 -0
  133. package/dist/data/task-status.cjs.map +1 -0
  134. package/dist/data/task-status.d.ts +21 -0
  135. package/dist/data/task-status.d.ts.map +1 -0
  136. package/dist/data/task-status.js +108 -0
  137. package/dist/data/task-status.js.map +1 -0
  138. package/dist/data.cjs +27 -0
  139. package/dist/data.cjs.map +1 -1
  140. package/dist/data.d.ts +9 -1
  141. package/dist/data.d.ts.map +1 -1
  142. package/dist/data.js +27 -0
  143. package/dist/data.js.map +1 -1
  144. package/dist/hooks/use-task-run.cjs +137 -0
  145. package/dist/hooks/use-task-run.cjs.map +1 -0
  146. package/dist/hooks/use-task-run.d.ts +35 -0
  147. package/dist/hooks/use-task-run.d.ts.map +1 -0
  148. package/dist/hooks/use-task-run.js +137 -0
  149. package/dist/hooks/use-task-run.js.map +1 -0
  150. package/dist/hooks.cjs +3 -0
  151. package/dist/hooks.cjs.map +1 -1
  152. package/dist/hooks.d.ts +1 -0
  153. package/dist/hooks.d.ts.map +1 -1
  154. package/dist/hooks.js +3 -0
  155. package/dist/hooks.js.map +1 -1
  156. package/dist/index.cjs +43 -0
  157. package/dist/index.cjs.map +1 -1
  158. package/dist/index.js +43 -0
  159. package/dist/index.js.map +1 -1
  160. package/dist/lib/color.cjs +33 -0
  161. package/dist/lib/color.cjs.map +1 -0
  162. package/dist/lib/color.d.ts +10 -0
  163. package/dist/lib/color.d.ts.map +1 -0
  164. package/dist/lib/color.js +33 -0
  165. package/dist/lib/color.js.map +1 -0
  166. package/dist/lib/format.cjs +77 -0
  167. package/dist/lib/format.cjs.map +1 -0
  168. package/dist/lib/format.d.ts +30 -0
  169. package/dist/lib/format.d.ts.map +1 -0
  170. package/dist/lib/format.js +77 -0
  171. package/dist/lib/format.js.map +1 -0
  172. package/dist/overlay/DropdownMenu.cjs +18 -2
  173. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  174. package/dist/overlay/DropdownMenu.d.ts +3 -1
  175. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  176. package/dist/overlay/DropdownMenu.js +18 -2
  177. package/dist/overlay/DropdownMenu.js.map +1 -1
  178. package/dist/rpc/types.cjs.map +1 -1
  179. package/dist/rpc/types.d.ts +4 -0
  180. package/dist/rpc/types.d.ts.map +1 -1
  181. package/dist/rpc/types.js.map +1 -1
  182. package/dist/styles.css +1 -1
  183. package/dist/utils.cjs +6 -0
  184. package/dist/utils.cjs.map +1 -1
  185. package/dist/utils.d.ts +1 -0
  186. package/dist/utils.d.ts.map +1 -1
  187. package/dist/utils.js +6 -0
  188. package/dist/utils.js.map +1 -1
  189. package/package.json +4 -1
@@ -20,10 +20,18 @@ function TreeNode({
20
20
  rowClass,
21
21
  indentPx = 16,
22
22
  basePaddingPx = 8,
23
- isSecondary
23
+ isSecondary,
24
+ hasMoreChildren,
25
+ loadChildren
24
26
  }) {
25
- const children = getChildren(node);
27
+ const [loadedChildren, setLoadedChildren] = react.useState(null);
28
+ const [loading, setLoading] = react.useState(false);
29
+ const [error, setError] = react.useState(null);
30
+ const syncChildren = getChildren(node);
31
+ const children = loadedChildren ?? syncChildren;
26
32
  const hasChildren = ((children == null ? void 0 : children.length) ?? 0) > 0;
33
+ const lazyUnloaded = ((hasMoreChildren == null ? void 0 : hasMoreChildren(node)) ?? false) && loadedChildren === null;
34
+ const expandable = hasChildren || lazyUnloaded;
27
35
  const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;
28
36
  const [open, setOpen] = react.useState(initialOpen);
29
37
  const prevExpandAll = react.useRef(expandAll);
@@ -40,8 +48,33 @@ function TreeNode({
40
48
  }
41
49
  prevExpandAll.current = expandAll;
42
50
  }, [expandAll, skipExpandAll]);
51
+ const loadRequested = react.useRef(false);
52
+ const nodeKey = getKey(node);
53
+ const loadKeyRef = react.useRef(nodeKey);
54
+ function loadLazyChildren() {
55
+ if (!loadChildren || loadRequested.current) return;
56
+ loadRequested.current = true;
57
+ loadKeyRef.current = nodeKey;
58
+ setLoading(true);
59
+ setError(null);
60
+ loadChildren(node).then((kids) => {
61
+ if (loadKeyRef.current !== nodeKey) return;
62
+ setLoadedChildren(kids);
63
+ setLoading(false);
64
+ }).catch((err) => {
65
+ if (loadKeyRef.current !== nodeKey) return;
66
+ loadRequested.current = false;
67
+ setError(err);
68
+ setLoading(false);
69
+ });
70
+ }
43
71
  function toggle() {
44
- if (hasChildren) setOpen((o) => !o);
72
+ if (!expandable) return;
73
+ setOpen((o) => {
74
+ const next = !o;
75
+ if (next && lazyUnloaded) loadLazyChildren();
76
+ return next;
77
+ });
45
78
  }
46
79
  const defaultRowBg = isSelected ? "bg-primary/10 border-l-2 border-primary" : "hover:bg-accent";
47
80
  const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;
@@ -49,7 +82,7 @@ function TreeNode({
49
82
  "div",
50
83
  {
51
84
  role: "treeitem",
52
- "aria-expanded": hasChildren ? isOpen : void 0,
85
+ "aria-expanded": expandable ? isOpen : void 0,
53
86
  "aria-selected": isSelected,
54
87
  children: [
55
88
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -63,7 +96,7 @@ function TreeNode({
63
96
  toggle();
64
97
  },
65
98
  children: [
66
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
99
+ expandable ? /* @__PURE__ */ jsxRuntime.jsx(
67
100
  Icon.Icon,
68
101
  {
69
102
  icon: isOpen ? UiChevronDown.UiChevronDown : UiChevronRight.UiChevronRight,
@@ -76,7 +109,9 @@ function TreeNode({
76
109
  open: isOpen,
77
110
  selected: isSelected,
78
111
  hasChildren,
79
- toggle
112
+ toggle,
113
+ loading,
114
+ error
80
115
  })
81
116
  ]
82
117
  }
@@ -97,7 +132,9 @@ function TreeNode({
97
132
  ...defaultOpen ? { defaultOpen } : {},
98
133
  ...onSelect ? { onSelect } : {},
99
134
  ...rowClass ? { rowClass } : {},
100
- ...isSecondary ? { isSecondary } : {}
135
+ ...isSecondary ? { isSecondary } : {},
136
+ ...hasMoreChildren ? { hasMoreChildren } : {},
137
+ ...loadChildren ? { loadChildren } : {}
101
138
  },
102
139
  getKey(child)
103
140
  )) })
@@ -1 +1 @@
1
- {"version":3,"file":"TreeNode.cjs","sources":["../../src/data/TreeNode.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiChevronDown, UiChevronRight } from \"../icons\";\n\nexport type TreeRowContext<T> = {\n node: T;\n depth: number;\n open: boolean;\n selected: boolean;\n hasChildren: boolean;\n toggle: () => void;\n};\n\nexport type TreeNodeProps<T> = {\n node: T;\n depth?: number;\n expandAll?: boolean | null;\n forcedOpenKeys?: Set<string | number> | null;\n selected?: T | null;\n defaultOpen?: (node: T, depth: number) => boolean;\n getChildren: (node: T) => T[] | undefined;\n getKey: (node: T) => string | number;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n rowClass?: (node: T, selected: boolean) => string;\n indentPx?: number;\n basePaddingPx?: number;\n /**\n * Marks a node as a **secondary child** of its parent. Secondary\n * children keep the full tree structure (they render, they can be\n * opened manually) but opt out of the bulk behaviours a caller\n * typically wants when they click the toolbar:\n *\n * - Tree-wide filter: their text is skipped during match detection\n * so typing in the search box never surfaces a row because one\n * of its secondary children happened to match.\n * - Expand all: secondary nodes stay at their default-open state\n * instead of being forced open, preventing 28k field rows from\n * flooding the viewport when an operator hits the button on a\n * schema with hundreds of tables.\n *\n * Default: every node is primary (returns false).\n */\n isSecondary?: (node: T) => boolean;\n};\n\nexport function TreeNode<T>({\n node,\n depth = 0,\n expandAll = null,\n forcedOpenKeys = null,\n selected = null,\n defaultOpen,\n getChildren,\n getKey,\n onSelect,\n renderRow,\n rowClass,\n indentPx = 16,\n basePaddingPx = 8,\n isSecondary,\n}: TreeNodeProps<T>) {\n const children = getChildren(node);\n const hasChildren = (children?.length ?? 0) > 0;\n const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;\n const [open, setOpen] = useState(initialOpen);\n const prevExpandAll = useRef(expandAll);\n const isSelected = selected === node;\n const key = getKey(node);\n const isForcedOpen = forcedOpenKeys?.has(key) ?? false;\n const isOpen = isForcedOpen || open;\n // A node opts out of bulk expand-all in two distinct cases:\n //\n // 1. The node itself is secondary (its edge from the parent is\n // secondary) — rare at the root, but the rule is symmetric.\n // 2. All of the node's children are secondary — the common case.\n // This is what keeps an \"Expand all\" on a schema tree from\n // flooding the viewport with every table's field rows: the\n // table opens to nothing structural worth seeing in bulk, so\n // we leave it closed until the operator clicks it manually.\n //\n // In both cases the manual toggle still works, and filter matches\n // still force the row open via forcedOpenKeys, so users never\n // lose access — they just don't get auto-flooded.\n const secondaryNode = isSecondary?.(node) ?? false;\n const childrenAllSecondary =\n hasChildren && isSecondary != null && children!.every((c) => isSecondary(c));\n const skipExpandAll = secondaryNode || childrenAllSecondary;\n\n useEffect(() => {\n if (expandAll !== null && expandAll !== prevExpandAll.current && !skipExpandAll) {\n setOpen(expandAll);\n }\n prevExpandAll.current = expandAll;\n }, [expandAll, skipExpandAll]);\n\n function toggle() {\n if (hasChildren) setOpen((o) => !o);\n }\n\n const defaultRowBg = isSelected ? \"bg-primary/10 border-l-2 border-primary\" : \"hover:bg-accent\";\n const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;\n\n return (\n <div\n role=\"treeitem\"\n aria-expanded={hasChildren ? isOpen : undefined}\n aria-selected={isSelected}\n >\n <div\n className={cn(\"flex items-center gap-1.5 py-1 px-2 cursor-pointer text-sm\", rowClassName)}\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx}px` }}\n onClick={(e) => {\n e.stopPropagation();\n onSelect?.(node);\n toggle();\n }}\n >\n {hasChildren ? (\n <Icon\n icon={isOpen ? UiChevronDown : UiChevronRight}\n className=\"text-muted-foreground text-xs w-3\"\n />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n {renderRow({\n node,\n depth,\n open: isOpen,\n selected: isSelected,\n hasChildren,\n toggle,\n })}\n </div>\n {isOpen && hasChildren && (\n <div role=\"group\">\n {children!.map((child) => (\n <TreeNode\n key={getKey(child)}\n node={child}\n depth={depth + 1}\n expandAll={expandAll}\n forcedOpenKeys={forcedOpenKeys}\n selected={selected}\n getChildren={getChildren}\n getKey={getKey}\n renderRow={renderRow}\n indentPx={indentPx}\n basePaddingPx={basePaddingPx}\n {...(defaultOpen ? { defaultOpen } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","jsxs","cn","jsx","Icon","UiChevronDown","UiChevronRight"],"mappings":"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"}
1
+ {"version":3,"file":"TreeNode.cjs","sources":["../../src/data/TreeNode.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiChevronDown, UiChevronRight } from \"../icons\";\n\nexport type TreeRowContext<T> = {\n node: T;\n depth: number;\n open: boolean;\n selected: boolean;\n hasChildren: boolean;\n toggle: () => void;\n /** True while this node's lazy children are being fetched via loadChildren. */\n loading: boolean;\n /** The rejection value from loadChildren, or null when there was no error. */\n error: unknown;\n};\n\nexport type TreeNodeProps<T> = {\n node: T;\n depth?: number;\n expandAll?: boolean | null;\n forcedOpenKeys?: Set<string | number> | null;\n selected?: T | null;\n defaultOpen?: (node: T, depth: number) => boolean;\n getChildren: (node: T) => T[] | undefined;\n getKey: (node: T) => string | number;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n rowClass?: (node: T, selected: boolean) => string;\n indentPx?: number;\n basePaddingPx?: number;\n /**\n * Reports that a node has children that are not yet loaded — it renders an\n * expand chevron even when `getChildren` returns nothing. On the first open\n * the node fetches them via `loadChildren`. Without this (the default), a\n * node is expandable only when `getChildren` already returns children.\n */\n hasMoreChildren?: (node: T) => boolean;\n /**\n * Lazily fetch a node's children the first time it is opened. Resolves to the\n * child nodes (which may themselves be lazy). A rejection is surfaced to\n * `renderRow` via `ctx.error` and is not retried until the node is collapsed\n * and reopened. Required for any node `hasMoreChildren` returns true for.\n */\n loadChildren?: (node: T) => Promise<T[]>;\n /**\n * Marks a node as a **secondary child** of its parent. Secondary\n * children keep the full tree structure (they render, they can be\n * opened manually) but opt out of the bulk behaviours a caller\n * typically wants when they click the toolbar:\n *\n * - Tree-wide filter: their text is skipped during match detection\n * so typing in the search box never surfaces a row because one\n * of its secondary children happened to match.\n * - Expand all: secondary nodes stay at their default-open state\n * instead of being forced open, preventing 28k field rows from\n * flooding the viewport when an operator hits the button on a\n * schema with hundreds of tables.\n *\n * Default: every node is primary (returns false).\n */\n isSecondary?: (node: T) => boolean;\n};\n\nexport function TreeNode<T>({\n node,\n depth = 0,\n expandAll = null,\n forcedOpenKeys = null,\n selected = null,\n defaultOpen,\n getChildren,\n getKey,\n onSelect,\n renderRow,\n rowClass,\n indentPx = 16,\n basePaddingPx = 8,\n isSecondary,\n hasMoreChildren,\n loadChildren,\n}: TreeNodeProps<T>) {\n // Lazily-fetched children take precedence over the synchronous ones once a\n // load has resolved; until then `loadedChildren` is null and we fall back to\n // getChildren. A node can be \"lazy\" (hasMoreChildren) with no sync children.\n const [loadedChildren, setLoadedChildren] = useState<T[] | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>(null);\n const syncChildren = getChildren(node);\n const children = loadedChildren ?? syncChildren;\n const hasChildren = (children?.length ?? 0) > 0;\n const lazyUnloaded = (hasMoreChildren?.(node) ?? false) && loadedChildren === null;\n // The chevron shows when there is something to reveal: either real children\n // are present, or the node is lazy and has not been loaded yet.\n const expandable = hasChildren || lazyUnloaded;\n const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;\n const [open, setOpen] = useState(initialOpen);\n const prevExpandAll = useRef(expandAll);\n const isSelected = selected === node;\n const key = getKey(node);\n const isForcedOpen = forcedOpenKeys?.has(key) ?? false;\n const isOpen = isForcedOpen || open;\n // A node opts out of bulk expand-all in two distinct cases:\n //\n // 1. The node itself is secondary (its edge from the parent is\n // secondary) — rare at the root, but the rule is symmetric.\n // 2. All of the node's children are secondary — the common case.\n // This is what keeps an \"Expand all\" on a schema tree from\n // flooding the viewport with every table's field rows: the\n // table opens to nothing structural worth seeing in bulk, so\n // we leave it closed until the operator clicks it manually.\n //\n // In both cases the manual toggle still works, and filter matches\n // still force the row open via forcedOpenKeys, so users never\n // lose access — they just don't get auto-flooded.\n const secondaryNode = isSecondary?.(node) ?? false;\n const childrenAllSecondary =\n hasChildren && isSecondary != null && children!.every((c) => isSecondary(c));\n const skipExpandAll = secondaryNode || childrenAllSecondary;\n\n useEffect(() => {\n if (expandAll !== null && expandAll !== prevExpandAll.current && !skipExpandAll) {\n setOpen(expandAll);\n }\n prevExpandAll.current = expandAll;\n }, [expandAll, skipExpandAll]);\n\n // Guards a lazy fetch so a node loads its children at most once per mount and\n // a resolution arriving after the node was reused (key changed) is ignored.\n const loadRequested = useRef(false);\n const nodeKey = getKey(node);\n const loadKeyRef = useRef(nodeKey);\n\n function loadLazyChildren() {\n if (!loadChildren || loadRequested.current) return;\n loadRequested.current = true;\n loadKeyRef.current = nodeKey;\n setLoading(true);\n setError(null);\n loadChildren(node)\n .then((kids) => {\n if (loadKeyRef.current !== nodeKey) return;\n setLoadedChildren(kids);\n setLoading(false);\n })\n .catch((err) => {\n if (loadKeyRef.current !== nodeKey) return;\n // Surfaced to renderRow via ctx.error; allow a retry on re-toggle.\n loadRequested.current = false;\n setError(err);\n setLoading(false);\n });\n }\n\n function toggle() {\n if (!expandable) return;\n setOpen((o) => {\n const next = !o;\n // Only fetch when transitioning into the open state, so collapsing a\n // not-yet-loaded (e.g. previously-failed) lazy node never triggers a load.\n if (next && lazyUnloaded) loadLazyChildren();\n return next;\n });\n }\n\n const defaultRowBg = isSelected ? \"bg-primary/10 border-l-2 border-primary\" : \"hover:bg-accent\";\n const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;\n\n return (\n <div\n role=\"treeitem\"\n aria-expanded={expandable ? isOpen : undefined}\n aria-selected={isSelected}\n >\n <div\n className={cn(\"flex items-center gap-1.5 py-1 px-2 cursor-pointer text-sm\", rowClassName)}\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx}px` }}\n onClick={(e) => {\n e.stopPropagation();\n onSelect?.(node);\n toggle();\n }}\n >\n {expandable ? (\n <Icon\n icon={isOpen ? UiChevronDown : UiChevronRight}\n className=\"text-muted-foreground text-xs w-3\"\n />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n {renderRow({\n node,\n depth,\n open: isOpen,\n selected: isSelected,\n hasChildren,\n toggle,\n loading,\n error,\n })}\n </div>\n {isOpen && hasChildren && (\n <div role=\"group\">\n {children!.map((child) => (\n <TreeNode\n key={getKey(child)}\n node={child}\n depth={depth + 1}\n expandAll={expandAll}\n forcedOpenKeys={forcedOpenKeys}\n selected={selected}\n getChildren={getChildren}\n getKey={getKey}\n renderRow={renderRow}\n indentPx={indentPx}\n basePaddingPx={basePaddingPx}\n {...(defaultOpen ? { defaultOpen } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","jsxs","cn","jsx","Icon","UiChevronDown","UiChevronRight"],"mappings":"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"}
@@ -6,6 +6,10 @@ export type TreeRowContext<T> = {
6
6
  selected: boolean;
7
7
  hasChildren: boolean;
8
8
  toggle: () => void;
9
+ /** True while this node's lazy children are being fetched via loadChildren. */
10
+ loading: boolean;
11
+ /** The rejection value from loadChildren, or null when there was no error. */
12
+ error: unknown;
9
13
  };
10
14
  export type TreeNodeProps<T> = {
11
15
  node: T;
@@ -21,6 +25,20 @@ export type TreeNodeProps<T> = {
21
25
  rowClass?: (node: T, selected: boolean) => string;
22
26
  indentPx?: number;
23
27
  basePaddingPx?: number;
28
+ /**
29
+ * Reports that a node has children that are not yet loaded — it renders an
30
+ * expand chevron even when `getChildren` returns nothing. On the first open
31
+ * the node fetches them via `loadChildren`. Without this (the default), a
32
+ * node is expandable only when `getChildren` already returns children.
33
+ */
34
+ hasMoreChildren?: (node: T) => boolean;
35
+ /**
36
+ * Lazily fetch a node's children the first time it is opened. Resolves to the
37
+ * child nodes (which may themselves be lazy). A rejection is surfaced to
38
+ * `renderRow` via `ctx.error` and is not retried until the node is collapsed
39
+ * and reopened. Required for any node `hasMoreChildren` returns true for.
40
+ */
41
+ loadChildren?: (node: T) => Promise<T[]>;
24
42
  /**
25
43
  * Marks a node as a **secondary child** of its parent. Secondary
26
44
  * children keep the full tree structure (they render, they can be
@@ -39,5 +57,5 @@ export type TreeNodeProps<T> = {
39
57
  */
40
58
  isSecondary?: (node: T) => boolean;
41
59
  };
42
- export declare function TreeNode<T>({ node, depth, expandAll, forcedOpenKeys, selected, defaultOpen, getChildren, getKey, onSelect, renderRow, rowClass, indentPx, basePaddingPx, isSecondary, }: TreeNodeProps<T>): import("react/jsx-runtime").JSX.Element;
60
+ export declare function TreeNode<T>({ node, depth, expandAll, forcedOpenKeys, selected, defaultOpen, getChildren, getKey, onSelect, renderRow, rowClass, indentPx, basePaddingPx, isSecondary, hasMoreChildren, loadChildren, }: TreeNodeProps<T>): import("react/jsx-runtime").JSX.Element;
43
61
  //# sourceMappingURL=TreeNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TreeNode.d.ts","sourceRoot":"","sources":["../../src/data/TreeNode.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClD,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;CACpC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAC1B,IAAI,EACJ,KAAS,EACT,SAAgB,EAChB,cAAqB,EACrB,QAAe,EACf,WAAW,EACX,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAa,EACb,aAAiB,EACjB,WAAW,GACZ,EAAE,aAAa,CAAC,CAAC,CAAC,2CAmGlB"}
1
+ {"version":3,"file":"TreeNode.d.ts","sourceRoot":"","sources":["../../src/data/TreeNode.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClD,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACzC;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;CACpC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAC1B,IAAI,EACJ,KAAS,EACT,SAAgB,EAChB,cAAqB,EACrB,QAAe,EACf,WAAW,EACX,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAa,EACb,aAAiB,EACjB,WAAW,EACX,eAAe,EACf,YAAY,GACb,EAAE,aAAa,CAAC,CAAC,CAAC,2CAoJlB"}
@@ -18,10 +18,18 @@ function TreeNode({
18
18
  rowClass,
19
19
  indentPx = 16,
20
20
  basePaddingPx = 8,
21
- isSecondary
21
+ isSecondary,
22
+ hasMoreChildren,
23
+ loadChildren
22
24
  }) {
23
- const children = getChildren(node);
25
+ const [loadedChildren, setLoadedChildren] = useState(null);
26
+ const [loading, setLoading] = useState(false);
27
+ const [error, setError] = useState(null);
28
+ const syncChildren = getChildren(node);
29
+ const children = loadedChildren ?? syncChildren;
24
30
  const hasChildren = ((children == null ? void 0 : children.length) ?? 0) > 0;
31
+ const lazyUnloaded = ((hasMoreChildren == null ? void 0 : hasMoreChildren(node)) ?? false) && loadedChildren === null;
32
+ const expandable = hasChildren || lazyUnloaded;
25
33
  const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;
26
34
  const [open, setOpen] = useState(initialOpen);
27
35
  const prevExpandAll = useRef(expandAll);
@@ -38,8 +46,33 @@ function TreeNode({
38
46
  }
39
47
  prevExpandAll.current = expandAll;
40
48
  }, [expandAll, skipExpandAll]);
49
+ const loadRequested = useRef(false);
50
+ const nodeKey = getKey(node);
51
+ const loadKeyRef = useRef(nodeKey);
52
+ function loadLazyChildren() {
53
+ if (!loadChildren || loadRequested.current) return;
54
+ loadRequested.current = true;
55
+ loadKeyRef.current = nodeKey;
56
+ setLoading(true);
57
+ setError(null);
58
+ loadChildren(node).then((kids) => {
59
+ if (loadKeyRef.current !== nodeKey) return;
60
+ setLoadedChildren(kids);
61
+ setLoading(false);
62
+ }).catch((err) => {
63
+ if (loadKeyRef.current !== nodeKey) return;
64
+ loadRequested.current = false;
65
+ setError(err);
66
+ setLoading(false);
67
+ });
68
+ }
41
69
  function toggle() {
42
- if (hasChildren) setOpen((o) => !o);
70
+ if (!expandable) return;
71
+ setOpen((o) => {
72
+ const next = !o;
73
+ if (next && lazyUnloaded) loadLazyChildren();
74
+ return next;
75
+ });
43
76
  }
44
77
  const defaultRowBg = isSelected ? "bg-primary/10 border-l-2 border-primary" : "hover:bg-accent";
45
78
  const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;
@@ -47,7 +80,7 @@ function TreeNode({
47
80
  "div",
48
81
  {
49
82
  role: "treeitem",
50
- "aria-expanded": hasChildren ? isOpen : void 0,
83
+ "aria-expanded": expandable ? isOpen : void 0,
51
84
  "aria-selected": isSelected,
52
85
  children: [
53
86
  /* @__PURE__ */ jsxs(
@@ -61,7 +94,7 @@ function TreeNode({
61
94
  toggle();
62
95
  },
63
96
  children: [
64
- hasChildren ? /* @__PURE__ */ jsx(
97
+ expandable ? /* @__PURE__ */ jsx(
65
98
  Icon,
66
99
  {
67
100
  icon: isOpen ? UiChevronDown : UiChevronRight,
@@ -74,7 +107,9 @@ function TreeNode({
74
107
  open: isOpen,
75
108
  selected: isSelected,
76
109
  hasChildren,
77
- toggle
110
+ toggle,
111
+ loading,
112
+ error
78
113
  })
79
114
  ]
80
115
  }
@@ -95,7 +130,9 @@ function TreeNode({
95
130
  ...defaultOpen ? { defaultOpen } : {},
96
131
  ...onSelect ? { onSelect } : {},
97
132
  ...rowClass ? { rowClass } : {},
98
- ...isSecondary ? { isSecondary } : {}
133
+ ...isSecondary ? { isSecondary } : {},
134
+ ...hasMoreChildren ? { hasMoreChildren } : {},
135
+ ...loadChildren ? { loadChildren } : {}
99
136
  },
100
137
  getKey(child)
101
138
  )) })
@@ -1 +1 @@
1
- {"version":3,"file":"TreeNode.js","sources":["../../src/data/TreeNode.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiChevronDown, UiChevronRight } from \"../icons\";\n\nexport type TreeRowContext<T> = {\n node: T;\n depth: number;\n open: boolean;\n selected: boolean;\n hasChildren: boolean;\n toggle: () => void;\n};\n\nexport type TreeNodeProps<T> = {\n node: T;\n depth?: number;\n expandAll?: boolean | null;\n forcedOpenKeys?: Set<string | number> | null;\n selected?: T | null;\n defaultOpen?: (node: T, depth: number) => boolean;\n getChildren: (node: T) => T[] | undefined;\n getKey: (node: T) => string | number;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n rowClass?: (node: T, selected: boolean) => string;\n indentPx?: number;\n basePaddingPx?: number;\n /**\n * Marks a node as a **secondary child** of its parent. Secondary\n * children keep the full tree structure (they render, they can be\n * opened manually) but opt out of the bulk behaviours a caller\n * typically wants when they click the toolbar:\n *\n * - Tree-wide filter: their text is skipped during match detection\n * so typing in the search box never surfaces a row because one\n * of its secondary children happened to match.\n * - Expand all: secondary nodes stay at their default-open state\n * instead of being forced open, preventing 28k field rows from\n * flooding the viewport when an operator hits the button on a\n * schema with hundreds of tables.\n *\n * Default: every node is primary (returns false).\n */\n isSecondary?: (node: T) => boolean;\n};\n\nexport function TreeNode<T>({\n node,\n depth = 0,\n expandAll = null,\n forcedOpenKeys = null,\n selected = null,\n defaultOpen,\n getChildren,\n getKey,\n onSelect,\n renderRow,\n rowClass,\n indentPx = 16,\n basePaddingPx = 8,\n isSecondary,\n}: TreeNodeProps<T>) {\n const children = getChildren(node);\n const hasChildren = (children?.length ?? 0) > 0;\n const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;\n const [open, setOpen] = useState(initialOpen);\n const prevExpandAll = useRef(expandAll);\n const isSelected = selected === node;\n const key = getKey(node);\n const isForcedOpen = forcedOpenKeys?.has(key) ?? false;\n const isOpen = isForcedOpen || open;\n // A node opts out of bulk expand-all in two distinct cases:\n //\n // 1. The node itself is secondary (its edge from the parent is\n // secondary) — rare at the root, but the rule is symmetric.\n // 2. All of the node's children are secondary — the common case.\n // This is what keeps an \"Expand all\" on a schema tree from\n // flooding the viewport with every table's field rows: the\n // table opens to nothing structural worth seeing in bulk, so\n // we leave it closed until the operator clicks it manually.\n //\n // In both cases the manual toggle still works, and filter matches\n // still force the row open via forcedOpenKeys, so users never\n // lose access — they just don't get auto-flooded.\n const secondaryNode = isSecondary?.(node) ?? false;\n const childrenAllSecondary =\n hasChildren && isSecondary != null && children!.every((c) => isSecondary(c));\n const skipExpandAll = secondaryNode || childrenAllSecondary;\n\n useEffect(() => {\n if (expandAll !== null && expandAll !== prevExpandAll.current && !skipExpandAll) {\n setOpen(expandAll);\n }\n prevExpandAll.current = expandAll;\n }, [expandAll, skipExpandAll]);\n\n function toggle() {\n if (hasChildren) setOpen((o) => !o);\n }\n\n const defaultRowBg = isSelected ? \"bg-primary/10 border-l-2 border-primary\" : \"hover:bg-accent\";\n const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;\n\n return (\n <div\n role=\"treeitem\"\n aria-expanded={hasChildren ? isOpen : undefined}\n aria-selected={isSelected}\n >\n <div\n className={cn(\"flex items-center gap-1.5 py-1 px-2 cursor-pointer text-sm\", rowClassName)}\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx}px` }}\n onClick={(e) => {\n e.stopPropagation();\n onSelect?.(node);\n toggle();\n }}\n >\n {hasChildren ? (\n <Icon\n icon={isOpen ? UiChevronDown : UiChevronRight}\n className=\"text-muted-foreground text-xs w-3\"\n />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n {renderRow({\n node,\n depth,\n open: isOpen,\n selected: isSelected,\n hasChildren,\n toggle,\n })}\n </div>\n {isOpen && hasChildren && (\n <div role=\"group\">\n {children!.map((child) => (\n <TreeNode\n key={getKey(child)}\n node={child}\n depth={depth + 1}\n expandAll={expandAll}\n forcedOpenKeys={forcedOpenKeys}\n selected={selected}\n getChildren={getChildren}\n getKey={getKey}\n renderRow={renderRow}\n indentPx={indentPx}\n basePaddingPx={basePaddingPx}\n {...(defaultOpen ? { defaultOpen } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AA+CO,SAAS,SAAY;AAAA,EAC1B;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB;AACF,GAAqB;AACnB,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,gBAAe,qCAAU,WAAU,KAAK;AAC9C,QAAM,cAAc,cAAc,YAAY,MAAM,KAAK,IAAI,QAAQ;AACrE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,WAAW;AAC5C,QAAM,gBAAgB,OAAO,SAAS;AACtC,QAAM,aAAa,aAAa;AAChC,QAAM,MAAM,OAAO,IAAI;AACvB,QAAM,gBAAe,iDAAgB,IAAI,SAAQ;AACjD,QAAM,SAAS,gBAAgB;AAc/B,QAAM,iBAAgB,2CAAc,UAAS;AAC7C,QAAM,uBACJ,eAAe,eAAe,QAAQ,SAAU,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC;AAC7E,QAAM,gBAAgB,iBAAiB;AAEvC,YAAU,MAAM;AACd,QAAI,cAAc,QAAQ,cAAc,cAAc,WAAW,CAAC,eAAe;AAC/E,cAAQ,SAAS;AAAA,IACnB;AACA,kBAAc,UAAU;AAAA,EAC1B,GAAG,CAAC,WAAW,aAAa,CAAC;AAE7B,WAAS,SAAS;AAChB,QAAI,YAAa,SAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,EACpC;AAEA,QAAM,eAAe,aAAa,4CAA4C;AAC9E,QAAM,eAAe,WAAW,SAAS,MAAM,UAAU,IAAI;AAE7D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,iBAAe,cAAc,SAAS;AAAA,MACtC,iBAAe;AAAA,MAEf,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,GAAG,8DAA8D,YAAY;AAAA,YACxF,OAAO,EAAE,aAAa,GAAG,QAAQ,WAAW,aAAa,KAAA;AAAA,YACzD,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAA;AACF,mDAAW;AACX,qBAAA;AAAA,YACF;AAAA,YAEC,UAAA;AAAA,cAAA,cACC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAM,SAAS,gBAAgB;AAAA,kBAC/B,WAAU;AAAA,gBAAA;AAAA,cAAA,IAGZ,oBAAC,QAAA,EAAK,WAAU,gBAAe,eAAW,MAAC;AAAA,cAE5C,UAAU;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA,MAAM;AAAA,gBACN,UAAU;AAAA,gBACV;AAAA,gBACA;AAAA,cAAA,CACD;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,UAAU,eACT,oBAAC,OAAA,EAAI,MAAK,SACP,UAAA,SAAU,IAAI,CAAC,UACd;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAM;AAAA,YACN,OAAO,QAAQ;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACC,GAAI,cAAc,EAAE,YAAA,IAAgB,CAAA;AAAA,YACpC,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,YAC9B,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,YAC9B,GAAI,cAAc,EAAE,gBAAgB,CAAA;AAAA,UAAC;AAAA,UAdjC,OAAO,KAAK;AAAA,QAAA,CAgBpB,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;"}
1
+ {"version":3,"file":"TreeNode.js","sources":["../../src/data/TreeNode.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiChevronDown, UiChevronRight } from \"../icons\";\n\nexport type TreeRowContext<T> = {\n node: T;\n depth: number;\n open: boolean;\n selected: boolean;\n hasChildren: boolean;\n toggle: () => void;\n /** True while this node's lazy children are being fetched via loadChildren. */\n loading: boolean;\n /** The rejection value from loadChildren, or null when there was no error. */\n error: unknown;\n};\n\nexport type TreeNodeProps<T> = {\n node: T;\n depth?: number;\n expandAll?: boolean | null;\n forcedOpenKeys?: Set<string | number> | null;\n selected?: T | null;\n defaultOpen?: (node: T, depth: number) => boolean;\n getChildren: (node: T) => T[] | undefined;\n getKey: (node: T) => string | number;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n rowClass?: (node: T, selected: boolean) => string;\n indentPx?: number;\n basePaddingPx?: number;\n /**\n * Reports that a node has children that are not yet loaded — it renders an\n * expand chevron even when `getChildren` returns nothing. On the first open\n * the node fetches them via `loadChildren`. Without this (the default), a\n * node is expandable only when `getChildren` already returns children.\n */\n hasMoreChildren?: (node: T) => boolean;\n /**\n * Lazily fetch a node's children the first time it is opened. Resolves to the\n * child nodes (which may themselves be lazy). A rejection is surfaced to\n * `renderRow` via `ctx.error` and is not retried until the node is collapsed\n * and reopened. Required for any node `hasMoreChildren` returns true for.\n */\n loadChildren?: (node: T) => Promise<T[]>;\n /**\n * Marks a node as a **secondary child** of its parent. Secondary\n * children keep the full tree structure (they render, they can be\n * opened manually) but opt out of the bulk behaviours a caller\n * typically wants when they click the toolbar:\n *\n * - Tree-wide filter: their text is skipped during match detection\n * so typing in the search box never surfaces a row because one\n * of its secondary children happened to match.\n * - Expand all: secondary nodes stay at their default-open state\n * instead of being forced open, preventing 28k field rows from\n * flooding the viewport when an operator hits the button on a\n * schema with hundreds of tables.\n *\n * Default: every node is primary (returns false).\n */\n isSecondary?: (node: T) => boolean;\n};\n\nexport function TreeNode<T>({\n node,\n depth = 0,\n expandAll = null,\n forcedOpenKeys = null,\n selected = null,\n defaultOpen,\n getChildren,\n getKey,\n onSelect,\n renderRow,\n rowClass,\n indentPx = 16,\n basePaddingPx = 8,\n isSecondary,\n hasMoreChildren,\n loadChildren,\n}: TreeNodeProps<T>) {\n // Lazily-fetched children take precedence over the synchronous ones once a\n // load has resolved; until then `loadedChildren` is null and we fall back to\n // getChildren. A node can be \"lazy\" (hasMoreChildren) with no sync children.\n const [loadedChildren, setLoadedChildren] = useState<T[] | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>(null);\n const syncChildren = getChildren(node);\n const children = loadedChildren ?? syncChildren;\n const hasChildren = (children?.length ?? 0) > 0;\n const lazyUnloaded = (hasMoreChildren?.(node) ?? false) && loadedChildren === null;\n // The chevron shows when there is something to reveal: either real children\n // are present, or the node is lazy and has not been loaded yet.\n const expandable = hasChildren || lazyUnloaded;\n const initialOpen = defaultOpen ? defaultOpen(node, depth) : depth < 1;\n const [open, setOpen] = useState(initialOpen);\n const prevExpandAll = useRef(expandAll);\n const isSelected = selected === node;\n const key = getKey(node);\n const isForcedOpen = forcedOpenKeys?.has(key) ?? false;\n const isOpen = isForcedOpen || open;\n // A node opts out of bulk expand-all in two distinct cases:\n //\n // 1. The node itself is secondary (its edge from the parent is\n // secondary) — rare at the root, but the rule is symmetric.\n // 2. All of the node's children are secondary — the common case.\n // This is what keeps an \"Expand all\" on a schema tree from\n // flooding the viewport with every table's field rows: the\n // table opens to nothing structural worth seeing in bulk, so\n // we leave it closed until the operator clicks it manually.\n //\n // In both cases the manual toggle still works, and filter matches\n // still force the row open via forcedOpenKeys, so users never\n // lose access — they just don't get auto-flooded.\n const secondaryNode = isSecondary?.(node) ?? false;\n const childrenAllSecondary =\n hasChildren && isSecondary != null && children!.every((c) => isSecondary(c));\n const skipExpandAll = secondaryNode || childrenAllSecondary;\n\n useEffect(() => {\n if (expandAll !== null && expandAll !== prevExpandAll.current && !skipExpandAll) {\n setOpen(expandAll);\n }\n prevExpandAll.current = expandAll;\n }, [expandAll, skipExpandAll]);\n\n // Guards a lazy fetch so a node loads its children at most once per mount and\n // a resolution arriving after the node was reused (key changed) is ignored.\n const loadRequested = useRef(false);\n const nodeKey = getKey(node);\n const loadKeyRef = useRef(nodeKey);\n\n function loadLazyChildren() {\n if (!loadChildren || loadRequested.current) return;\n loadRequested.current = true;\n loadKeyRef.current = nodeKey;\n setLoading(true);\n setError(null);\n loadChildren(node)\n .then((kids) => {\n if (loadKeyRef.current !== nodeKey) return;\n setLoadedChildren(kids);\n setLoading(false);\n })\n .catch((err) => {\n if (loadKeyRef.current !== nodeKey) return;\n // Surfaced to renderRow via ctx.error; allow a retry on re-toggle.\n loadRequested.current = false;\n setError(err);\n setLoading(false);\n });\n }\n\n function toggle() {\n if (!expandable) return;\n setOpen((o) => {\n const next = !o;\n // Only fetch when transitioning into the open state, so collapsing a\n // not-yet-loaded (e.g. previously-failed) lazy node never triggers a load.\n if (next && lazyUnloaded) loadLazyChildren();\n return next;\n });\n }\n\n const defaultRowBg = isSelected ? \"bg-primary/10 border-l-2 border-primary\" : \"hover:bg-accent\";\n const rowClassName = rowClass ? rowClass(node, isSelected) : defaultRowBg;\n\n return (\n <div\n role=\"treeitem\"\n aria-expanded={expandable ? isOpen : undefined}\n aria-selected={isSelected}\n >\n <div\n className={cn(\"flex items-center gap-1.5 py-1 px-2 cursor-pointer text-sm\", rowClassName)}\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx}px` }}\n onClick={(e) => {\n e.stopPropagation();\n onSelect?.(node);\n toggle();\n }}\n >\n {expandable ? (\n <Icon\n icon={isOpen ? UiChevronDown : UiChevronRight}\n className=\"text-muted-foreground text-xs w-3\"\n />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n {renderRow({\n node,\n depth,\n open: isOpen,\n selected: isSelected,\n hasChildren,\n toggle,\n loading,\n error,\n })}\n </div>\n {isOpen && hasChildren && (\n <div role=\"group\">\n {children!.map((child) => (\n <TreeNode\n key={getKey(child)}\n node={child}\n depth={depth + 1}\n expandAll={expandAll}\n forcedOpenKeys={forcedOpenKeys}\n selected={selected}\n getChildren={getChildren}\n getKey={getKey}\n renderRow={renderRow}\n indentPx={indentPx}\n basePaddingPx={basePaddingPx}\n {...(defaultOpen ? { defaultOpen } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -9,10 +9,11 @@ const GoroutineCard = require("./GoroutineCard.cjs");
9
9
  const ThreadCard = require("./ThreadCard.cjs");
10
10
  const UiServerProcess = require("../../icons/components/UiServerProcess.cjs");
11
11
  const UiDebug = require("../../icons/components/UiDebug.cjs");
12
- const UiSearch = require("../../icons/components/UiSearch.cjs");
13
12
  const UiLoader = require("../../icons/components/UiLoader.cjs");
14
13
  const UiDebugSmall = require("../../icons/components/UiDebugSmall.cjs");
15
14
  const UiRefresh = require("../../icons/components/UiRefresh.cjs");
15
+ const format = require("../../lib/format.cjs");
16
+ const UiSearch = require("../../icons/components/UiSearch.cjs");
16
17
  const STACK_MIN_LINES = 10;
17
18
  const STACK_LINE_HEIGHT_REM = 1;
18
19
  const STACK_MIN_HEIGHT = `${STACK_MIN_LINES * STACK_LINE_HEIGHT_REM}rem`;
@@ -33,7 +34,10 @@ function DiagnosticsDetailPanel({
33
34
  process,
34
35
  collectBusy,
35
36
  onCollectStack,
36
- runMeta
37
+ runMeta,
38
+ resolveSource,
39
+ headerActions,
40
+ singleItem
37
41
  }) {
38
42
  const [search, setSearch] = react.useState("");
39
43
  const [selectedStates, setSelectedStates] = react.useState(/* @__PURE__ */ new Set());
@@ -44,13 +48,14 @@ function DiagnosticsDetailPanel({
44
48
  const filtered = react.useMemo(() => {
45
49
  const needle = search.trim().toLowerCase();
46
50
  const items = parsed.format === "jvm" ? parsed.threads : parsed.format === "go" ? parsed.goroutines : [];
51
+ if (singleItem) return items;
47
52
  return items.filter((item) => {
48
53
  if (selectedStates.size > 0 && !selectedStates.has(item.state)) return false;
49
54
  if (hideRuntimeOnly && item.userFrameCount === 0) return false;
50
55
  if (needle && !item.searchText.includes(needle)) return false;
51
56
  return true;
52
57
  });
53
- }, [parsed, search, selectedStates, hideRuntimeOnly]);
58
+ }, [parsed, search, selectedStates, hideRuntimeOnly, singleItem]);
54
59
  react.useEffect(() => {
55
60
  setSearch("");
56
61
  setSelectedStates(/* @__PURE__ */ new Set());
@@ -63,7 +68,16 @@ function DiagnosticsDetailPanel({
63
68
  ] }) });
64
69
  }
65
70
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full min-h-0 flex flex-col gap-density-3 p-density-4", children: [
66
- /* @__PURE__ */ jsxRuntime.jsx(Header, { process }),
71
+ /* @__PURE__ */ jsxRuntime.jsx(
72
+ Header,
73
+ {
74
+ process,
75
+ actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-density-2", children: [
76
+ singleItem && onCollectStack && /* @__PURE__ */ jsxRuntime.jsx(CollectButton, { pid: process.pid, busy: collectBusy ?? false, onClick: onCollectStack }),
77
+ headerActions
78
+ ] })
79
+ }
80
+ ),
67
81
  runMeta && /* @__PURE__ */ jsxRuntime.jsx(RunSection, { runMeta }),
68
82
  process.command && /* @__PURE__ */ jsxRuntime.jsx(PanelSection, { title: "Command", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-foreground whitespace-pre-wrap font-mono bg-blue-50 dark:bg-blue-500/10 rounded p-2 break-all", children: process.command }) }),
69
83
  /* @__PURE__ */ jsxRuntime.jsx(PanelSection, { title: "Metrics", children: /* @__PURE__ */ jsxRuntime.jsx(ProcessMetrics, { process }) }),
@@ -81,46 +95,51 @@ function DiagnosticsDetailPanel({
81
95
  hideRuntimeOnly,
82
96
  setHideRuntimeOnly,
83
97
  collectBusy: collectBusy ?? false,
84
- ...onCollectStack ? { onCollectStack } : {}
98
+ singleItem: singleItem ?? false,
99
+ ...onCollectStack ? { onCollectStack } : {},
100
+ ...resolveSource ? { resolveSource } : {}
85
101
  }
86
102
  ) })
87
103
  ] });
88
104
  }
89
- function Header({ process }) {
90
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between gap-density-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
91
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-density-2", children: [
92
- /* @__PURE__ */ jsxRuntime.jsx(
93
- Icon.Icon,
94
- {
95
- icon: process.is_root ? UiServerProcess.UiServerProcess : UiDebug.UiDebug,
96
- className: "text-2xl text-blue-600"
97
- }
98
- ),
99
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-foreground break-words", children: utils.processLabel(process) })
100
- ] }),
101
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center gap-density-2 flex-wrap text-xs text-muted-foreground", children: [
102
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
103
- "pid ",
104
- process.pid
105
+ function Header({ process, actions }) {
106
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-density-3", children: [
107
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-density-2 flex-wrap", children: [
109
+ /* @__PURE__ */ jsxRuntime.jsx(
110
+ Icon.Icon,
111
+ {
112
+ icon: process.is_root ? UiServerProcess.UiServerProcess : UiDebug.UiDebug,
113
+ className: "text-2xl text-blue-600"
114
+ }
115
+ ),
116
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-foreground break-words", children: utils.processLabel(process) }),
117
+ process.status ? /* @__PURE__ */ jsxRuntime.jsxs(
118
+ "span",
119
+ {
120
+ className: `inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs ${utils.processStateColor(
121
+ process.status
122
+ )}`,
123
+ children: [
124
+ /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: utils.processStateIcon(process.status) }),
125
+ process.status
126
+ ]
127
+ }
128
+ ) : null
105
129
  ] }),
106
- process.ppid ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
107
- "ppid ",
108
- process.ppid
109
- ] }) : null,
110
- process.status ? /* @__PURE__ */ jsxRuntime.jsxs(
111
- "span",
112
- {
113
- className: `inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 ${utils.processStateColor(
114
- process.status
115
- )}`,
116
- children: [
117
- /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: utils.processStateIcon(process.status) }),
118
- process.status
119
- ]
120
- }
121
- ) : null
122
- ] })
123
- ] }) });
130
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center gap-density-2 flex-wrap text-xs text-muted-foreground", children: [
131
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
132
+ "pid ",
133
+ process.pid
134
+ ] }),
135
+ process.ppid ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
136
+ "ppid ",
137
+ process.ppid
138
+ ] }) : null
139
+ ] })
140
+ ] }),
141
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: actions }) : null
142
+ ] });
124
143
  }
125
144
  function RunSection({ runMeta }) {
126
145
  return /* @__PURE__ */ jsxRuntime.jsx(PanelSection, { title: "Run", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-density-2 text-sm", children: [
@@ -166,7 +185,9 @@ function StackBlock(props) {
166
185
  hideRuntimeOnly,
167
186
  setHideRuntimeOnly,
168
187
  collectBusy,
169
- onCollectStack
188
+ onCollectStack,
189
+ resolveSource,
190
+ singleItem
170
191
  } = props;
171
192
  const stack = process.stack_capture;
172
193
  if (!(stack == null ? void 0 : stack.text)) {
@@ -206,7 +227,7 @@ function StackBlock(props) {
206
227
  ] });
207
228
  }
208
229
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full min-h-0 flex flex-col", children: [
209
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1.5 space-y-density-2", children: [
230
+ !singleItem && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1.5 space-y-density-2", children: [
210
231
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-density-2 text-[11px] text-muted-foreground flex-wrap", children: [
211
232
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-density-2 flex-wrap", children: [
212
233
  /* @__PURE__ */ jsxRuntime.jsx(StackStatusBadge, { status: stack.status }),
@@ -314,7 +335,8 @@ function StackBlock(props) {
314
335
  {
315
336
  thread: t,
316
337
  search,
317
- hideRuntimeOnly
338
+ hideRuntimeOnly,
339
+ ...resolveSource ? { resolveSource } : {}
318
340
  },
319
341
  t.id
320
342
  )),
@@ -393,12 +415,12 @@ function ProcessMetrics({ process }) {
393
415
  MetricTile,
394
416
  {
395
417
  label: "RSS",
396
- value: rss !== void 0 ? utils.formatBytes(rss) : "n/a",
418
+ value: rss !== void 0 ? format.formatBytes(rss) : "n/a",
397
419
  barPct: memRatioPct,
398
420
  tone: memoryTone(rss, vms)
399
421
  }
400
422
  ),
401
- /* @__PURE__ */ jsxRuntime.jsx(MetricTile, { label: "VMS", value: vms !== void 0 ? utils.formatBytes(vms) : "n/a", tone: "neutral" }),
423
+ /* @__PURE__ */ jsxRuntime.jsx(MetricTile, { label: "VMS", value: vms !== void 0 ? format.formatBytes(vms) : "n/a", tone: "neutral" }),
402
424
  /* @__PURE__ */ jsxRuntime.jsx(
403
425
  MetricTile,
404
426
  {