@flanksource/clicky-ui 0.3.45 → 0.3.46

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 (140) hide show
  1. package/README.md +22 -0
  2. package/dist/data/AccessMark.cjs +23 -0
  3. package/dist/data/AccessMark.cjs.map +1 -0
  4. package/dist/data/AccessMark.d.ts +11 -0
  5. package/dist/data/AccessMark.d.ts.map +1 -0
  6. package/dist/data/AccessMark.js +23 -0
  7. package/dist/data/AccessMark.js.map +1 -0
  8. package/dist/data/CodeDiff.cjs +116 -23
  9. package/dist/data/CodeDiff.cjs.map +1 -1
  10. package/dist/data/CodeDiff.d.ts +26 -0
  11. package/dist/data/CodeDiff.d.ts.map +1 -1
  12. package/dist/data/CodeDiff.js +116 -23
  13. package/dist/data/CodeDiff.js.map +1 -1
  14. package/dist/data/GraphDiagram.cjs +65 -13
  15. package/dist/data/GraphDiagram.cjs.map +1 -1
  16. package/dist/data/GraphDiagram.d.ts +9 -2
  17. package/dist/data/GraphDiagram.d.ts.map +1 -1
  18. package/dist/data/GraphDiagram.js +66 -14
  19. package/dist/data/GraphDiagram.js.map +1 -1
  20. package/dist/data/GraphDiagramGroups.cjs +100 -0
  21. package/dist/data/GraphDiagramGroups.cjs.map +1 -0
  22. package/dist/data/GraphDiagramGroups.d.ts +24 -0
  23. package/dist/data/GraphDiagramGroups.d.ts.map +1 -0
  24. package/dist/data/GraphDiagramGroups.js +100 -0
  25. package/dist/data/GraphDiagramGroups.js.map +1 -0
  26. package/dist/data/GraphDiagramNodes.cjs +17 -31
  27. package/dist/data/GraphDiagramNodes.cjs.map +1 -1
  28. package/dist/data/GraphDiagramNodes.d.ts +6 -13
  29. package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
  30. package/dist/data/GraphDiagramNodes.js +17 -31
  31. package/dist/data/GraphDiagramNodes.js.map +1 -1
  32. package/dist/data/TreeNode.cjs +1 -1
  33. package/dist/data/TreeNode.cjs.map +1 -1
  34. package/dist/data/TreeNode.d.ts.map +1 -1
  35. package/dist/data/TreeNode.js +1 -1
  36. package/dist/data/TreeNode.js.map +1 -1
  37. package/dist/data/call-graph/CallGraph.cjs +50 -31
  38. package/dist/data/call-graph/CallGraph.cjs.map +1 -1
  39. package/dist/data/call-graph/CallGraph.d.ts +10 -1
  40. package/dist/data/call-graph/CallGraph.d.ts.map +1 -1
  41. package/dist/data/call-graph/CallGraph.js +52 -33
  42. package/dist/data/call-graph/CallGraph.js.map +1 -1
  43. package/dist/data/call-graph/CallGraphMembers.cjs +5 -4
  44. package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -1
  45. package/dist/data/call-graph/CallGraphMembers.d.ts +1 -1
  46. package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -1
  47. package/dist/data/call-graph/CallGraphMembers.js +5 -4
  48. package/dist/data/call-graph/CallGraphMembers.js.map +1 -1
  49. package/dist/data/call-graph/CallGraphToolbar.cjs +17 -5
  50. package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -1
  51. package/dist/data/call-graph/CallGraphToolbar.d.ts +10 -0
  52. package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -1
  53. package/dist/data/call-graph/CallGraphToolbar.js +18 -6
  54. package/dist/data/call-graph/CallGraphToolbar.js.map +1 -1
  55. package/dist/data/call-graph/call-graph-model.cjs +169 -18
  56. package/dist/data/call-graph/call-graph-model.cjs.map +1 -1
  57. package/dist/data/call-graph/call-graph-model.d.ts +40 -4
  58. package/dist/data/call-graph/call-graph-model.d.ts.map +1 -1
  59. package/dist/data/call-graph/call-graph-model.js +170 -19
  60. package/dist/data/call-graph/call-graph-model.js.map +1 -1
  61. package/dist/data/call-graph/call-graph-vocabulary.cjs +19 -1
  62. package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -1
  63. package/dist/data/call-graph/call-graph-vocabulary.d.ts +4 -0
  64. package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -1
  65. package/dist/data/call-graph/call-graph-vocabulary.js +19 -1
  66. package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -1
  67. package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -1
  68. package/dist/data/call-graph/index.d.ts +2 -2
  69. package/dist/data/call-graph/index.d.ts.map +1 -1
  70. package/dist/data/call-graph/types.cjs.map +1 -1
  71. package/dist/data/call-graph/types.d.ts +7 -2
  72. package/dist/data/call-graph/types.d.ts.map +1 -1
  73. package/dist/data/call-graph/types.js.map +1 -1
  74. package/dist/data/data-access.cjs +8 -0
  75. package/dist/data/data-access.cjs.map +1 -0
  76. package/dist/data/data-access.d.ts +5 -0
  77. package/dist/data/data-access.d.ts.map +1 -0
  78. package/dist/data/data-access.js +8 -0
  79. package/dist/data/data-access.js.map +1 -0
  80. package/dist/data/git/GitChangeList.cjs +6 -2
  81. package/dist/data/git/GitChangeList.cjs.map +1 -1
  82. package/dist/data/git/GitChangeList.d.ts +6 -1
  83. package/dist/data/git/GitChangeList.d.ts.map +1 -1
  84. package/dist/data/git/GitChangeList.js +6 -2
  85. package/dist/data/git/GitChangeList.js.map +1 -1
  86. package/dist/data/graph-columns-layout.cjs +47 -16
  87. package/dist/data/graph-columns-layout.cjs.map +1 -1
  88. package/dist/data/graph-columns-layout.d.ts +34 -0
  89. package/dist/data/graph-columns-layout.d.ts.map +1 -1
  90. package/dist/data/graph-columns-layout.js +47 -16
  91. package/dist/data/graph-columns-layout.js.map +1 -1
  92. package/dist/data/graph-diagram-model.cjs +5 -0
  93. package/dist/data/graph-diagram-model.cjs.map +1 -1
  94. package/dist/data/graph-diagram-model.d.ts +19 -0
  95. package/dist/data/graph-diagram-model.d.ts.map +1 -1
  96. package/dist/data/graph-diagram-model.js +5 -0
  97. package/dist/data/graph-diagram-model.js.map +1 -1
  98. package/dist/data/graph-group-window.cjs +60 -0
  99. package/dist/data/graph-group-window.cjs.map +1 -0
  100. package/dist/data/graph-group-window.d.ts +33 -0
  101. package/dist/data/graph-group-window.d.ts.map +1 -0
  102. package/dist/data/graph-group-window.js +60 -0
  103. package/dist/data/graph-group-window.js.map +1 -0
  104. package/dist/data/use-group-windows.cjs +50 -0
  105. package/dist/data/use-group-windows.cjs.map +1 -0
  106. package/dist/data/use-group-windows.d.ts +27 -0
  107. package/dist/data/use-group-windows.d.ts.map +1 -0
  108. package/dist/data/use-group-windows.js +50 -0
  109. package/dist/data/use-group-windows.js.map +1 -0
  110. package/dist/data/version-info.cjs +3 -3
  111. package/dist/data/version-info.js +3 -3
  112. package/dist/data.cjs +10 -0
  113. package/dist/data.cjs.map +1 -1
  114. package/dist/data.d.ts +4 -2
  115. package/dist/data.d.ts.map +1 -1
  116. package/dist/data.js +13 -3
  117. package/dist/data.js.map +1 -1
  118. package/dist/hooks/event-hub.cjs +229 -0
  119. package/dist/hooks/event-hub.cjs.map +1 -0
  120. package/dist/hooks/event-hub.d.ts +26 -0
  121. package/dist/hooks/event-hub.d.ts.map +1 -0
  122. package/dist/hooks/event-hub.js +229 -0
  123. package/dist/hooks/event-hub.js.map +1 -0
  124. package/dist/hooks.cjs +2 -0
  125. package/dist/hooks.cjs.map +1 -1
  126. package/dist/hooks.d.ts +1 -0
  127. package/dist/hooks.d.ts.map +1 -1
  128. package/dist/hooks.js +2 -0
  129. package/dist/hooks.js.map +1 -1
  130. package/dist/index.cjs +12 -0
  131. package/dist/index.cjs.map +1 -1
  132. package/dist/index.js +15 -3
  133. package/dist/index.js.map +1 -1
  134. package/dist/overlay/modalStack.cjs +13 -6
  135. package/dist/overlay/modalStack.cjs.map +1 -1
  136. package/dist/overlay/modalStack.d.ts.map +1 -1
  137. package/dist/overlay/modalStack.js +13 -6
  138. package/dist/overlay/modalStack.js.map +1 -1
  139. package/dist/styles.css +1 -1
  140. package/package.json +1 -1
@@ -39,7 +39,7 @@ function NodeLabel({ node, oneLine }) {
39
39
  }
40
40
  );
41
41
  if (node.icon == null) return label;
42
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex max-w-full items-center gap-1.5", children: [
42
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 max-w-full items-center gap-1.5", children: [
43
43
  /* @__PURE__ */ jsxRuntime.jsx("span", { "data-graph-node-icon": "", className: "flex shrink-0 text-base leading-none", children: node.icon }),
44
44
  label
45
45
  ] });
@@ -48,6 +48,7 @@ function NodeBody({
48
48
  node,
49
49
  oneLine,
50
50
  compact,
51
+ row,
51
52
  selected,
52
53
  onSelect
53
54
  }) {
@@ -56,13 +57,14 @@ function NodeBody({
56
57
  "div",
57
58
  {
58
59
  className: utils.cn(
59
- "flex h-full w-full border text-xs shadow-sm",
60
+ "flex h-full w-full text-xs",
61
+ row ? "border-t border-border/60 hover:bg-accent" : "border shadow-sm",
60
62
  compact ? "items-center px-2 text-left text-[11px]" : "flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center",
61
- node.shape === "pill" ? "rounded-full" : compact ? "rounded-md" : "rounded-lg",
62
- graphDiagramModel.TONE_NODE_CLASS[tone],
63
- node.muted && "border-dashed text-muted-foreground shadow-none",
64
- node.muted && tone === "neutral" && "bg-muted",
65
- selected && "ring-2 ring-primary ring-offset-2 ring-offset-background",
63
+ !row && (node.shape === "pill" ? "rounded-full" : compact ? "rounded-md" : "rounded-lg"),
64
+ row ? tone === "info" ? "bg-sky-500/10 text-foreground" : "text-foreground" : graphDiagramModel.TONE_NODE_CLASS[tone],
65
+ node.muted && (row ? "text-muted-foreground" : "border-dashed text-muted-foreground shadow-none"),
66
+ !row && node.muted && tone === "neutral" && "bg-muted",
67
+ selected && (row ? "bg-primary/10 ring-2 ring-inset ring-primary" : "ring-2 ring-primary ring-offset-2 ring-offset-background"),
66
68
  onSelect && "cursor-pointer"
67
69
  ),
68
70
  ...onSelect ? {
@@ -76,6 +78,9 @@ function NodeBody({
76
78
  title: node.title,
77
79
  children: [
78
80
  /* @__PURE__ */ jsxRuntime.jsx(NodeLabel, { node, oneLine: oneLine || compact }),
81
+ row && node.aside != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto min-w-0 truncate pl-2 font-mono text-[10px] text-muted-foreground", children: node.aside }),
82
+ node.mark != null && // Kept out of the node's accessible name, which stays the node's own; the mark is its tooltip.
83
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-graph-node-mark": "", "aria-hidden": true, className: utils.cn("flex shrink-0 items-center", compact ? row && node.aside != null ? "pl-1" : "ml-auto pl-1" : "absolute right-1 top-1"), children: node.mark }),
79
84
  !compact && node.detail != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-1 text-[10px] leading-tight text-muted-foreground", children: node.detail }),
80
85
  !compact && node.badge != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5", children: node.badge })
81
86
  ]
@@ -85,10 +90,12 @@ function NodeBody({
85
90
  function GraphDiagramNodes({
86
91
  nodes,
87
92
  positions,
93
+ windowOf,
88
94
  place,
89
95
  nodeWidth,
90
96
  nodeHeight,
91
97
  nodeHeights,
98
+ recordNodes,
92
99
  selectedId,
93
100
  onNodeSelect,
94
101
  onNodeExpand,
@@ -97,11 +104,13 @@ function GraphDiagramNodes({
97
104
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node) => {
98
105
  const height = nodeHeights[node.id] ?? nodeHeight;
99
106
  const compact = node.id in nodeHeights;
107
+ const windowBox = windowOf.get(node.id);
100
108
  return /* @__PURE__ */ jsxRuntime.jsxs(
101
109
  "div",
102
110
  {
103
111
  "data-graph-node": node.id,
104
112
  "data-graph-node-size": compact ? "compact" : "regular",
113
+ ...windowBox !== void 0 ? { "data-graph-window": windowBox } : {},
105
114
  className: "pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2",
106
115
  style: { ...place(graphDiagramModel.requireEntry(positions, node.id, "layout position")), width: nodeWidth, height },
107
116
  ...onNodeHover ? graphDiagramModel.hoverHandlers(node.id, onNodeHover) : {},
@@ -112,6 +121,7 @@ function GraphDiagramNodes({
112
121
  node,
113
122
  oneLine: height < TWO_LINE_HEIGHT,
114
123
  compact,
124
+ row: recordNodes.has(node.id),
115
125
  selected: selectedId != null && node.id === selectedId,
116
126
  onSelect: onNodeSelect ? () => onNodeSelect(node.id) : void 0
117
127
  }
@@ -123,29 +133,5 @@ function GraphDiagramNodes({
123
133
  );
124
134
  }) });
125
135
  }
126
- function GraphDiagramGroupBoxes({ boxes, groups }) {
127
- const captions = new Map(groups == null ? void 0 : groups.map((group) => [group.id, group]));
128
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: boxes.map((box) => {
129
- const caption = captions.get(box.group);
130
- return /* @__PURE__ */ jsxRuntime.jsx(
131
- "div",
132
- {
133
- "data-graph-group": box.id,
134
- className: "pointer-events-none absolute rounded-xl border border-dashed border-border bg-muted/30",
135
- style: { left: box.x, top: box.y, width: box.width, height: box.height },
136
- children: /* @__PURE__ */ jsxRuntime.jsx(
137
- "span",
138
- {
139
- title: caption == null ? void 0 : caption.title,
140
- className: "pointer-events-auto absolute left-2 top-0 max-w-[calc(100%-1rem)] -translate-y-1/2 truncate rounded border border-border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground",
141
- children: caption ? caption.label : box.group
142
- }
143
- )
144
- },
145
- box.id
146
- );
147
- }) });
148
- }
149
- exports.GraphDiagramGroupBoxes = GraphDiagramGroupBoxes;
150
136
  exports.GraphDiagramNodes = GraphDiagramNodes;
151
137
  //# sourceMappingURL=GraphDiagramNodes.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"GraphDiagramNodes.cjs","sources":["../../src/data/GraphDiagramNodes.tsx"],"sourcesContent":["import type { KeyboardEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\nimport type { GraphLayoutGroupBox } from \"./graph-columns-layout\";\nimport {\n activateOnKey,\n hoverHandlers,\n nodeName,\n requireEntry,\n TONE_NODE_CLASS,\n type GraphDiagramGroup,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\n\nexport interface GraphDiagramNodesProps {\n nodes: GraphDiagramNode[];\n positions: Record<string, GraphLayoutPosition>;\n place: GraphPlacer;\n nodeWidth: number;\n nodeHeight: number;\n /** Heights of the nodes not drawn at `nodeHeight`, by id: the compact ones. */\n nodeHeights: Readonly<Record<string, number>>;\n selectedId: string | undefined;\n onNodeSelect: ((id: string) => void) | undefined;\n onNodeExpand: ((id: string) => void) | undefined;\n onNodeHover: ((id: string | undefined) => void) | undefined;\n}\n\nconst EXPAND_CLASS =\n \"absolute bottom-0 translate-y-1/2 rounded-full border border-border bg-background px-1.5 text-[10px] font-semibold leading-4 text-muted-foreground shadow-sm\";\n\n/**\n * The `+N` control. It hangs off the node's bottom edge at its outer end —\n * the left for a node left of level 0, the right otherwise — which keeps it\n * clear of the side middles where edges attach. It is a sibling of the node,\n * not a child, so activating it never also selects the node.\n */\nfunction ExpandControl({ node, onExpand }: { node: GraphDiagramNode; onExpand: (() => void) | undefined }) {\n const side = (node.level ?? 0) < 0 ? \"left-2\" : \"right-2\";\n const text = `+${node.expandCount}`;\n if (!onExpand) {\n return <span className={cn(EXPAND_CLASS, side)}>{text}</span>;\n }\n return (\n <button\n type=\"button\"\n aria-label={`Expand ${node.expandCount} more from ${nodeName(node)}`}\n className={cn(\n EXPAND_CLASS,\n side,\n \"cursor-pointer hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n onClick={onExpand}\n >\n {text}\n </button>\n );\n}\n\n// Two 15px label lines, the body's vertical padding and its border.\nconst TWO_LINE_HEIGHT = 44;\n\n/** The label, behind the node's icon when it has one. `oneLine` cuts it with an ellipsis instead of wrapping. */\nfunction NodeLabel({ node, oneLine }: { node: GraphDiagramNode; oneLine: boolean }) {\n const label = (\n <span\n className={cn(\n oneLine ? \"max-w-full truncate\" : \"line-clamp-2\",\n \"font-medium leading-tight\",\n node.icon != null && \"min-w-0\",\n )}\n >\n {node.label}\n </span>\n );\n if (node.icon == null) return label;\n return (\n <span className=\"flex max-w-full items-center gap-1.5\">\n <span data-graph-node-icon=\"\" className=\"flex shrink-0 text-base leading-none\">\n {node.icon}\n </span>\n {label}\n </span>\n );\n}\n\nfunction NodeBody({\n node,\n oneLine,\n compact,\n selected,\n onSelect,\n}: {\n node: GraphDiagramNode;\n /** The node is too short for two label lines. */\n oneLine: boolean;\n /** Its icon and label on one short line, left-aligned, without its detail or badge. */\n compact: boolean;\n selected: boolean;\n onSelect: (() => void) | undefined;\n}) {\n const tone = node.tone ?? \"neutral\";\n return (\n <div\n className={cn(\n \"flex h-full w-full border text-xs shadow-sm\",\n compact\n ? \"items-center px-2 text-left text-[11px]\"\n : \"flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center\",\n node.shape === \"pill\" ? \"rounded-full\" : compact ? \"rounded-md\" : \"rounded-lg\",\n TONE_NODE_CLASS[tone],\n node.muted && \"border-dashed text-muted-foreground shadow-none\",\n node.muted && tone === \"neutral\" && \"bg-muted\",\n selected && \"ring-2 ring-primary ring-offset-2 ring-offset-background\",\n onSelect && \"cursor-pointer\",\n )}\n {...(onSelect\n ? {\n role: \"button\" as const,\n tabIndex: 0,\n \"aria-pressed\": selected,\n onClick: onSelect,\n onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => activateOnKey(event, onSelect),\n }\n : {})}\n aria-label={node.ariaLabel}\n title={node.title}\n >\n <NodeLabel node={node} oneLine={oneLine || compact} />\n {!compact && node.detail != null && (\n <span className=\"line-clamp-1 text-[10px] leading-tight text-muted-foreground\">{node.detail}</span>\n )}\n {!compact && node.badge != null && <span className=\"mt-0.5\">{node.badge}</span>}\n </div>\n );\n}\n\nexport function GraphDiagramNodes({\n nodes,\n positions,\n place,\n nodeWidth,\n nodeHeight,\n nodeHeights,\n selectedId,\n onNodeSelect,\n onNodeExpand,\n onNodeHover,\n}: GraphDiagramNodesProps) {\n return (\n <>\n {nodes.map((node) => {\n const height = nodeHeights[node.id] ?? nodeHeight;\n const compact = node.id in nodeHeights;\n return (\n <div\n key={node.id}\n data-graph-node={node.id}\n data-graph-node-size={compact ? \"compact\" : \"regular\"}\n className=\"pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2\"\n style={{ ...place(requireEntry(positions, node.id, \"layout position\")), width: nodeWidth, height }}\n {...(onNodeHover ? hoverHandlers(node.id, onNodeHover) : {})}\n >\n <NodeBody\n node={node}\n oneLine={height < TWO_LINE_HEIGHT}\n compact={compact}\n selected={selectedId != null && node.id === selectedId}\n onSelect={onNodeSelect ? () => onNodeSelect(node.id) : undefined}\n />\n {node.expandCount != null && node.expandCount > 0 && (\n <ExpandControl node={node} onExpand={onNodeExpand ? () => onNodeExpand(node.id) : undefined} />\n )}\n </div>\n );\n })}\n </>\n );\n}\n\nexport interface GraphDiagramGroupBoxesProps {\n boxes: GraphLayoutGroupBox[];\n groups: GraphDiagramGroup[] | undefined;\n}\n\n/**\n * Group rectangles, drawn behind the edges and nodes, each captioned on its top border. A box lets the\n * pointer through to the stage, so a click inside it never lands on the box; only its caption takes\n * the pointer, for its tooltip.\n */\nexport function GraphDiagramGroupBoxes({ boxes, groups }: GraphDiagramGroupBoxesProps) {\n const captions = new Map(groups?.map((group) => [group.id, group]));\n return (\n <>\n {boxes.map((box) => {\n const caption = captions.get(box.group);\n return (\n <div\n key={box.id}\n data-graph-group={box.id}\n className=\"pointer-events-none absolute rounded-xl border border-dashed border-border bg-muted/30\"\n style={{ left: box.x, top: box.y, width: box.width, height: box.height }}\n >\n <span\n title={caption?.title}\n className=\"pointer-events-auto absolute left-2 top-0 max-w-[calc(100%-1rem)] -translate-y-1/2 truncate rounded border border-border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground\"\n >\n {caption ? caption.label : box.group}\n </span>\n </div>\n );\n })}\n </>\n );\n}\n"],"names":["cn","jsx","nodeName","jsxs","TONE_NODE_CLASS","activateOnKey","Fragment","requireEntry","hoverHandlers"],"mappings":"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"}
1
+ {"version":3,"file":"GraphDiagramNodes.cjs","sources":["../../src/data/GraphDiagramNodes.tsx"],"sourcesContent":["import type { KeyboardEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\nimport {\n activateOnKey,\n hoverHandlers,\n nodeName,\n requireEntry,\n TONE_NODE_CLASS,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\n\nexport interface GraphDiagramNodesProps {\n /** The nodes to draw: members scrolled out of their window and collapsed groups' stand-ins are left out. */\n nodes: GraphDiagramNode[];\n positions: Record<string, GraphLayoutPosition>;\n /** The windowed group box each scrolling member belongs to, by node id: a wheel over the member scrolls it. */\n windowOf: ReadonlyMap<string, string>;\n place: GraphPlacer;\n nodeWidth: number;\n nodeHeight: number;\n /** Heights of the nodes not drawn at `nodeHeight`, by id: the compact ones. */\n nodeHeights: Readonly<Record<string, number>>;\n recordNodes: ReadonlySet<string>;\n selectedId: string | undefined;\n onNodeSelect: ((id: string) => void) | undefined;\n onNodeExpand: ((id: string) => void) | undefined;\n onNodeHover: ((id: string | undefined) => void) | undefined;\n}\n\nconst EXPAND_CLASS =\n \"absolute bottom-0 translate-y-1/2 rounded-full border border-border bg-background px-1.5 text-[10px] font-semibold leading-4 text-muted-foreground shadow-sm\";\n\n/**\n * The `+N` control. It hangs off the node's bottom edge at its outer end —\n * the left for a node left of level 0, the right otherwise — which keeps it\n * clear of the side middles where edges attach. It is a sibling of the node,\n * not a child, so activating it never also selects the node.\n */\nfunction ExpandControl({ node, onExpand }: { node: GraphDiagramNode; onExpand: (() => void) | undefined }) {\n const side = (node.level ?? 0) < 0 ? \"left-2\" : \"right-2\";\n const text = `+${node.expandCount}`;\n if (!onExpand) {\n return <span className={cn(EXPAND_CLASS, side)}>{text}</span>;\n }\n return (\n <button\n type=\"button\"\n aria-label={`Expand ${node.expandCount} more from ${nodeName(node)}`}\n className={cn(\n EXPAND_CLASS,\n side,\n \"cursor-pointer hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n onClick={onExpand}\n >\n {text}\n </button>\n );\n}\n\n// Two 15px label lines, the body's vertical padding and its border.\nconst TWO_LINE_HEIGHT = 44;\n\n/** The label, behind the node's icon when it has one. `oneLine` cuts it with an ellipsis instead of wrapping. */\nfunction NodeLabel({ node, oneLine }: { node: GraphDiagramNode; oneLine: boolean }) {\n const label = (\n <span\n className={cn(\n oneLine ? \"max-w-full truncate\" : \"line-clamp-2\",\n \"font-medium leading-tight\",\n node.icon != null && \"min-w-0\",\n )}\n >\n {node.label}\n </span>\n );\n if (node.icon == null) return label;\n return (\n <span className=\"flex min-w-0 max-w-full items-center gap-1.5\">\n <span data-graph-node-icon=\"\" className=\"flex shrink-0 text-base leading-none\">\n {node.icon}\n </span>\n {label}\n </span>\n );\n}\n\nfunction NodeBody({\n node,\n oneLine,\n compact,\n row,\n selected,\n onSelect,\n}: {\n node: GraphDiagramNode;\n /** The node is too short for two label lines. */\n oneLine: boolean;\n /** Its icon and label on one short line, left-aligned, without its detail or badge. */\n compact: boolean;\n row: boolean;\n selected: boolean;\n onSelect: (() => void) | undefined;\n}) {\n const tone = node.tone ?? \"neutral\";\n return (\n <div\n className={cn(\n \"flex h-full w-full text-xs\",\n row ? \"border-t border-border/60 hover:bg-accent\" : \"border shadow-sm\",\n compact\n ? \"items-center px-2 text-left text-[11px]\"\n : \"flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center\",\n !row && (node.shape === \"pill\" ? \"rounded-full\" : compact ? \"rounded-md\" : \"rounded-lg\"),\n row ? (tone === \"info\" ? \"bg-sky-500/10 text-foreground\" : \"text-foreground\") : TONE_NODE_CLASS[tone],\n node.muted && (row ? \"text-muted-foreground\" : \"border-dashed text-muted-foreground shadow-none\"),\n !row && node.muted && tone === \"neutral\" && \"bg-muted\",\n selected && (row ? \"bg-primary/10 ring-2 ring-inset ring-primary\" : \"ring-2 ring-primary ring-offset-2 ring-offset-background\"),\n onSelect && \"cursor-pointer\",\n )}\n {...(onSelect\n ? {\n role: \"button\" as const,\n tabIndex: 0,\n \"aria-pressed\": selected,\n onClick: onSelect,\n onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => activateOnKey(event, onSelect),\n }\n : {})}\n aria-label={node.ariaLabel}\n title={node.title}\n >\n <NodeLabel node={node} oneLine={oneLine || compact} />\n {row && node.aside != null && <span className=\"ml-auto min-w-0 truncate pl-2 font-mono text-[10px] text-muted-foreground\">{node.aside}</span>}\n {node.mark != null && (\n // Kept out of the node's accessible name, which stays the node's own; the mark is its tooltip.\n <span data-graph-node-mark=\"\" aria-hidden className={cn(\"flex shrink-0 items-center\", compact ? (row && node.aside != null ? \"pl-1\" : \"ml-auto pl-1\") : \"absolute right-1 top-1\")}>\n {node.mark}\n </span>\n )}\n {!compact && node.detail != null && (\n <span className=\"line-clamp-1 text-[10px] leading-tight text-muted-foreground\">{node.detail}</span>\n )}\n {!compact && node.badge != null && <span className=\"mt-0.5\">{node.badge}</span>}\n </div>\n );\n}\n\nexport function GraphDiagramNodes({\n nodes,\n positions,\n windowOf,\n place,\n nodeWidth,\n nodeHeight,\n nodeHeights,\n recordNodes,\n selectedId,\n onNodeSelect,\n onNodeExpand,\n onNodeHover,\n}: GraphDiagramNodesProps) {\n return (\n <>\n {nodes.map((node) => {\n const height = nodeHeights[node.id] ?? nodeHeight;\n const compact = node.id in nodeHeights;\n const windowBox = windowOf.get(node.id);\n return (\n <div\n key={node.id}\n data-graph-node={node.id}\n data-graph-node-size={compact ? \"compact\" : \"regular\"}\n {...(windowBox !== undefined ? { \"data-graph-window\": windowBox } : {})}\n className=\"pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2\"\n style={{ ...place(requireEntry(positions, node.id, \"layout position\")), width: nodeWidth, height }}\n {...(onNodeHover ? hoverHandlers(node.id, onNodeHover) : {})}\n >\n <NodeBody\n node={node}\n oneLine={height < TWO_LINE_HEIGHT}\n compact={compact}\n row={recordNodes.has(node.id)}\n selected={selectedId != null && node.id === selectedId}\n onSelect={onNodeSelect ? () => onNodeSelect(node.id) : undefined}\n />\n {node.expandCount != null && node.expandCount > 0 && (\n <ExpandControl node={node} onExpand={onNodeExpand ? () => onNodeExpand(node.id) : undefined} />\n )}\n </div>\n );\n })}\n </>\n );\n}\n"],"names":["cn","jsx","nodeName","jsxs","TONE_NODE_CLASS","activateOnKey","Fragment","requireEntry","hoverHandlers"],"mappings":"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"}
@@ -1,28 +1,21 @@
1
1
  import { GraphLayoutPosition } from './graph-layout.js';
2
- import { GraphLayoutGroupBox } from './graph-columns-layout.js';
3
- import { GraphDiagramGroup, GraphDiagramNode, GraphPlacer } from './graph-diagram-model.js';
2
+ import { GraphDiagramNode, GraphPlacer } from './graph-diagram-model.js';
4
3
  export interface GraphDiagramNodesProps {
4
+ /** The nodes to draw: members scrolled out of their window and collapsed groups' stand-ins are left out. */
5
5
  nodes: GraphDiagramNode[];
6
6
  positions: Record<string, GraphLayoutPosition>;
7
+ /** The windowed group box each scrolling member belongs to, by node id: a wheel over the member scrolls it. */
8
+ windowOf: ReadonlyMap<string, string>;
7
9
  place: GraphPlacer;
8
10
  nodeWidth: number;
9
11
  nodeHeight: number;
10
12
  /** Heights of the nodes not drawn at `nodeHeight`, by id: the compact ones. */
11
13
  nodeHeights: Readonly<Record<string, number>>;
14
+ recordNodes: ReadonlySet<string>;
12
15
  selectedId: string | undefined;
13
16
  onNodeSelect: ((id: string) => void) | undefined;
14
17
  onNodeExpand: ((id: string) => void) | undefined;
15
18
  onNodeHover: ((id: string | undefined) => void) | undefined;
16
19
  }
17
- export declare function GraphDiagramNodes({ nodes, positions, place, nodeWidth, nodeHeight, nodeHeights, selectedId, onNodeSelect, onNodeExpand, onNodeHover, }: GraphDiagramNodesProps): import("react").JSX.Element;
18
- export interface GraphDiagramGroupBoxesProps {
19
- boxes: GraphLayoutGroupBox[];
20
- groups: GraphDiagramGroup[] | undefined;
21
- }
22
- /**
23
- * Group rectangles, drawn behind the edges and nodes, each captioned on its top border. A box lets the
24
- * pointer through to the stage, so a click inside it never lands on the box; only its caption takes
25
- * the pointer, for its tooltip.
26
- */
27
- export declare function GraphDiagramGroupBoxes({ boxes, groups }: GraphDiagramGroupBoxesProps): import("react").JSX.Element;
20
+ export declare function GraphDiagramNodes({ nodes, positions, windowOf, place, nodeWidth, nodeHeight, nodeHeights, recordNodes, selectedId, onNodeSelect, onNodeExpand, onNodeHover, }: GraphDiagramNodesProps): import("react").JSX.Element;
28
21
  //# sourceMappingURL=GraphDiagramNodes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GraphDiagramNodes.d.ts","sourceRoot":"","sources":["../../src/data/GraphDiagramNodes.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC7D;AA+GD,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,SAAS,EACT,KAAK,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,EAAE,sBAAsB,+BA8BxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,MAAM,EAAE,iBAAiB,EAAE,GAAG,SAAS,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,2BAA2B,+BAwBpF"}
1
+ {"version":3,"file":"GraphDiagramNodes.d.ts","sourceRoot":"","sources":["../../src/data/GraphDiagramNodes.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAML,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,sBAAsB;IACrC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,+GAA+G;IAC/G,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC7D;AAyHD,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,SAAS,EACT,QAAQ,EACR,KAAK,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,EAAE,sBAAsB,+BAiCxB"}
@@ -37,7 +37,7 @@ function NodeLabel({ node, oneLine }) {
37
37
  }
38
38
  );
39
39
  if (node.icon == null) return label;
40
- return /* @__PURE__ */ jsxs("span", { className: "flex max-w-full items-center gap-1.5", children: [
40
+ return /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 max-w-full items-center gap-1.5", children: [
41
41
  /* @__PURE__ */ jsx("span", { "data-graph-node-icon": "", className: "flex shrink-0 text-base leading-none", children: node.icon }),
42
42
  label
43
43
  ] });
@@ -46,6 +46,7 @@ function NodeBody({
46
46
  node,
47
47
  oneLine,
48
48
  compact,
49
+ row,
49
50
  selected,
50
51
  onSelect
51
52
  }) {
@@ -54,13 +55,14 @@ function NodeBody({
54
55
  "div",
55
56
  {
56
57
  className: cn(
57
- "flex h-full w-full border text-xs shadow-sm",
58
+ "flex h-full w-full text-xs",
59
+ row ? "border-t border-border/60 hover:bg-accent" : "border shadow-sm",
58
60
  compact ? "items-center px-2 text-left text-[11px]" : "flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center",
59
- node.shape === "pill" ? "rounded-full" : compact ? "rounded-md" : "rounded-lg",
60
- TONE_NODE_CLASS[tone],
61
- node.muted && "border-dashed text-muted-foreground shadow-none",
62
- node.muted && tone === "neutral" && "bg-muted",
63
- selected && "ring-2 ring-primary ring-offset-2 ring-offset-background",
61
+ !row && (node.shape === "pill" ? "rounded-full" : compact ? "rounded-md" : "rounded-lg"),
62
+ row ? tone === "info" ? "bg-sky-500/10 text-foreground" : "text-foreground" : TONE_NODE_CLASS[tone],
63
+ node.muted && (row ? "text-muted-foreground" : "border-dashed text-muted-foreground shadow-none"),
64
+ !row && node.muted && tone === "neutral" && "bg-muted",
65
+ selected && (row ? "bg-primary/10 ring-2 ring-inset ring-primary" : "ring-2 ring-primary ring-offset-2 ring-offset-background"),
64
66
  onSelect && "cursor-pointer"
65
67
  ),
66
68
  ...onSelect ? {
@@ -74,6 +76,9 @@ function NodeBody({
74
76
  title: node.title,
75
77
  children: [
76
78
  /* @__PURE__ */ jsx(NodeLabel, { node, oneLine: oneLine || compact }),
79
+ row && node.aside != null && /* @__PURE__ */ jsx("span", { className: "ml-auto min-w-0 truncate pl-2 font-mono text-[10px] text-muted-foreground", children: node.aside }),
80
+ node.mark != null && // Kept out of the node's accessible name, which stays the node's own; the mark is its tooltip.
81
+ /* @__PURE__ */ jsx("span", { "data-graph-node-mark": "", "aria-hidden": true, className: cn("flex shrink-0 items-center", compact ? row && node.aside != null ? "pl-1" : "ml-auto pl-1" : "absolute right-1 top-1"), children: node.mark }),
77
82
  !compact && node.detail != null && /* @__PURE__ */ jsx("span", { className: "line-clamp-1 text-[10px] leading-tight text-muted-foreground", children: node.detail }),
78
83
  !compact && node.badge != null && /* @__PURE__ */ jsx("span", { className: "mt-0.5", children: node.badge })
79
84
  ]
@@ -83,10 +88,12 @@ function NodeBody({
83
88
  function GraphDiagramNodes({
84
89
  nodes,
85
90
  positions,
91
+ windowOf,
86
92
  place,
87
93
  nodeWidth,
88
94
  nodeHeight,
89
95
  nodeHeights,
96
+ recordNodes,
90
97
  selectedId,
91
98
  onNodeSelect,
92
99
  onNodeExpand,
@@ -95,11 +102,13 @@ function GraphDiagramNodes({
95
102
  return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node) => {
96
103
  const height = nodeHeights[node.id] ?? nodeHeight;
97
104
  const compact = node.id in nodeHeights;
105
+ const windowBox = windowOf.get(node.id);
98
106
  return /* @__PURE__ */ jsxs(
99
107
  "div",
100
108
  {
101
109
  "data-graph-node": node.id,
102
110
  "data-graph-node-size": compact ? "compact" : "regular",
111
+ ...windowBox !== void 0 ? { "data-graph-window": windowBox } : {},
103
112
  className: "pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2",
104
113
  style: { ...place(requireEntry(positions, node.id, "layout position")), width: nodeWidth, height },
105
114
  ...onNodeHover ? hoverHandlers(node.id, onNodeHover) : {},
@@ -110,6 +119,7 @@ function GraphDiagramNodes({
110
119
  node,
111
120
  oneLine: height < TWO_LINE_HEIGHT,
112
121
  compact,
122
+ row: recordNodes.has(node.id),
113
123
  selected: selectedId != null && node.id === selectedId,
114
124
  onSelect: onNodeSelect ? () => onNodeSelect(node.id) : void 0
115
125
  }
@@ -121,31 +131,7 @@ function GraphDiagramNodes({
121
131
  );
122
132
  }) });
123
133
  }
124
- function GraphDiagramGroupBoxes({ boxes, groups }) {
125
- const captions = new Map(groups == null ? void 0 : groups.map((group) => [group.id, group]));
126
- return /* @__PURE__ */ jsx(Fragment, { children: boxes.map((box) => {
127
- const caption = captions.get(box.group);
128
- return /* @__PURE__ */ jsx(
129
- "div",
130
- {
131
- "data-graph-group": box.id,
132
- className: "pointer-events-none absolute rounded-xl border border-dashed border-border bg-muted/30",
133
- style: { left: box.x, top: box.y, width: box.width, height: box.height },
134
- children: /* @__PURE__ */ jsx(
135
- "span",
136
- {
137
- title: caption == null ? void 0 : caption.title,
138
- className: "pointer-events-auto absolute left-2 top-0 max-w-[calc(100%-1rem)] -translate-y-1/2 truncate rounded border border-border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground",
139
- children: caption ? caption.label : box.group
140
- }
141
- )
142
- },
143
- box.id
144
- );
145
- }) });
146
- }
147
134
  export {
148
- GraphDiagramGroupBoxes,
149
135
  GraphDiagramNodes
150
136
  };
151
137
  //# sourceMappingURL=GraphDiagramNodes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GraphDiagramNodes.js","sources":["../../src/data/GraphDiagramNodes.tsx"],"sourcesContent":["import type { KeyboardEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\nimport type { GraphLayoutGroupBox } from \"./graph-columns-layout\";\nimport {\n activateOnKey,\n hoverHandlers,\n nodeName,\n requireEntry,\n TONE_NODE_CLASS,\n type GraphDiagramGroup,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\n\nexport interface GraphDiagramNodesProps {\n nodes: GraphDiagramNode[];\n positions: Record<string, GraphLayoutPosition>;\n place: GraphPlacer;\n nodeWidth: number;\n nodeHeight: number;\n /** Heights of the nodes not drawn at `nodeHeight`, by id: the compact ones. */\n nodeHeights: Readonly<Record<string, number>>;\n selectedId: string | undefined;\n onNodeSelect: ((id: string) => void) | undefined;\n onNodeExpand: ((id: string) => void) | undefined;\n onNodeHover: ((id: string | undefined) => void) | undefined;\n}\n\nconst EXPAND_CLASS =\n \"absolute bottom-0 translate-y-1/2 rounded-full border border-border bg-background px-1.5 text-[10px] font-semibold leading-4 text-muted-foreground shadow-sm\";\n\n/**\n * The `+N` control. It hangs off the node's bottom edge at its outer end —\n * the left for a node left of level 0, the right otherwise — which keeps it\n * clear of the side middles where edges attach. It is a sibling of the node,\n * not a child, so activating it never also selects the node.\n */\nfunction ExpandControl({ node, onExpand }: { node: GraphDiagramNode; onExpand: (() => void) | undefined }) {\n const side = (node.level ?? 0) < 0 ? \"left-2\" : \"right-2\";\n const text = `+${node.expandCount}`;\n if (!onExpand) {\n return <span className={cn(EXPAND_CLASS, side)}>{text}</span>;\n }\n return (\n <button\n type=\"button\"\n aria-label={`Expand ${node.expandCount} more from ${nodeName(node)}`}\n className={cn(\n EXPAND_CLASS,\n side,\n \"cursor-pointer hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n onClick={onExpand}\n >\n {text}\n </button>\n );\n}\n\n// Two 15px label lines, the body's vertical padding and its border.\nconst TWO_LINE_HEIGHT = 44;\n\n/** The label, behind the node's icon when it has one. `oneLine` cuts it with an ellipsis instead of wrapping. */\nfunction NodeLabel({ node, oneLine }: { node: GraphDiagramNode; oneLine: boolean }) {\n const label = (\n <span\n className={cn(\n oneLine ? \"max-w-full truncate\" : \"line-clamp-2\",\n \"font-medium leading-tight\",\n node.icon != null && \"min-w-0\",\n )}\n >\n {node.label}\n </span>\n );\n if (node.icon == null) return label;\n return (\n <span className=\"flex max-w-full items-center gap-1.5\">\n <span data-graph-node-icon=\"\" className=\"flex shrink-0 text-base leading-none\">\n {node.icon}\n </span>\n {label}\n </span>\n );\n}\n\nfunction NodeBody({\n node,\n oneLine,\n compact,\n selected,\n onSelect,\n}: {\n node: GraphDiagramNode;\n /** The node is too short for two label lines. */\n oneLine: boolean;\n /** Its icon and label on one short line, left-aligned, without its detail or badge. */\n compact: boolean;\n selected: boolean;\n onSelect: (() => void) | undefined;\n}) {\n const tone = node.tone ?? \"neutral\";\n return (\n <div\n className={cn(\n \"flex h-full w-full border text-xs shadow-sm\",\n compact\n ? \"items-center px-2 text-left text-[11px]\"\n : \"flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center\",\n node.shape === \"pill\" ? \"rounded-full\" : compact ? \"rounded-md\" : \"rounded-lg\",\n TONE_NODE_CLASS[tone],\n node.muted && \"border-dashed text-muted-foreground shadow-none\",\n node.muted && tone === \"neutral\" && \"bg-muted\",\n selected && \"ring-2 ring-primary ring-offset-2 ring-offset-background\",\n onSelect && \"cursor-pointer\",\n )}\n {...(onSelect\n ? {\n role: \"button\" as const,\n tabIndex: 0,\n \"aria-pressed\": selected,\n onClick: onSelect,\n onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => activateOnKey(event, onSelect),\n }\n : {})}\n aria-label={node.ariaLabel}\n title={node.title}\n >\n <NodeLabel node={node} oneLine={oneLine || compact} />\n {!compact && node.detail != null && (\n <span className=\"line-clamp-1 text-[10px] leading-tight text-muted-foreground\">{node.detail}</span>\n )}\n {!compact && node.badge != null && <span className=\"mt-0.5\">{node.badge}</span>}\n </div>\n );\n}\n\nexport function GraphDiagramNodes({\n nodes,\n positions,\n place,\n nodeWidth,\n nodeHeight,\n nodeHeights,\n selectedId,\n onNodeSelect,\n onNodeExpand,\n onNodeHover,\n}: GraphDiagramNodesProps) {\n return (\n <>\n {nodes.map((node) => {\n const height = nodeHeights[node.id] ?? nodeHeight;\n const compact = node.id in nodeHeights;\n return (\n <div\n key={node.id}\n data-graph-node={node.id}\n data-graph-node-size={compact ? \"compact\" : \"regular\"}\n className=\"pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2\"\n style={{ ...place(requireEntry(positions, node.id, \"layout position\")), width: nodeWidth, height }}\n {...(onNodeHover ? hoverHandlers(node.id, onNodeHover) : {})}\n >\n <NodeBody\n node={node}\n oneLine={height < TWO_LINE_HEIGHT}\n compact={compact}\n selected={selectedId != null && node.id === selectedId}\n onSelect={onNodeSelect ? () => onNodeSelect(node.id) : undefined}\n />\n {node.expandCount != null && node.expandCount > 0 && (\n <ExpandControl node={node} onExpand={onNodeExpand ? () => onNodeExpand(node.id) : undefined} />\n )}\n </div>\n );\n })}\n </>\n );\n}\n\nexport interface GraphDiagramGroupBoxesProps {\n boxes: GraphLayoutGroupBox[];\n groups: GraphDiagramGroup[] | undefined;\n}\n\n/**\n * Group rectangles, drawn behind the edges and nodes, each captioned on its top border. A box lets the\n * pointer through to the stage, so a click inside it never lands on the box; only its caption takes\n * the pointer, for its tooltip.\n */\nexport function GraphDiagramGroupBoxes({ boxes, groups }: GraphDiagramGroupBoxesProps) {\n const captions = new Map(groups?.map((group) => [group.id, group]));\n return (\n <>\n {boxes.map((box) => {\n const caption = captions.get(box.group);\n return (\n <div\n key={box.id}\n data-graph-group={box.id}\n className=\"pointer-events-none absolute rounded-xl border border-dashed border-border bg-muted/30\"\n style={{ left: box.x, top: box.y, width: box.width, height: box.height }}\n >\n <span\n title={caption?.title}\n className=\"pointer-events-auto absolute left-2 top-0 max-w-[calc(100%-1rem)] -translate-y-1/2 truncate rounded border border-border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground\"\n >\n {caption ? caption.label : box.group}\n </span>\n </div>\n );\n })}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GraphDiagramNodes.js","sources":["../../src/data/GraphDiagramNodes.tsx"],"sourcesContent":["import type { KeyboardEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\nimport {\n activateOnKey,\n hoverHandlers,\n nodeName,\n requireEntry,\n TONE_NODE_CLASS,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\n\nexport interface GraphDiagramNodesProps {\n /** The nodes to draw: members scrolled out of their window and collapsed groups' stand-ins are left out. */\n nodes: GraphDiagramNode[];\n positions: Record<string, GraphLayoutPosition>;\n /** The windowed group box each scrolling member belongs to, by node id: a wheel over the member scrolls it. */\n windowOf: ReadonlyMap<string, string>;\n place: GraphPlacer;\n nodeWidth: number;\n nodeHeight: number;\n /** Heights of the nodes not drawn at `nodeHeight`, by id: the compact ones. */\n nodeHeights: Readonly<Record<string, number>>;\n recordNodes: ReadonlySet<string>;\n selectedId: string | undefined;\n onNodeSelect: ((id: string) => void) | undefined;\n onNodeExpand: ((id: string) => void) | undefined;\n onNodeHover: ((id: string | undefined) => void) | undefined;\n}\n\nconst EXPAND_CLASS =\n \"absolute bottom-0 translate-y-1/2 rounded-full border border-border bg-background px-1.5 text-[10px] font-semibold leading-4 text-muted-foreground shadow-sm\";\n\n/**\n * The `+N` control. It hangs off the node's bottom edge at its outer end —\n * the left for a node left of level 0, the right otherwise — which keeps it\n * clear of the side middles where edges attach. It is a sibling of the node,\n * not a child, so activating it never also selects the node.\n */\nfunction ExpandControl({ node, onExpand }: { node: GraphDiagramNode; onExpand: (() => void) | undefined }) {\n const side = (node.level ?? 0) < 0 ? \"left-2\" : \"right-2\";\n const text = `+${node.expandCount}`;\n if (!onExpand) {\n return <span className={cn(EXPAND_CLASS, side)}>{text}</span>;\n }\n return (\n <button\n type=\"button\"\n aria-label={`Expand ${node.expandCount} more from ${nodeName(node)}`}\n className={cn(\n EXPAND_CLASS,\n side,\n \"cursor-pointer hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n onClick={onExpand}\n >\n {text}\n </button>\n );\n}\n\n// Two 15px label lines, the body's vertical padding and its border.\nconst TWO_LINE_HEIGHT = 44;\n\n/** The label, behind the node's icon when it has one. `oneLine` cuts it with an ellipsis instead of wrapping. */\nfunction NodeLabel({ node, oneLine }: { node: GraphDiagramNode; oneLine: boolean }) {\n const label = (\n <span\n className={cn(\n oneLine ? \"max-w-full truncate\" : \"line-clamp-2\",\n \"font-medium leading-tight\",\n node.icon != null && \"min-w-0\",\n )}\n >\n {node.label}\n </span>\n );\n if (node.icon == null) return label;\n return (\n <span className=\"flex min-w-0 max-w-full items-center gap-1.5\">\n <span data-graph-node-icon=\"\" className=\"flex shrink-0 text-base leading-none\">\n {node.icon}\n </span>\n {label}\n </span>\n );\n}\n\nfunction NodeBody({\n node,\n oneLine,\n compact,\n row,\n selected,\n onSelect,\n}: {\n node: GraphDiagramNode;\n /** The node is too short for two label lines. */\n oneLine: boolean;\n /** Its icon and label on one short line, left-aligned, without its detail or badge. */\n compact: boolean;\n row: boolean;\n selected: boolean;\n onSelect: (() => void) | undefined;\n}) {\n const tone = node.tone ?? \"neutral\";\n return (\n <div\n className={cn(\n \"flex h-full w-full text-xs\",\n row ? \"border-t border-border/60 hover:bg-accent\" : \"border shadow-sm\",\n compact\n ? \"items-center px-2 text-left text-[11px]\"\n : \"flex-col items-center justify-center gap-0.5 px-3 py-1.5 text-center\",\n !row && (node.shape === \"pill\" ? \"rounded-full\" : compact ? \"rounded-md\" : \"rounded-lg\"),\n row ? (tone === \"info\" ? \"bg-sky-500/10 text-foreground\" : \"text-foreground\") : TONE_NODE_CLASS[tone],\n node.muted && (row ? \"text-muted-foreground\" : \"border-dashed text-muted-foreground shadow-none\"),\n !row && node.muted && tone === \"neutral\" && \"bg-muted\",\n selected && (row ? \"bg-primary/10 ring-2 ring-inset ring-primary\" : \"ring-2 ring-primary ring-offset-2 ring-offset-background\"),\n onSelect && \"cursor-pointer\",\n )}\n {...(onSelect\n ? {\n role: \"button\" as const,\n tabIndex: 0,\n \"aria-pressed\": selected,\n onClick: onSelect,\n onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => activateOnKey(event, onSelect),\n }\n : {})}\n aria-label={node.ariaLabel}\n title={node.title}\n >\n <NodeLabel node={node} oneLine={oneLine || compact} />\n {row && node.aside != null && <span className=\"ml-auto min-w-0 truncate pl-2 font-mono text-[10px] text-muted-foreground\">{node.aside}</span>}\n {node.mark != null && (\n // Kept out of the node's accessible name, which stays the node's own; the mark is its tooltip.\n <span data-graph-node-mark=\"\" aria-hidden className={cn(\"flex shrink-0 items-center\", compact ? (row && node.aside != null ? \"pl-1\" : \"ml-auto pl-1\") : \"absolute right-1 top-1\")}>\n {node.mark}\n </span>\n )}\n {!compact && node.detail != null && (\n <span className=\"line-clamp-1 text-[10px] leading-tight text-muted-foreground\">{node.detail}</span>\n )}\n {!compact && node.badge != null && <span className=\"mt-0.5\">{node.badge}</span>}\n </div>\n );\n}\n\nexport function GraphDiagramNodes({\n nodes,\n positions,\n windowOf,\n place,\n nodeWidth,\n nodeHeight,\n nodeHeights,\n recordNodes,\n selectedId,\n onNodeSelect,\n onNodeExpand,\n onNodeHover,\n}: GraphDiagramNodesProps) {\n return (\n <>\n {nodes.map((node) => {\n const height = nodeHeights[node.id] ?? nodeHeight;\n const compact = node.id in nodeHeights;\n const windowBox = windowOf.get(node.id);\n return (\n <div\n key={node.id}\n data-graph-node={node.id}\n data-graph-node-size={compact ? \"compact\" : \"regular\"}\n {...(windowBox !== undefined ? { \"data-graph-window\": windowBox } : {})}\n className=\"pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2\"\n style={{ ...place(requireEntry(positions, node.id, \"layout position\")), width: nodeWidth, height }}\n {...(onNodeHover ? hoverHandlers(node.id, onNodeHover) : {})}\n >\n <NodeBody\n node={node}\n oneLine={height < TWO_LINE_HEIGHT}\n compact={compact}\n row={recordNodes.has(node.id)}\n selected={selectedId != null && node.id === selectedId}\n onSelect={onNodeSelect ? () => onNodeSelect(node.id) : undefined}\n />\n {node.expandCount != null && node.expandCount > 0 && (\n <ExpandControl node={node} onExpand={onNodeExpand ? () => onNodeExpand(node.id) : undefined} />\n )}\n </div>\n );\n })}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -38,7 +38,7 @@ function TreeNode({
38
38
  const expandable = hasChildren || lazyUnloaded;
39
39
  const secondaryNode = (isSecondary == null ? void 0 : isSecondary(node)) ?? false;
40
40
  const childrenAllSecondary = hasChildren && isSecondary != null && children.every((c) => isSecondary(c));
41
- const skipExpandAll = secondaryNode || childrenAllSecondary;
41
+ const skipExpandAll = secondaryNode || childrenAllSecondary || expandAll === true && lazyUnloaded;
42
42
  const initialOpen = expandAll !== null && !skipExpandAll ? expandAll : defaultOpen ? defaultOpen(node, depth) : depth < 1;
43
43
  const [open, setOpen] = react.useState(initialOpen);
44
44
  const prevExpandAll = react.useRef(expandAll);
@@ -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\n// A row's content column starts after the chevron (`w-3`, 12px) plus the row's\n// `gap-1.5` (6px) — fixed regardless of indentPx/basePaddingPx, since the\n// chevron/spacer width and gap are not configurable. renderDetail's wrapper\n// adds this on top of the row's own paddingLeft so it lines up under the label.\nconst ROW_CONTENT_OFFSET_PX = 18;\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 getAriaLabel?: (node: T) => string;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n /**\n * Render extra content beneath a node's row and above its children (e.g. the\n * decompiled source line a call was made from). Rendered for leaf and parent\n * nodes alike, regardless of open/collapsed state, and left-aligned with the\n * row's content column (after the depth indent and chevron). Returning\n * `null`/`undefined`/`false` renders nothing — no wrapper, no extra spacing.\n * A click or keydown inside it is stopped from propagating, so it can never\n * toggle the row's expand/collapse state.\n */\n renderDetail?: (node: T) => ReactNode;\n /** Render a node's result after its children, including when the children are collapsed. */\n renderAfterChildren?: (node: 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 getAriaLabel,\n onSelect,\n renderRow,\n renderDetail,\n renderAfterChildren,\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 // 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 // A non-null `expandAll` is a live bulk state, not just a one-off change\n // signal: a node mounted *while* expand-all is active (because an ancestor\n // just opened) inherits it as its initial open state, so opening a parent\n // cascades all the way down — expand/collapse-all are recursive. The\n // skipExpandAll guard keeps secondary subtrees from auto-flooding. When\n // expandAll is null (before any toolbar click) we fall back to defaultOpen.\n const initialOpen =\n expandAll !== null && !skipExpandAll\n ? expandAll\n : defaultOpen\n ? defaultOpen(node, depth)\n : 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\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 // A lazy node can be open without ever having been clicked — defaultOpen said\n // so, expand-all did, or the filter forced it open to reveal a match. Loading\n // only on the open transition would leave every one of those showing a caret\n // over nothing.\n //\n // A node that already failed is skipped: loadLazyChildren clears its own retry\n // guard on failure so a re-toggle can try again, and re-running here would\n // turn that into an unbounded retry loop instead.\n useEffect(() => {\n if (isOpen && lazyUnloaded && error === null) loadLazyChildren();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, lazyUnloaded, error]);\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 const detail = renderDetail?.(node);\n const afterChildren = renderAfterChildren?.(node);\n\n return (\n <div\n role=\"treeitem\"\n aria-label={getAriaLabel?.(node)}\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 <button\n type=\"button\"\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n // The caret only toggles expansion — stopPropagation keeps the row's\n // onClick (which selects) from firing, so expanding a node never\n // selects it.\n onClick={(e) => {\n e.stopPropagation();\n toggle();\n }}\n className=\"flex shrink-0 items-center text-muted-foreground\"\n >\n <Icon icon={isOpen ? UiChevronDown : UiChevronRight} className=\"text-xs w-3\" />\n </button>\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 {detail != null && detail !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {detail}\n </div>\n )}\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 {...(renderDetail ? { renderDetail } : {})}\n {...(renderAfterChildren ? { renderAfterChildren } : {})}\n {...(getAriaLabel ? { getAriaLabel } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n {afterChildren != null && afterChildren !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {afterChildren}\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\n// A row's content column starts after the chevron (`w-3`, 12px) plus the row's\n// `gap-1.5` (6px) — fixed regardless of indentPx/basePaddingPx, since the\n// chevron/spacer width and gap are not configurable. renderDetail's wrapper\n// adds this on top of the row's own paddingLeft so it lines up under the label.\nconst ROW_CONTENT_OFFSET_PX = 18;\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 getAriaLabel?: (node: T) => string;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n /**\n * Render extra content beneath a node's row and above its children (e.g. the\n * decompiled source line a call was made from). Rendered for leaf and parent\n * nodes alike, regardless of open/collapsed state, and left-aligned with the\n * row's content column (after the depth indent and chevron). Returning\n * `null`/`undefined`/`false` renders nothing — no wrapper, no extra spacing.\n * A click or keydown inside it is stopped from propagating, so it can never\n * toggle the row's expand/collapse state.\n */\n renderDetail?: (node: T) => ReactNode;\n /** Render a node's result after its children, including when the children are collapsed. */\n renderAfterChildren?: (node: 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 getAriaLabel,\n onSelect,\n renderRow,\n renderDetail,\n renderAfterChildren,\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 // 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 // An unloaded lazy node never opens in bulk either: opening it would fetch,\n // and bulk operations never trigger loads. Collapse-all still closes it.\n const skipExpandAll = secondaryNode || childrenAllSecondary || (expandAll === true && lazyUnloaded);\n // A non-null `expandAll` is a live bulk state, not just a one-off change\n // signal: a node mounted *while* expand-all is active (because an ancestor\n // just opened) inherits it as its initial open state, so opening a parent\n // cascades all the way down — expand/collapse-all are recursive. The\n // skipExpandAll guard keeps secondary subtrees from auto-flooding. When\n // expandAll is null (before any toolbar click) we fall back to defaultOpen.\n const initialOpen =\n expandAll !== null && !skipExpandAll\n ? expandAll\n : defaultOpen\n ? defaultOpen(node, depth)\n : 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\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 // A lazy node can be open without ever having been clicked — defaultOpen said\n // so, expand-all did, or the filter forced it open to reveal a match. Loading\n // only on the open transition would leave every one of those showing a caret\n // over nothing.\n //\n // A node that already failed is skipped: loadLazyChildren clears its own retry\n // guard on failure so a re-toggle can try again, and re-running here would\n // turn that into an unbounded retry loop instead.\n useEffect(() => {\n if (isOpen && lazyUnloaded && error === null) loadLazyChildren();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, lazyUnloaded, error]);\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 const detail = renderDetail?.(node);\n const afterChildren = renderAfterChildren?.(node);\n\n return (\n <div\n role=\"treeitem\"\n aria-label={getAriaLabel?.(node)}\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 <button\n type=\"button\"\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n // The caret only toggles expansion — stopPropagation keeps the row's\n // onClick (which selects) from firing, so expanding a node never\n // selects it.\n onClick={(e) => {\n e.stopPropagation();\n toggle();\n }}\n className=\"flex shrink-0 items-center text-muted-foreground\"\n >\n <Icon icon={isOpen ? UiChevronDown : UiChevronRight} className=\"text-xs w-3\" />\n </button>\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 {detail != null && detail !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {detail}\n </div>\n )}\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 {...(renderDetail ? { renderDetail } : {})}\n {...(renderAfterChildren ? { renderAfterChildren } : {})}\n {...(getAriaLabel ? { getAriaLabel } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n {afterChildren != null && afterChildren !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {afterChildren}\n </div>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","jsxs","cn","jsx","Icon","UiChevronDown","UiChevronRight"],"mappings":"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"}
@@ -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;AAWpE,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,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACjD;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IACtC,4FAA4F;IAC5F,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAC7C,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,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,QAAa,EACb,aAAiB,EACjB,WAAW,EACX,eAAe,EACf,YAAY,GACb,EAAE,aAAa,CAAC,CAAC,CAAC,+BAgNlB"}
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;AAWpE,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,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACjD;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IACtC,4FAA4F;IAC5F,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAC7C,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,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,QAAa,EACb,aAAiB,EACjB,WAAW,EACX,eAAe,EACf,YAAY,GACb,EAAE,aAAa,CAAC,CAAC,CAAC,+BAkNlB"}
@@ -36,7 +36,7 @@ function TreeNode({
36
36
  const expandable = hasChildren || lazyUnloaded;
37
37
  const secondaryNode = (isSecondary == null ? void 0 : isSecondary(node)) ?? false;
38
38
  const childrenAllSecondary = hasChildren && isSecondary != null && children.every((c) => isSecondary(c));
39
- const skipExpandAll = secondaryNode || childrenAllSecondary;
39
+ const skipExpandAll = secondaryNode || childrenAllSecondary || expandAll === true && lazyUnloaded;
40
40
  const initialOpen = expandAll !== null && !skipExpandAll ? expandAll : defaultOpen ? defaultOpen(node, depth) : depth < 1;
41
41
  const [open, setOpen] = useState(initialOpen);
42
42
  const prevExpandAll = useRef(expandAll);
@@ -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\n// A row's content column starts after the chevron (`w-3`, 12px) plus the row's\n// `gap-1.5` (6px) — fixed regardless of indentPx/basePaddingPx, since the\n// chevron/spacer width and gap are not configurable. renderDetail's wrapper\n// adds this on top of the row's own paddingLeft so it lines up under the label.\nconst ROW_CONTENT_OFFSET_PX = 18;\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 getAriaLabel?: (node: T) => string;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n /**\n * Render extra content beneath a node's row and above its children (e.g. the\n * decompiled source line a call was made from). Rendered for leaf and parent\n * nodes alike, regardless of open/collapsed state, and left-aligned with the\n * row's content column (after the depth indent and chevron). Returning\n * `null`/`undefined`/`false` renders nothing — no wrapper, no extra spacing.\n * A click or keydown inside it is stopped from propagating, so it can never\n * toggle the row's expand/collapse state.\n */\n renderDetail?: (node: T) => ReactNode;\n /** Render a node's result after its children, including when the children are collapsed. */\n renderAfterChildren?: (node: 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 getAriaLabel,\n onSelect,\n renderRow,\n renderDetail,\n renderAfterChildren,\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 // 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 // A non-null `expandAll` is a live bulk state, not just a one-off change\n // signal: a node mounted *while* expand-all is active (because an ancestor\n // just opened) inherits it as its initial open state, so opening a parent\n // cascades all the way down — expand/collapse-all are recursive. The\n // skipExpandAll guard keeps secondary subtrees from auto-flooding. When\n // expandAll is null (before any toolbar click) we fall back to defaultOpen.\n const initialOpen =\n expandAll !== null && !skipExpandAll\n ? expandAll\n : defaultOpen\n ? defaultOpen(node, depth)\n : 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\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 // A lazy node can be open without ever having been clicked — defaultOpen said\n // so, expand-all did, or the filter forced it open to reveal a match. Loading\n // only on the open transition would leave every one of those showing a caret\n // over nothing.\n //\n // A node that already failed is skipped: loadLazyChildren clears its own retry\n // guard on failure so a re-toggle can try again, and re-running here would\n // turn that into an unbounded retry loop instead.\n useEffect(() => {\n if (isOpen && lazyUnloaded && error === null) loadLazyChildren();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, lazyUnloaded, error]);\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 const detail = renderDetail?.(node);\n const afterChildren = renderAfterChildren?.(node);\n\n return (\n <div\n role=\"treeitem\"\n aria-label={getAriaLabel?.(node)}\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 <button\n type=\"button\"\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n // The caret only toggles expansion — stopPropagation keeps the row's\n // onClick (which selects) from firing, so expanding a node never\n // selects it.\n onClick={(e) => {\n e.stopPropagation();\n toggle();\n }}\n className=\"flex shrink-0 items-center text-muted-foreground\"\n >\n <Icon icon={isOpen ? UiChevronDown : UiChevronRight} className=\"text-xs w-3\" />\n </button>\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 {detail != null && detail !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {detail}\n </div>\n )}\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 {...(renderDetail ? { renderDetail } : {})}\n {...(renderAfterChildren ? { renderAfterChildren } : {})}\n {...(getAriaLabel ? { getAriaLabel } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n {afterChildren != null && afterChildren !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {afterChildren}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
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\n// A row's content column starts after the chevron (`w-3`, 12px) plus the row's\n// `gap-1.5` (6px) — fixed regardless of indentPx/basePaddingPx, since the\n// chevron/spacer width and gap are not configurable. renderDetail's wrapper\n// adds this on top of the row's own paddingLeft so it lines up under the label.\nconst ROW_CONTENT_OFFSET_PX = 18;\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 getAriaLabel?: (node: T) => string;\n onSelect?: (node: T) => void;\n renderRow: (ctx: TreeRowContext<T>) => ReactNode;\n /**\n * Render extra content beneath a node's row and above its children (e.g. the\n * decompiled source line a call was made from). Rendered for leaf and parent\n * nodes alike, regardless of open/collapsed state, and left-aligned with the\n * row's content column (after the depth indent and chevron). Returning\n * `null`/`undefined`/`false` renders nothing — no wrapper, no extra spacing.\n * A click or keydown inside it is stopped from propagating, so it can never\n * toggle the row's expand/collapse state.\n */\n renderDetail?: (node: T) => ReactNode;\n /** Render a node's result after its children, including when the children are collapsed. */\n renderAfterChildren?: (node: 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 getAriaLabel,\n onSelect,\n renderRow,\n renderDetail,\n renderAfterChildren,\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 // 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 // An unloaded lazy node never opens in bulk either: opening it would fetch,\n // and bulk operations never trigger loads. Collapse-all still closes it.\n const skipExpandAll = secondaryNode || childrenAllSecondary || (expandAll === true && lazyUnloaded);\n // A non-null `expandAll` is a live bulk state, not just a one-off change\n // signal: a node mounted *while* expand-all is active (because an ancestor\n // just opened) inherits it as its initial open state, so opening a parent\n // cascades all the way down — expand/collapse-all are recursive. The\n // skipExpandAll guard keeps secondary subtrees from auto-flooding. When\n // expandAll is null (before any toolbar click) we fall back to defaultOpen.\n const initialOpen =\n expandAll !== null && !skipExpandAll\n ? expandAll\n : defaultOpen\n ? defaultOpen(node, depth)\n : 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\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 // A lazy node can be open without ever having been clicked — defaultOpen said\n // so, expand-all did, or the filter forced it open to reveal a match. Loading\n // only on the open transition would leave every one of those showing a caret\n // over nothing.\n //\n // A node that already failed is skipped: loadLazyChildren clears its own retry\n // guard on failure so a re-toggle can try again, and re-running here would\n // turn that into an unbounded retry loop instead.\n useEffect(() => {\n if (isOpen && lazyUnloaded && error === null) loadLazyChildren();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, lazyUnloaded, error]);\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 const detail = renderDetail?.(node);\n const afterChildren = renderAfterChildren?.(node);\n\n return (\n <div\n role=\"treeitem\"\n aria-label={getAriaLabel?.(node)}\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 <button\n type=\"button\"\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n // The caret only toggles expansion — stopPropagation keeps the row's\n // onClick (which selects) from firing, so expanding a node never\n // selects it.\n onClick={(e) => {\n e.stopPropagation();\n toggle();\n }}\n className=\"flex shrink-0 items-center text-muted-foreground\"\n >\n <Icon icon={isOpen ? UiChevronDown : UiChevronRight} className=\"text-xs w-3\" />\n </button>\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 {detail != null && detail !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {detail}\n </div>\n )}\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 {...(renderDetail ? { renderDetail } : {})}\n {...(renderAfterChildren ? { renderAfterChildren } : {})}\n {...(getAriaLabel ? { getAriaLabel } : {})}\n {...(onSelect ? { onSelect } : {})}\n {...(rowClass ? { rowClass } : {})}\n {...(isSecondary ? { isSecondary } : {})}\n {...(hasMoreChildren ? { hasMoreChildren } : {})}\n {...(loadChildren ? { loadChildren } : {})}\n />\n ))}\n </div>\n )}\n {afterChildren != null && afterChildren !== false && (\n <div\n className=\"pb-1 pr-2\"\n style={{ paddingLeft: `${depth * indentPx + basePaddingPx + ROW_CONTENT_OFFSET_PX}px` }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {afterChildren}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}