@flanksource/clicky-ui 0.3.45 → 0.3.47

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 (505) hide show
  1. package/NOTICE.md +11 -0
  2. package/README.md +22 -0
  3. package/dist/ai.cjs +13 -8
  4. package/dist/ai.cjs.map +1 -1
  5. package/dist/ai.js +7 -2
  6. package/dist/ai.js.map +1 -1
  7. package/dist/components/ErrorWrapper.cjs +2 -3
  8. package/dist/components/ErrorWrapper.cjs.map +1 -1
  9. package/dist/components/ErrorWrapper.d.ts.map +1 -1
  10. package/dist/components/ErrorWrapper.js +3 -4
  11. package/dist/components/ErrorWrapper.js.map +1 -1
  12. package/dist/data/AccessMark.cjs +23 -0
  13. package/dist/data/AccessMark.cjs.map +1 -0
  14. package/dist/data/AccessMark.d.ts +11 -0
  15. package/dist/data/AccessMark.d.ts.map +1 -0
  16. package/dist/data/AccessMark.js +23 -0
  17. package/dist/data/AccessMark.js.map +1 -0
  18. package/dist/data/Clicky.cjs +3 -0
  19. package/dist/data/Clicky.cjs.map +1 -1
  20. package/dist/data/Clicky.js +3 -0
  21. package/dist/data/Clicky.js.map +1 -1
  22. package/dist/data/CodeDiff.cjs +116 -23
  23. package/dist/data/CodeDiff.cjs.map +1 -1
  24. package/dist/data/CodeDiff.d.ts +26 -0
  25. package/dist/data/CodeDiff.d.ts.map +1 -1
  26. package/dist/data/CodeDiff.js +116 -23
  27. package/dist/data/CodeDiff.js.map +1 -1
  28. package/dist/data/FilterPill.cjs +15 -2
  29. package/dist/data/FilterPill.cjs.map +1 -1
  30. package/dist/data/FilterPill.d.ts +1 -1
  31. package/dist/data/FilterPill.d.ts.map +1 -1
  32. package/dist/data/FilterPill.js +15 -2
  33. package/dist/data/FilterPill.js.map +1 -1
  34. package/dist/data/GraphDiagram.cjs +65 -13
  35. package/dist/data/GraphDiagram.cjs.map +1 -1
  36. package/dist/data/GraphDiagram.d.ts +9 -2
  37. package/dist/data/GraphDiagram.d.ts.map +1 -1
  38. package/dist/data/GraphDiagram.js +66 -14
  39. package/dist/data/GraphDiagram.js.map +1 -1
  40. package/dist/data/GraphDiagramGroups.cjs +100 -0
  41. package/dist/data/GraphDiagramGroups.cjs.map +1 -0
  42. package/dist/data/GraphDiagramGroups.d.ts +24 -0
  43. package/dist/data/GraphDiagramGroups.d.ts.map +1 -0
  44. package/dist/data/GraphDiagramGroups.js +100 -0
  45. package/dist/data/GraphDiagramGroups.js.map +1 -0
  46. package/dist/data/GraphDiagramNodes.cjs +17 -31
  47. package/dist/data/GraphDiagramNodes.cjs.map +1 -1
  48. package/dist/data/GraphDiagramNodes.d.ts +6 -13
  49. package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
  50. package/dist/data/GraphDiagramNodes.js +17 -31
  51. package/dist/data/GraphDiagramNodes.js.map +1 -1
  52. package/dist/data/LogViewer.cjs +27 -8
  53. package/dist/data/LogViewer.cjs.map +1 -1
  54. package/dist/data/LogViewer.d.ts +11 -3
  55. package/dist/data/LogViewer.d.ts.map +1 -1
  56. package/dist/data/LogViewer.js +27 -8
  57. package/dist/data/LogViewer.js.map +1 -1
  58. package/dist/data/Properties.cjs +2 -15
  59. package/dist/data/Properties.cjs.map +1 -1
  60. package/dist/data/Properties.d.ts +4 -0
  61. package/dist/data/Properties.d.ts.map +1 -1
  62. package/dist/data/Properties.js +2 -15
  63. package/dist/data/Properties.js.map +1 -1
  64. package/dist/data/PropertyValue.cjs +37 -0
  65. package/dist/data/PropertyValue.cjs.map +1 -0
  66. package/dist/data/PropertyValue.d.ts +6 -0
  67. package/dist/data/PropertyValue.d.ts.map +1 -0
  68. package/dist/data/PropertyValue.js +37 -0
  69. package/dist/data/PropertyValue.js.map +1 -0
  70. package/dist/data/Tree.cjs +4 -3
  71. package/dist/data/Tree.cjs.map +1 -1
  72. package/dist/data/Tree.d.ts +3 -1
  73. package/dist/data/Tree.d.ts.map +1 -1
  74. package/dist/data/Tree.js +4 -3
  75. package/dist/data/Tree.js.map +1 -1
  76. package/dist/data/TreeNode.cjs +1 -1
  77. package/dist/data/TreeNode.cjs.map +1 -1
  78. package/dist/data/TreeNode.d.ts.map +1 -1
  79. package/dist/data/TreeNode.js +1 -1
  80. package/dist/data/TreeNode.js.map +1 -1
  81. package/dist/data/ai/ChatFab.cjs +1 -1
  82. package/dist/data/ai/ChatFab.cjs.map +1 -1
  83. package/dist/data/ai/ChatFab.js +1 -1
  84. package/dist/data/ai/ChatFab.js.map +1 -1
  85. package/dist/data/ai/ChatWindow.cjs +106 -171
  86. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  87. package/dist/data/ai/ChatWindow.d.ts +6 -2
  88. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  89. package/dist/data/ai/ChatWindow.header.cjs +104 -0
  90. package/dist/data/ai/ChatWindow.header.cjs.map +1 -0
  91. package/dist/data/ai/ChatWindow.header.d.ts +29 -0
  92. package/dist/data/ai/ChatWindow.header.d.ts.map +1 -0
  93. package/dist/data/ai/ChatWindow.header.js +104 -0
  94. package/dist/data/ai/ChatWindow.header.js.map +1 -0
  95. package/dist/data/ai/ChatWindow.js +107 -172
  96. package/dist/data/ai/ChatWindow.js.map +1 -1
  97. package/dist/data/ai/ChatWindow.placement.cjs +95 -0
  98. package/dist/data/ai/ChatWindow.placement.cjs.map +1 -0
  99. package/dist/data/ai/ChatWindow.placement.d.ts +32 -0
  100. package/dist/data/ai/ChatWindow.placement.d.ts.map +1 -0
  101. package/dist/data/ai/ChatWindow.placement.js +95 -0
  102. package/dist/data/ai/ChatWindow.placement.js.map +1 -0
  103. package/dist/data/ai/SessionChatComposer.cjs +3 -1
  104. package/dist/data/ai/SessionChatComposer.cjs.map +1 -1
  105. package/dist/data/ai/SessionChatComposer.d.ts +4 -1
  106. package/dist/data/ai/SessionChatComposer.d.ts.map +1 -1
  107. package/dist/data/ai/SessionChatComposer.js +3 -1
  108. package/dist/data/ai/SessionChatComposer.js.map +1 -1
  109. package/dist/data/ai/SessionInspector.cjs +68 -72
  110. package/dist/data/ai/SessionInspector.cjs.map +1 -1
  111. package/dist/data/ai/SessionInspector.composer-slot.cjs +8 -0
  112. package/dist/data/ai/SessionInspector.composer-slot.cjs.map +1 -0
  113. package/dist/data/ai/SessionInspector.composer-slot.d.ts +9 -0
  114. package/dist/data/ai/SessionInspector.composer-slot.d.ts.map +1 -0
  115. package/dist/data/ai/SessionInspector.composer-slot.js +8 -0
  116. package/dist/data/ai/SessionInspector.composer-slot.js.map +1 -0
  117. package/dist/data/ai/SessionInspector.d.ts +6 -1
  118. package/dist/data/ai/SessionInspector.d.ts.map +1 -1
  119. package/dist/data/ai/SessionInspector.js +69 -73
  120. package/dist/data/ai/SessionInspector.js.map +1 -1
  121. package/dist/data/ai/SessionInspector.legacy.cjs +41 -42
  122. package/dist/data/ai/SessionInspector.legacy.cjs.map +1 -1
  123. package/dist/data/ai/SessionInspector.legacy.d.ts.map +1 -1
  124. package/dist/data/ai/SessionInspector.legacy.js +41 -42
  125. package/dist/data/ai/SessionInspector.legacy.js.map +1 -1
  126. package/dist/data/ai/SessionInspector.remote-view.cjs +14 -9
  127. package/dist/data/ai/SessionInspector.remote-view.cjs.map +1 -1
  128. package/dist/data/ai/SessionInspector.remote-view.d.ts +6 -2
  129. package/dist/data/ai/SessionInspector.remote-view.d.ts.map +1 -1
  130. package/dist/data/ai/SessionInspector.remote-view.js +14 -9
  131. package/dist/data/ai/SessionInspector.remote-view.js.map +1 -1
  132. package/dist/data/ai/SessionInspector.remote.cjs +84 -16
  133. package/dist/data/ai/SessionInspector.remote.cjs.map +1 -1
  134. package/dist/data/ai/SessionInspector.remote.d.ts +42 -7
  135. package/dist/data/ai/SessionInspector.remote.d.ts.map +1 -1
  136. package/dist/data/ai/SessionInspector.remote.js +86 -18
  137. package/dist/data/ai/SessionInspector.remote.js.map +1 -1
  138. package/dist/data/ai/SessionInspector.transcript.cjs +5 -0
  139. package/dist/data/ai/SessionInspector.transcript.cjs.map +1 -1
  140. package/dist/data/ai/SessionInspector.transcript.d.ts.map +1 -1
  141. package/dist/data/ai/SessionInspector.transcript.js +5 -0
  142. package/dist/data/ai/SessionInspector.transcript.js.map +1 -1
  143. package/dist/data/ai/SessionTail.cjs +63 -0
  144. package/dist/data/ai/SessionTail.cjs.map +1 -0
  145. package/dist/data/ai/SessionTail.d.ts +14 -0
  146. package/dist/data/ai/SessionTail.d.ts.map +1 -0
  147. package/dist/data/ai/SessionTail.js +63 -0
  148. package/dist/data/ai/SessionTail.js.map +1 -0
  149. package/dist/data/ai/SessionViewer.cjs +10 -3
  150. package/dist/data/ai/SessionViewer.cjs.map +1 -1
  151. package/dist/data/ai/SessionViewer.d.ts +7 -1
  152. package/dist/data/ai/SessionViewer.d.ts.map +1 -1
  153. package/dist/data/ai/SessionViewer.js +10 -3
  154. package/dist/data/ai/SessionViewer.js.map +1 -1
  155. package/dist/data/ai/SessionViewer.unified.d.ts +14 -0
  156. package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
  157. package/dist/data/ai/index.d.ts +4 -3
  158. package/dist/data/ai/index.d.ts.map +1 -1
  159. package/dist/data/ai/use-session-scroll.cjs +30 -6
  160. package/dist/data/ai/use-session-scroll.cjs.map +1 -1
  161. package/dist/data/ai/use-session-scroll.d.ts +7 -0
  162. package/dist/data/ai/use-session-scroll.d.ts.map +1 -1
  163. package/dist/data/ai/use-session-scroll.js +30 -6
  164. package/dist/data/ai/use-session-scroll.js.map +1 -1
  165. package/dist/data/call-graph/CallGraph.cjs +50 -31
  166. package/dist/data/call-graph/CallGraph.cjs.map +1 -1
  167. package/dist/data/call-graph/CallGraph.d.ts +10 -1
  168. package/dist/data/call-graph/CallGraph.d.ts.map +1 -1
  169. package/dist/data/call-graph/CallGraph.js +52 -33
  170. package/dist/data/call-graph/CallGraph.js.map +1 -1
  171. package/dist/data/call-graph/CallGraphMembers.cjs +5 -4
  172. package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -1
  173. package/dist/data/call-graph/CallGraphMembers.d.ts +1 -1
  174. package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -1
  175. package/dist/data/call-graph/CallGraphMembers.js +5 -4
  176. package/dist/data/call-graph/CallGraphMembers.js.map +1 -1
  177. package/dist/data/call-graph/CallGraphToolbar.cjs +17 -5
  178. package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -1
  179. package/dist/data/call-graph/CallGraphToolbar.d.ts +10 -0
  180. package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -1
  181. package/dist/data/call-graph/CallGraphToolbar.js +18 -6
  182. package/dist/data/call-graph/CallGraphToolbar.js.map +1 -1
  183. package/dist/data/call-graph/call-graph-model.cjs +169 -18
  184. package/dist/data/call-graph/call-graph-model.cjs.map +1 -1
  185. package/dist/data/call-graph/call-graph-model.d.ts +40 -4
  186. package/dist/data/call-graph/call-graph-model.d.ts.map +1 -1
  187. package/dist/data/call-graph/call-graph-model.js +170 -19
  188. package/dist/data/call-graph/call-graph-model.js.map +1 -1
  189. package/dist/data/call-graph/call-graph-vocabulary.cjs +19 -1
  190. package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -1
  191. package/dist/data/call-graph/call-graph-vocabulary.d.ts +4 -0
  192. package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -1
  193. package/dist/data/call-graph/call-graph-vocabulary.js +19 -1
  194. package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -1
  195. package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -1
  196. package/dist/data/call-graph/index.d.ts +2 -2
  197. package/dist/data/call-graph/index.d.ts.map +1 -1
  198. package/dist/data/call-graph/types.cjs.map +1 -1
  199. package/dist/data/call-graph/types.d.ts +7 -2
  200. package/dist/data/call-graph/types.d.ts.map +1 -1
  201. package/dist/data/call-graph/types.js.map +1 -1
  202. package/dist/data/chat/Chat.cjs +1 -1
  203. package/dist/data/chat/Chat.cjs.map +1 -1
  204. package/dist/data/chat/Chat.js +1 -1
  205. package/dist/data/chat/Chat.js.map +1 -1
  206. package/dist/data/chat/ChatRuntimeToolbar.cjs +4 -4
  207. package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
  208. package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
  209. package/dist/data/chat/ChatRuntimeToolbar.js +4 -4
  210. package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
  211. package/dist/data/chat/PromptInput.cjs +4 -2
  212. package/dist/data/chat/PromptInput.cjs.map +1 -1
  213. package/dist/data/chat/PromptInput.d.ts +5 -1
  214. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  215. package/dist/data/chat/PromptInput.js +4 -2
  216. package/dist/data/chat/PromptInput.js.map +1 -1
  217. package/dist/data/data-access.cjs +8 -0
  218. package/dist/data/data-access.cjs.map +1 -0
  219. package/dist/data/data-access.d.ts +5 -0
  220. package/dist/data/data-access.d.ts.map +1 -0
  221. package/dist/data/data-access.js +8 -0
  222. package/dist/data/data-access.js.map +1 -0
  223. package/dist/data/diagnostics/ErrorDetails.cjs +13 -2
  224. package/dist/data/diagnostics/ErrorDetails.cjs.map +1 -1
  225. package/dist/data/diagnostics/ErrorDetails.d.ts +3 -1
  226. package/dist/data/diagnostics/ErrorDetails.d.ts.map +1 -1
  227. package/dist/data/diagnostics/ErrorDetails.js +14 -3
  228. package/dist/data/diagnostics/ErrorDetails.js.map +1 -1
  229. package/dist/data/diagnostics/FrameSourceWindow.cjs +3 -2
  230. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
  231. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
  232. package/dist/data/diagnostics/FrameSourceWindow.js +3 -2
  233. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
  234. package/dist/data/diagnostics/error-diagnostics.cjs +5 -0
  235. package/dist/data/diagnostics/error-diagnostics.cjs.map +1 -1
  236. package/dist/data/diagnostics/error-diagnostics.d.ts +1 -0
  237. package/dist/data/diagnostics/error-diagnostics.d.ts.map +1 -1
  238. package/dist/data/diagnostics/error-diagnostics.js +6 -1
  239. package/dist/data/diagnostics/error-diagnostics.js.map +1 -1
  240. package/dist/data/git/GitChangeList.cjs +6 -2
  241. package/dist/data/git/GitChangeList.cjs.map +1 -1
  242. package/dist/data/git/GitChangeList.d.ts +6 -1
  243. package/dist/data/git/GitChangeList.d.ts.map +1 -1
  244. package/dist/data/git/GitChangeList.js +6 -2
  245. package/dist/data/git/GitChangeList.js.map +1 -1
  246. package/dist/data/graph-columns-layout.cjs +47 -16
  247. package/dist/data/graph-columns-layout.cjs.map +1 -1
  248. package/dist/data/graph-columns-layout.d.ts +34 -0
  249. package/dist/data/graph-columns-layout.d.ts.map +1 -1
  250. package/dist/data/graph-columns-layout.js +47 -16
  251. package/dist/data/graph-columns-layout.js.map +1 -1
  252. package/dist/data/graph-diagram-model.cjs +5 -0
  253. package/dist/data/graph-diagram-model.cjs.map +1 -1
  254. package/dist/data/graph-diagram-model.d.ts +19 -0
  255. package/dist/data/graph-diagram-model.d.ts.map +1 -1
  256. package/dist/data/graph-diagram-model.js +5 -0
  257. package/dist/data/graph-diagram-model.js.map +1 -1
  258. package/dist/data/graph-group-window.cjs +60 -0
  259. package/dist/data/graph-group-window.cjs.map +1 -0
  260. package/dist/data/graph-group-window.d.ts +33 -0
  261. package/dist/data/graph-group-window.d.ts.map +1 -0
  262. package/dist/data/graph-group-window.js +60 -0
  263. package/dist/data/graph-group-window.js.map +1 -0
  264. package/dist/data/har/HarPanel.cjs +3 -1
  265. package/dist/data/har/HarPanel.cjs.map +1 -1
  266. package/dist/data/har/HarPanel.d.ts +4 -1
  267. package/dist/data/har/HarPanel.d.ts.map +1 -1
  268. package/dist/data/har/HarPanel.js +3 -1
  269. package/dist/data/har/HarPanel.js.map +1 -1
  270. package/dist/data/test-runner/Expectation.cjs +57 -0
  271. package/dist/data/test-runner/Expectation.cjs.map +1 -0
  272. package/dist/data/test-runner/Expectation.d.ts +19 -0
  273. package/dist/data/test-runner/Expectation.d.ts.map +1 -0
  274. package/dist/data/test-runner/Expectation.js +57 -0
  275. package/dist/data/test-runner/Expectation.js.map +1 -0
  276. package/dist/data/test-runner/MarkIcons.cjs +38 -0
  277. package/dist/data/test-runner/MarkIcons.cjs.map +1 -0
  278. package/dist/data/test-runner/MarkIcons.d.ts +15 -0
  279. package/dist/data/test-runner/MarkIcons.d.ts.map +1 -0
  280. package/dist/data/test-runner/MarkIcons.js +38 -0
  281. package/dist/data/test-runner/MarkIcons.js.map +1 -0
  282. package/dist/data/test-runner/TestDetailPanel.cjs +74 -42
  283. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  284. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  285. package/dist/data/test-runner/TestDetailPanel.js +76 -44
  286. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  287. package/dist/data/test-runner/TestFailureDetail.cjs +23 -27
  288. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
  289. package/dist/data/test-runner/TestFailureDetail.d.ts +2 -2
  290. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
  291. package/dist/data/test-runner/TestFailureDetail.js +23 -27
  292. package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
  293. package/dist/data/test-runner/TestFilterBar.cjs +87 -93
  294. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -1
  295. package/dist/data/test-runner/TestFilterBar.d.ts +3 -2
  296. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -1
  297. package/dist/data/test-runner/TestFilterBar.js +89 -95
  298. package/dist/data/test-runner/TestFilterBar.js.map +1 -1
  299. package/dist/data/test-runner/TestRunSummary.cjs +35 -21
  300. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
  301. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
  302. package/dist/data/test-runner/TestRunSummary.js +35 -21
  303. package/dist/data/test-runner/TestRunSummary.js.map +1 -1
  304. package/dist/data/test-runner/TestRunner.cjs +52 -30
  305. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  306. package/dist/data/test-runner/TestRunner.d.ts +3 -2
  307. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  308. package/dist/data/test-runner/TestRunner.js +52 -30
  309. package/dist/data/test-runner/TestRunner.js.map +1 -1
  310. package/dist/data/test-runner/TestTree.cjs +5 -2
  311. package/dist/data/test-runner/TestTree.cjs.map +1 -1
  312. package/dist/data/test-runner/TestTree.d.ts +5 -2
  313. package/dist/data/test-runner/TestTree.d.ts.map +1 -1
  314. package/dist/data/test-runner/TestTree.js +5 -2
  315. package/dist/data/test-runner/TestTree.js.map +1 -1
  316. package/dist/data/test-runner/TestTreeNode.cjs +27 -19
  317. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  318. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  319. package/dist/data/test-runner/TestTreeNode.js +28 -20
  320. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  321. package/dist/data/test-runner/adapter.cjs +11 -0
  322. package/dist/data/test-runner/adapter.cjs.map +1 -1
  323. package/dist/data/test-runner/adapter.d.ts +18 -7
  324. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  325. package/dist/data/test-runner/adapter.js +11 -0
  326. package/dist/data/test-runner/adapter.js.map +1 -1
  327. package/dist/data/test-runner/copy.cjs +114 -0
  328. package/dist/data/test-runner/copy.cjs.map +1 -0
  329. package/dist/data/test-runner/copy.d.ts +47 -0
  330. package/dist/data/test-runner/copy.d.ts.map +1 -0
  331. package/dist/data/test-runner/copy.js +114 -0
  332. package/dist/data/test-runner/copy.js.map +1 -0
  333. package/dist/data/test-runner/index.d.ts +5 -2
  334. package/dist/data/test-runner/index.d.ts.map +1 -1
  335. package/dist/data/test-runner/marks.cjs +143 -0
  336. package/dist/data/test-runner/marks.cjs.map +1 -0
  337. package/dist/data/test-runner/marks.d.ts +30 -0
  338. package/dist/data/test-runner/marks.d.ts.map +1 -0
  339. package/dist/data/test-runner/marks.js +143 -0
  340. package/dist/data/test-runner/marks.js.map +1 -0
  341. package/dist/data/test-runner/status.cjs +27 -44
  342. package/dist/data/test-runner/status.cjs.map +1 -1
  343. package/dist/data/test-runner/status.d.ts +12 -3
  344. package/dist/data/test-runner/status.d.ts.map +1 -1
  345. package/dist/data/test-runner/status.js +27 -44
  346. package/dist/data/test-runner/status.js.map +1 -1
  347. package/dist/data/use-group-windows.cjs +50 -0
  348. package/dist/data/use-group-windows.cjs.map +1 -0
  349. package/dist/data/use-group-windows.d.ts +27 -0
  350. package/dist/data/use-group-windows.d.ts.map +1 -0
  351. package/dist/data/use-group-windows.js +50 -0
  352. package/dist/data/use-group-windows.js.map +1 -0
  353. package/dist/data/verification/adapterViews.cjs +16 -41
  354. package/dist/data/verification/adapterViews.cjs.map +1 -1
  355. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  356. package/dist/data/verification/adapterViews.js +16 -41
  357. package/dist/data/verification/adapterViews.js.map +1 -1
  358. package/dist/data/verification/adapters.cjs +115 -3
  359. package/dist/data/verification/adapters.cjs.map +1 -1
  360. package/dist/data/verification/adapters.d.ts.map +1 -1
  361. package/dist/data/verification/adapters.js +115 -3
  362. package/dist/data/verification/adapters.js.map +1 -1
  363. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  364. package/dist/data/verification/verifyReportTests.js.map +1 -1
  365. package/dist/data/version-info.cjs +3 -3
  366. package/dist/data/version-info.js +3 -3
  367. package/dist/data.cjs +54 -3
  368. package/dist/data.cjs.map +1 -1
  369. package/dist/data.d.ts +6 -2
  370. package/dist/data.d.ts.map +1 -1
  371. package/dist/data.js +58 -7
  372. package/dist/data.js.map +1 -1
  373. package/dist/hooks/event-hub.cjs +229 -0
  374. package/dist/hooks/event-hub.cjs.map +1 -0
  375. package/dist/hooks/event-hub.d.ts +26 -0
  376. package/dist/hooks/event-hub.d.ts.map +1 -0
  377. package/dist/hooks/event-hub.js +229 -0
  378. package/dist/hooks/event-hub.js.map +1 -0
  379. package/dist/hooks.cjs +2 -0
  380. package/dist/hooks.cjs.map +1 -1
  381. package/dist/hooks.d.ts +1 -0
  382. package/dist/hooks.d.ts.map +1 -1
  383. package/dist/hooks.js +2 -0
  384. package/dist/hooks.js.map +1 -1
  385. package/dist/icons/components/UiLogoEslint.cjs +10 -0
  386. package/dist/icons/components/UiLogoEslint.cjs.map +1 -0
  387. package/dist/icons/components/UiLogoEslint.d.ts +9 -0
  388. package/dist/icons/components/UiLogoEslint.d.ts.map +1 -0
  389. package/dist/icons/components/UiLogoEslint.js +10 -0
  390. package/dist/icons/components/UiLogoEslint.js.map +1 -0
  391. package/dist/icons/components/UiLogoGo.cjs +10 -0
  392. package/dist/icons/components/UiLogoGo.cjs.map +1 -0
  393. package/dist/icons/components/UiLogoGo.d.ts +9 -0
  394. package/dist/icons/components/UiLogoGo.d.ts.map +1 -0
  395. package/dist/icons/components/UiLogoGo.js +10 -0
  396. package/dist/icons/components/UiLogoGo.js.map +1 -0
  397. package/dist/icons/components/UiLogoJest.cjs +10 -0
  398. package/dist/icons/components/UiLogoJest.cjs.map +1 -0
  399. package/dist/icons/components/UiLogoJest.d.ts +9 -0
  400. package/dist/icons/components/UiLogoJest.d.ts.map +1 -0
  401. package/dist/icons/components/UiLogoJest.js +10 -0
  402. package/dist/icons/components/UiLogoJest.js.map +1 -0
  403. package/dist/icons/components/UiLogoMarkdown.cjs +10 -0
  404. package/dist/icons/components/UiLogoMarkdown.cjs.map +1 -0
  405. package/dist/icons/components/UiLogoMarkdown.d.ts +9 -0
  406. package/dist/icons/components/UiLogoMarkdown.d.ts.map +1 -0
  407. package/dist/icons/components/UiLogoMarkdown.js +10 -0
  408. package/dist/icons/components/UiLogoMarkdown.js.map +1 -0
  409. package/dist/icons/components/UiLogoOxc.cjs +10 -0
  410. package/dist/icons/components/UiLogoOxc.cjs.map +1 -0
  411. package/dist/icons/components/UiLogoOxc.d.ts +9 -0
  412. package/dist/icons/components/UiLogoOxc.d.ts.map +1 -0
  413. package/dist/icons/components/UiLogoOxc.js +10 -0
  414. package/dist/icons/components/UiLogoOxc.js.map +1 -0
  415. package/dist/icons/components/UiLogoPlaywright.cjs +10 -0
  416. package/dist/icons/components/UiLogoPlaywright.cjs.map +1 -0
  417. package/dist/icons/components/UiLogoPlaywright.d.ts +9 -0
  418. package/dist/icons/components/UiLogoPlaywright.d.ts.map +1 -0
  419. package/dist/icons/components/UiLogoPlaywright.js +10 -0
  420. package/dist/icons/components/UiLogoPlaywright.js.map +1 -0
  421. package/dist/icons/components/UiLogoPython.cjs +10 -0
  422. package/dist/icons/components/UiLogoPython.cjs.map +1 -0
  423. package/dist/icons/components/UiLogoPython.d.ts +9 -0
  424. package/dist/icons/components/UiLogoPython.d.ts.map +1 -0
  425. package/dist/icons/components/UiLogoPython.js +10 -0
  426. package/dist/icons/components/UiLogoPython.js.map +1 -0
  427. package/dist/icons/components/UiLogoReact.cjs +10 -0
  428. package/dist/icons/components/UiLogoReact.cjs.map +1 -0
  429. package/dist/icons/components/UiLogoReact.d.ts +9 -0
  430. package/dist/icons/components/UiLogoReact.d.ts.map +1 -0
  431. package/dist/icons/components/UiLogoReact.js +10 -0
  432. package/dist/icons/components/UiLogoReact.js.map +1 -0
  433. package/dist/icons/components/UiLogoRuff.cjs +10 -0
  434. package/dist/icons/components/UiLogoRuff.cjs.map +1 -0
  435. package/dist/icons/components/UiLogoRuff.d.ts +9 -0
  436. package/dist/icons/components/UiLogoRuff.d.ts.map +1 -0
  437. package/dist/icons/components/UiLogoRuff.js +10 -0
  438. package/dist/icons/components/UiLogoRuff.js.map +1 -0
  439. package/dist/icons/components/UiLogoTypescript.cjs +10 -0
  440. package/dist/icons/components/UiLogoTypescript.cjs.map +1 -0
  441. package/dist/icons/components/UiLogoTypescript.d.ts +9 -0
  442. package/dist/icons/components/UiLogoTypescript.d.ts.map +1 -0
  443. package/dist/icons/components/UiLogoTypescript.js +10 -0
  444. package/dist/icons/components/UiLogoTypescript.js.map +1 -0
  445. package/dist/icons/components/UiLogoVitest.cjs +10 -0
  446. package/dist/icons/components/UiLogoVitest.cjs.map +1 -0
  447. package/dist/icons/components/UiLogoVitest.d.ts +9 -0
  448. package/dist/icons/components/UiLogoVitest.d.ts.map +1 -0
  449. package/dist/icons/components/UiLogoVitest.js +10 -0
  450. package/dist/icons/components/UiLogoVitest.js.map +1 -0
  451. package/dist/icons/index.d.ts +11 -0
  452. package/dist/icons/index.d.ts.map +1 -1
  453. package/dist/icons/registry.cjs +22 -0
  454. package/dist/icons/registry.cjs.map +1 -1
  455. package/dist/icons/registry.d.ts.map +1 -1
  456. package/dist/icons/registry.js +22 -0
  457. package/dist/icons/registry.js.map +1 -1
  458. package/dist/icons.cjs +22 -0
  459. package/dist/icons.cjs.map +1 -1
  460. package/dist/icons.js +22 -0
  461. package/dist/icons.js.map +1 -1
  462. package/dist/index.cjs +56 -3
  463. package/dist/index.cjs.map +1 -1
  464. package/dist/index.js +60 -7
  465. package/dist/index.js.map +1 -1
  466. package/dist/overlay/DropdownMenuSubmenu.cjs +4 -4
  467. package/dist/overlay/DropdownMenuSubmenu.cjs.map +1 -1
  468. package/dist/overlay/DropdownMenuSubmenu.js +4 -4
  469. package/dist/overlay/DropdownMenuSubmenu.js.map +1 -1
  470. package/dist/overlay/modalStack.cjs +13 -6
  471. package/dist/overlay/modalStack.cjs.map +1 -1
  472. package/dist/overlay/modalStack.d.ts.map +1 -1
  473. package/dist/overlay/modalStack.js +13 -6
  474. package/dist/overlay/modalStack.js.map +1 -1
  475. package/dist/rpc/InlineError.cjs +12 -43
  476. package/dist/rpc/InlineError.cjs.map +1 -1
  477. package/dist/rpc/InlineError.d.ts.map +1 -1
  478. package/dist/rpc/InlineError.js +13 -44
  479. package/dist/rpc/InlineError.js.map +1 -1
  480. package/dist/rpc/OperationScheduleDialog.cjs +63 -10
  481. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  482. package/dist/rpc/OperationScheduleDialog.d.ts +4 -0
  483. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  484. package/dist/rpc/OperationScheduleDialog.js +63 -10
  485. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  486. package/dist/rpc/OperationSchedules.cjs +12 -2
  487. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  488. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  489. package/dist/rpc/OperationSchedules.js +12 -2
  490. package/dist/rpc/OperationSchedules.js.map +1 -1
  491. package/dist/rpc/operation-schedule-model.cjs +2 -0
  492. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  493. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  494. package/dist/rpc/operation-schedule-model.js +2 -0
  495. package/dist/rpc/operation-schedule-model.js.map +1 -1
  496. package/dist/rpc/operation-schedule-types.d.ts +7 -1
  497. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  498. package/dist/styles.css +1 -1
  499. package/package.json +1 -1
  500. package/dist/data/test-runner/frameworkIcon.cjs +0 -27
  501. package/dist/data/test-runner/frameworkIcon.cjs.map +0 -1
  502. package/dist/data/test-runner/frameworkIcon.d.ts +0 -3
  503. package/dist/data/test-runner/frameworkIcon.d.ts.map +0 -1
  504. package/dist/data/test-runner/frameworkIcon.js +0 -27
  505. package/dist/data/test-runner/frameworkIcon.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Tree.js","sources":["../../src/data/Tree.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiClose, UiCollapseAll, UiExpandAll, UiSearch } from \"../icons\";\nimport { TreeNode, type TreeNodeProps } from \"./TreeNode\";\n\nexport type TreeProps<T> = Omit<\n TreeNodeProps<T>,\n \"node\" | \"depth\" | \"expandAll\" | \"forcedOpenKeys\"\n> & {\n /** Root nodes to render. */\n roots: T[];\n /** Empty-state content when no roots are provided. */\n empty?: ReactNode;\n /** Classes applied to the tree root. */\n className?: string;\n /** Accessible name applied to the tree root. */\n ariaLabel?: string;\n /**\n * Render the Expand all / Collapse all toolbar above the tree. Enabled by\n * default. Set to false to suppress when the tree is tiny or the controls\n * are owned externally.\n */\n showControls?: boolean;\n /** Keep search visible for small or empty trees. */\n showSearch?: boolean;\n /** URL or parent-controlled search query. */\n searchQuery?: string;\n onSearchQueryChange?: (query: string) => void;\n /**\n * Externally controlled expand-all state. When provided together with\n * `onExpandAllChange`, the tree is controlled; otherwise it manages the\n * toolbar state internally.\n */\n expandAll?: boolean | null;\n /** Called when the toolbar expand-all state changes. */\n onExpandAllChange?: (next: boolean | null) => void;\n /** Classes applied to the search/control toolbar. */\n toolbarClassName?: string;\n /**\n * Extra controls rendered in the toolbar's right-hand group, before Expand\n * all / Collapse all. Providing actions shows the toolbar even when the tree\n * is too small for the filter and `showControls` is off.\n */\n actions?: ReactNode;\n /**\n * Override the text the filter matches against for a given node.\n * Defaults to a recursive walk of the node's own fields (excluding\n * its `children` and any secondary children), which works for\n * plain-shape trees but leaks text from nested domain objects —\n * e.g. a SQL Table's Field rows are marked secondary, yet the\n * parent record still carries `record.fields[].field` strings that\n * the default walker would collect. Callers that need precise\n * control over search behaviour should return only the text they\n * want surfaced (the node's own display label / id / type).\n */\n getSearchText?: (node: T) => string;\n /**\n * Force-open the ancestors of the `selected` node so it is always revealed,\n * even when its branch was collapsed (a deep-link, a programmatic selection\n * from outside the tree, or simply a folder the user never opened). While a\n * node is selected its ancestors cannot be collapsed. Off by default; other\n * nodes keep their normal expand state. Requires `selected` to be set.\n */\n revealSelected?: boolean;\n};\n\n// ancestorPathKeys returns the keys of every ancestor on the path from a root\n// down to `target` (excluding target itself — opening the ancestors is what\n// makes target render). Matches by getKey so it is robust to object identity\n// across roots rebuilds. Returns null when target is not found.\nfunction ancestorPathKeys<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n target: T,\n): Set<string | number> | null {\n const targetKey = getKey(target);\n const path: (string | number)[] = [];\n const visit = (node: T): boolean => {\n if (getKey(node) === targetKey) return true;\n for (const child of getChildren(node) ?? []) {\n if (visit(child)) {\n path.push(getKey(node));\n return true;\n }\n }\n return false;\n };\n for (const root of roots) {\n if (visit(root)) return path.length ? new Set(path) : null;\n }\n return null;\n}\n\n// mergeKeySets unions two optional key sets, preserving the null fast-path when\n// both are empty so consumers without a filter or reveal pass null downstream.\nfunction mergeKeySets(\n a: Set<string | number> | null,\n b: Set<string | number> | null,\n): Set<string | number> | null {\n if (!a) return b;\n if (!b) return a;\n return new Set([...a, ...b]);\n}\n\n// Edge counting, search-text collection and filtering all walk the tree through\n// the synchronous `getChildren` only. A lazy node (one declared via\n// `hasMoreChildren` whose children have not been fetched yet) therefore\n// contributes no edges and no search text until the operator expands it — so it\n// is invisible to \"expand all\" and to the filter while still collapsed. This is\n// intentional: we never trigger network loads from bulk/search operations.\nfunction countTreeEdges<T>(roots: T[], getChildren: TreeProps<T>[\"getChildren\"]) {\n let total = 0;\n const stack = [...roots];\n\n while (stack.length > 0) {\n const node = stack.pop()!;\n const children = getChildren(node) ?? [];\n total += children.length;\n stack.push(...children);\n }\n\n return total;\n}\n\nfunction collectTreeSearchText(\n value: unknown,\n excluded = new Set<unknown>(),\n seen = new Set<unknown>(),\n): string {\n if (value == null || excluded.has(value)) return \"\";\n\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n ) {\n return String(value);\n }\n\n if (typeof value !== \"object\") return \"\";\n if (seen.has(value)) return \"\";\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value\n .map((entry) => collectTreeSearchText(entry, excluded, seen))\n .filter(Boolean)\n .join(\" \");\n }\n\n const preferredKeys = [\"label\", \"name\", \"title\", \"text\", \"plain\", \"id\", \"content\"];\n const record = value as Record<string, unknown>;\n const chunks: string[] = [];\n\n for (const key of preferredKeys) {\n if (!(key in record)) continue;\n const text = collectTreeSearchText(record[key], excluded, seen);\n if (text) chunks.push(text);\n }\n\n for (const [key, entry] of Object.entries(record)) {\n if (key === \"children\" || preferredKeys.includes(key)) continue;\n const text = collectTreeSearchText(entry, excluded, seen);\n if (text) chunks.push(text);\n }\n\n return chunks.join(\" \");\n}\n\nfunction nodeSearchText<T>(node: T, children: ReturnType<TreeProps<T>[\"getChildren\"]>) {\n const excluded = new Set<unknown>();\n if (children) excluded.add(children);\n return collectTreeSearchText(node, excluded).trim().toLowerCase();\n}\n\nfunction filterTreeRoots<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n query: string,\n isSecondary?: (node: T) => boolean,\n getSearchText?: (node: T) => string,\n) {\n const normalizedQuery = query.trim().toLowerCase();\n if (!normalizedQuery) {\n return {\n roots,\n filteredChildren: null as Map<string | number, T[]> | null,\n forcedOpenKeys: null as Set<string | number> | null,\n };\n }\n\n const filteredChildren = new Map<string | number, T[]>();\n const forcedOpenKeys = new Set<string | number>();\n\n function matchText(node: T, children: T[]): string {\n // Honour the caller-supplied text extractor first — it's the\n // only way to get precise control over what the filter sees when\n // the node carries domain data whose fields would otherwise leak\n // into the default walker.\n if (getSearchText) return getSearchText(node).trim().toLowerCase();\n // Match-text skips secondary children so e.g. a Table's search\n // text only draws from the Table itself, not from its Field\n // rows. The Table is primary, so its own text still counts.\n const searchChildren = children.filter((c) => !isSecondary?.(c));\n return nodeSearchText(node, searchChildren);\n }\n\n function visit(node: T): boolean {\n // A secondary node never drives a filter match — it's\n // effectively invisible to search. Still rendered when the\n // caller opens the parent manually. This is what lets a 28k-row\n // field tree stay searchable at the record level without \"field\n // name contains 'x'\" matches spamming the results.\n if (isSecondary?.(node)) return false;\n\n const key = getKey(node);\n const children = getChildren(node) ?? [];\n const childrenAllSecondary =\n children.length > 0 && children.every((child) => isSecondary?.(child));\n const matches = matchText(node, children).includes(normalizedQuery);\n const visibleChildren: T[] = [];\n\n for (const child of children) {\n if (visit(child)) visibleChildren.push(child);\n }\n\n if (matches) {\n if (children.length > 0) {\n filteredChildren.set(key, children);\n if (!childrenAllSecondary) forcedOpenKeys.add(key);\n }\n return true;\n }\n\n if (visibleChildren.length > 0) {\n filteredChildren.set(key, visibleChildren);\n forcedOpenKeys.add(key);\n return true;\n }\n\n return false;\n }\n\n return {\n roots: roots.filter((root) => visit(root)),\n filteredChildren,\n forcedOpenKeys,\n };\n}\n\nexport function Tree<T>({\n roots,\n empty,\n className,\n ariaLabel,\n showControls = true,\n showSearch = false,\n searchQuery,\n onSearchQueryChange,\n expandAll: controlledExpandAll,\n onExpandAllChange,\n toolbarClassName,\n actions,\n getSearchText,\n revealSelected = false,\n ...nodeProps\n}: TreeProps<T>) {\n const [internalExpandAll, setInternalExpandAll] = useState<boolean | null>(null);\n const [internalFilterQuery, setInternalFilterQuery] = useState(\"\");\n const filterQuery = searchQuery ?? internalFilterQuery;\n const setFilterQuery = (next: string) => {\n if (searchQuery !== undefined) onSearchQueryChange?.(next);\n else setInternalFilterQuery(next);\n };\n const basePaddingPx = nodeProps.basePaddingPx ?? 8;\n const isControlled = onExpandAllChange !== undefined;\n const expandAll = isControlled ? (controlledExpandAll ?? null) : internalExpandAll;\n const totalEdges = useMemo(\n () => countTreeEdges(roots, nodeProps.getChildren),\n [roots, nodeProps.getChildren],\n );\n const showFilter = showSearch || totalEdges > 20 || Boolean(searchQuery);\n const activeFilter = showFilter ? filterQuery : \"\";\n const filteredTree = useMemo(\n () =>\n filterTreeRoots(\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ),\n [\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ],\n );\n const treeRoots = filteredTree.roots;\n const filteredChildren = filteredTree.filteredChildren;\n // Force-open the selected node's ancestors when revealSelected is set, unioned\n // with the filter's own forced-open keys. Recomputed when the selection moves\n // so the tree follows it without a remount.\n const revealKeys = useMemo(\n () =>\n revealSelected && nodeProps.selected\n ? ancestorPathKeys(roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected)\n : null,\n [revealSelected, roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected],\n );\n const forcedOpenKeys = useMemo(\n () => mergeKeySets(filteredTree.forcedOpenKeys, revealKeys),\n [filteredTree.forcedOpenKeys, revealKeys],\n );\n const effectiveGetChildren = filteredChildren\n ? (node: T) => filteredChildren.get(nodeProps.getKey(node)) ?? []\n : nodeProps.getChildren;\n const effectiveExpandAll = expandAll;\n const effectiveEmpty = activeFilter.trim() ? (\n <div className=\"px-3 py-4 text-sm text-muted-foreground\">No matching tree nodes.</div>\n ) : (\n empty\n );\n const showControlButtons = showControls && roots.length > 0;\n const showVirtualRow = showFilter || showControlButtons || actions != null;\n const setExpandAll = (next: boolean | null) => {\n if (isControlled) onExpandAllChange?.(next);\n else setInternalExpandAll(next);\n };\n\n if (treeRoots.length === 0 && !showVirtualRow) return <>{effectiveEmpty ?? null}</>;\n\n return (\n <div className={cn(\"flex flex-col min-h-0\", className)}>\n <div\n role=\"tree\"\n aria-label={ariaLabel}\n className=\"min-h-0 flex-1 overflow-auto\"\n >\n {showVirtualRow && (\n <div\n role=\"presentation\"\n className={cn(\n \"sticky top-0 z-10 flex items-center gap-1.5 border-b border-border/70 bg-background/95 py-1 pr-2 text-sm backdrop-blur supports-[backdrop-filter]:bg-background/80\",\n toolbarClassName,\n )}\n style={{ paddingLeft: `${basePaddingPx}px` }}\n >\n {showFilter ? (\n <Icon icon={UiSearch} className=\"w-3 shrink-0 text-xs text-muted-foreground\" />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n\n {showFilter ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground shadow-sm\">\n <input\n type=\"search\"\n value={filterQuery}\n onChange={(event) => setFilterQuery(event.target.value)}\n placeholder=\"Filter tree\"\n className=\"h-5 w-full min-w-0 border-0 bg-transparent p-0 text-xs outline-none placeholder:text-placeholder\"\n aria-label=\"Filter tree nodes\"\n />\n {filterQuery && (\n <button\n type=\"button\"\n onClick={() => setFilterQuery(\"\")}\n className=\"inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n aria-label=\"Clear tree filter\"\n title=\"Clear tree filter\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n </label>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {(showControlButtons || actions != null) && (\n <div className=\"ml-auto flex items-center gap-1\">\n {actions}\n {showControlButtons && (\n <>\n <button\n type=\"button\"\n onClick={() => setExpandAll(true)}\n aria-pressed={effectiveExpandAll === true}\n aria-label=\"Expand all\"\n title=\"Expand all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === true && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiExpandAll} className=\"text-sm\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setExpandAll(false)}\n aria-pressed={effectiveExpandAll === false}\n aria-label=\"Collapse all\"\n title=\"Collapse all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === false && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiCollapseAll} className=\"text-sm\" />\n </button>\n </>\n )}\n </div>\n )}\n </div>\n )}\n\n {treeRoots.length === 0 ? (\n (effectiveEmpty ?? null)\n ) : (\n <>\n {treeRoots.map((root) => (\n <TreeNode<T>\n {...nodeProps}\n key={nodeProps.getKey(root)}\n node={root}\n expandAll={effectiveExpandAll}\n forcedOpenKeys={forcedOpenKeys}\n getChildren={effectiveGetChildren}\n />\n ))}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Tree.js","sources":["../../src/data/Tree.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiClose, UiCollapseAll, UiExpandAll, UiSearch } from \"../icons\";\nimport { TreeNode, type TreeNodeProps } from \"./TreeNode\";\n\nexport type TreeProps<T> = Omit<\n TreeNodeProps<T>,\n \"node\" | \"depth\" | \"expandAll\" | \"forcedOpenKeys\"\n> & {\n /** Root nodes to render. */\n roots: T[];\n /** Empty-state content when no roots are provided. */\n empty?: ReactNode;\n /** Classes applied to the tree root. */\n className?: string;\n /** Accessible name applied to the tree root. */\n ariaLabel?: string;\n /**\n * Render the Expand all / Collapse all toolbar above the tree. Enabled by\n * default. Set to false to suppress when the tree is tiny or the controls\n * are owned externally.\n */\n showControls?: boolean;\n /** Keep search visible for small or empty trees. */\n showSearch?: boolean;\n /** Placeholder and accessible name of the search box, e.g. \"Filter tests\". */\n searchLabel?: string;\n /** URL or parent-controlled search query. */\n searchQuery?: string;\n onSearchQueryChange?: (query: string) => void;\n /**\n * Externally controlled expand-all state. When provided together with\n * `onExpandAllChange`, the tree is controlled; otherwise it manages the\n * toolbar state internally.\n */\n expandAll?: boolean | null;\n /** Called when the toolbar expand-all state changes. */\n onExpandAllChange?: (next: boolean | null) => void;\n /** Classes applied to the search/control toolbar. */\n toolbarClassName?: string;\n /**\n * Extra controls rendered in the toolbar's right-hand group, before Expand\n * all / Collapse all. Providing actions shows the toolbar even when the tree\n * is too small for the filter and `showControls` is off.\n */\n actions?: ReactNode;\n /**\n * Override the text the filter matches against for a given node.\n * Defaults to a recursive walk of the node's own fields (excluding\n * its `children` and any secondary children), which works for\n * plain-shape trees but leaks text from nested domain objects —\n * e.g. a SQL Table's Field rows are marked secondary, yet the\n * parent record still carries `record.fields[].field` strings that\n * the default walker would collect. Callers that need precise\n * control over search behaviour should return only the text they\n * want surfaced (the node's own display label / id / type).\n */\n getSearchText?: (node: T) => string;\n /**\n * Force-open the ancestors of the `selected` node so it is always revealed,\n * even when its branch was collapsed (a deep-link, a programmatic selection\n * from outside the tree, or simply a folder the user never opened). While a\n * node is selected its ancestors cannot be collapsed. Off by default; other\n * nodes keep their normal expand state. Requires `selected` to be set.\n */\n revealSelected?: boolean;\n};\n\n// ancestorPathKeys returns the keys of every ancestor on the path from a root\n// down to `target` (excluding target itself — opening the ancestors is what\n// makes target render). Matches by getKey so it is robust to object identity\n// across roots rebuilds. Returns null when target is not found.\nfunction ancestorPathKeys<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n target: T,\n): Set<string | number> | null {\n const targetKey = getKey(target);\n const path: (string | number)[] = [];\n const visit = (node: T): boolean => {\n if (getKey(node) === targetKey) return true;\n for (const child of getChildren(node) ?? []) {\n if (visit(child)) {\n path.push(getKey(node));\n return true;\n }\n }\n return false;\n };\n for (const root of roots) {\n if (visit(root)) return path.length ? new Set(path) : null;\n }\n return null;\n}\n\n// mergeKeySets unions two optional key sets, preserving the null fast-path when\n// both are empty so consumers without a filter or reveal pass null downstream.\nfunction mergeKeySets(\n a: Set<string | number> | null,\n b: Set<string | number> | null,\n): Set<string | number> | null {\n if (!a) return b;\n if (!b) return a;\n return new Set([...a, ...b]);\n}\n\n// Edge counting, search-text collection and filtering all walk the tree through\n// the synchronous `getChildren` only. A lazy node (one declared via\n// `hasMoreChildren` whose children have not been fetched yet) therefore\n// contributes no edges and no search text until the operator expands it — so it\n// is invisible to \"expand all\" and to the filter while still collapsed. This is\n// intentional: we never trigger network loads from bulk/search operations.\nfunction countTreeEdges<T>(roots: T[], getChildren: TreeProps<T>[\"getChildren\"]) {\n let total = 0;\n const stack = [...roots];\n\n while (stack.length > 0) {\n const node = stack.pop()!;\n const children = getChildren(node) ?? [];\n total += children.length;\n stack.push(...children);\n }\n\n return total;\n}\n\nfunction collectTreeSearchText(\n value: unknown,\n excluded = new Set<unknown>(),\n seen = new Set<unknown>(),\n): string {\n if (value == null || excluded.has(value)) return \"\";\n\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n ) {\n return String(value);\n }\n\n if (typeof value !== \"object\") return \"\";\n if (seen.has(value)) return \"\";\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value\n .map((entry) => collectTreeSearchText(entry, excluded, seen))\n .filter(Boolean)\n .join(\" \");\n }\n\n const preferredKeys = [\"label\", \"name\", \"title\", \"text\", \"plain\", \"id\", \"content\"];\n const record = value as Record<string, unknown>;\n const chunks: string[] = [];\n\n for (const key of preferredKeys) {\n if (!(key in record)) continue;\n const text = collectTreeSearchText(record[key], excluded, seen);\n if (text) chunks.push(text);\n }\n\n for (const [key, entry] of Object.entries(record)) {\n if (key === \"children\" || preferredKeys.includes(key)) continue;\n const text = collectTreeSearchText(entry, excluded, seen);\n if (text) chunks.push(text);\n }\n\n return chunks.join(\" \");\n}\n\nfunction nodeSearchText<T>(node: T, children: ReturnType<TreeProps<T>[\"getChildren\"]>) {\n const excluded = new Set<unknown>();\n if (children) excluded.add(children);\n return collectTreeSearchText(node, excluded).trim().toLowerCase();\n}\n\nfunction filterTreeRoots<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n query: string,\n isSecondary?: (node: T) => boolean,\n getSearchText?: (node: T) => string,\n) {\n const normalizedQuery = query.trim().toLowerCase();\n if (!normalizedQuery) {\n return {\n roots,\n filteredChildren: null as Map<string | number, T[]> | null,\n forcedOpenKeys: null as Set<string | number> | null,\n };\n }\n\n const filteredChildren = new Map<string | number, T[]>();\n const forcedOpenKeys = new Set<string | number>();\n\n function matchText(node: T, children: T[]): string {\n // Honour the caller-supplied text extractor first — it's the\n // only way to get precise control over what the filter sees when\n // the node carries domain data whose fields would otherwise leak\n // into the default walker.\n if (getSearchText) return getSearchText(node).trim().toLowerCase();\n // Match-text skips secondary children so e.g. a Table's search\n // text only draws from the Table itself, not from its Field\n // rows. The Table is primary, so its own text still counts.\n const searchChildren = children.filter((c) => !isSecondary?.(c));\n return nodeSearchText(node, searchChildren);\n }\n\n function visit(node: T): boolean {\n // A secondary node never drives a filter match — it's\n // effectively invisible to search. Still rendered when the\n // caller opens the parent manually. This is what lets a 28k-row\n // field tree stay searchable at the record level without \"field\n // name contains 'x'\" matches spamming the results.\n if (isSecondary?.(node)) return false;\n\n const key = getKey(node);\n const children = getChildren(node) ?? [];\n const childrenAllSecondary =\n children.length > 0 && children.every((child) => isSecondary?.(child));\n const matches = matchText(node, children).includes(normalizedQuery);\n const visibleChildren: T[] = [];\n\n for (const child of children) {\n if (visit(child)) visibleChildren.push(child);\n }\n\n if (matches) {\n if (children.length > 0) {\n filteredChildren.set(key, children);\n if (!childrenAllSecondary) forcedOpenKeys.add(key);\n }\n return true;\n }\n\n if (visibleChildren.length > 0) {\n filteredChildren.set(key, visibleChildren);\n forcedOpenKeys.add(key);\n return true;\n }\n\n return false;\n }\n\n return {\n roots: roots.filter((root) => visit(root)),\n filteredChildren,\n forcedOpenKeys,\n };\n}\n\nexport function Tree<T>({\n roots,\n empty,\n className,\n ariaLabel,\n showControls = true,\n showSearch = false,\n searchLabel,\n searchQuery,\n onSearchQueryChange,\n expandAll: controlledExpandAll,\n onExpandAllChange,\n toolbarClassName,\n actions,\n getSearchText,\n revealSelected = false,\n ...nodeProps\n}: TreeProps<T>) {\n const [internalExpandAll, setInternalExpandAll] = useState<boolean | null>(null);\n const [internalFilterQuery, setInternalFilterQuery] = useState(\"\");\n const filterQuery = searchQuery ?? internalFilterQuery;\n const setFilterQuery = (next: string) => {\n if (searchQuery !== undefined) onSearchQueryChange?.(next);\n else setInternalFilterQuery(next);\n };\n const basePaddingPx = nodeProps.basePaddingPx ?? 8;\n const isControlled = onExpandAllChange !== undefined;\n const expandAll = isControlled ? (controlledExpandAll ?? null) : internalExpandAll;\n const totalEdges = useMemo(\n () => countTreeEdges(roots, nodeProps.getChildren),\n [roots, nodeProps.getChildren],\n );\n const showFilter = showSearch || totalEdges > 20 || Boolean(searchQuery);\n const activeFilter = showFilter ? filterQuery : \"\";\n const filteredTree = useMemo(\n () =>\n filterTreeRoots(\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ),\n [\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ],\n );\n const treeRoots = filteredTree.roots;\n const filteredChildren = filteredTree.filteredChildren;\n // Force-open the selected node's ancestors when revealSelected is set, unioned\n // with the filter's own forced-open keys. Recomputed when the selection moves\n // so the tree follows it without a remount.\n const revealKeys = useMemo(\n () =>\n revealSelected && nodeProps.selected\n ? ancestorPathKeys(roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected)\n : null,\n [revealSelected, roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected],\n );\n const forcedOpenKeys = useMemo(\n () => mergeKeySets(filteredTree.forcedOpenKeys, revealKeys),\n [filteredTree.forcedOpenKeys, revealKeys],\n );\n const effectiveGetChildren = filteredChildren\n ? (node: T) => filteredChildren.get(nodeProps.getKey(node)) ?? []\n : nodeProps.getChildren;\n const effectiveExpandAll = expandAll;\n const effectiveEmpty = activeFilter.trim() ? (\n <div className=\"px-3 py-4 text-sm text-muted-foreground\">No matching tree nodes.</div>\n ) : (\n empty\n );\n const showControlButtons = showControls && roots.length > 0;\n const showVirtualRow = showFilter || showControlButtons || actions != null;\n const setExpandAll = (next: boolean | null) => {\n if (isControlled) onExpandAllChange?.(next);\n else setInternalExpandAll(next);\n };\n\n if (treeRoots.length === 0 && !showVirtualRow) return <>{effectiveEmpty ?? null}</>;\n\n return (\n <div className={cn(\"flex flex-col min-h-0\", className)}>\n <div\n role=\"tree\"\n aria-label={ariaLabel}\n className=\"min-h-0 flex-1 overflow-auto\"\n >\n {showVirtualRow && (\n <div\n role=\"presentation\"\n className={cn(\n \"sticky top-0 z-10 flex items-center gap-1.5 border-b border-border/70 bg-background/95 py-1 pr-2 text-sm backdrop-blur supports-[backdrop-filter]:bg-background/80\",\n toolbarClassName,\n )}\n style={{ paddingLeft: `${basePaddingPx}px` }}\n >\n {showFilter ? (\n <Icon icon={UiSearch} className=\"w-3 shrink-0 text-xs text-muted-foreground\" />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n\n {showFilter ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground shadow-sm\">\n <input\n type=\"search\"\n value={filterQuery}\n onChange={(event) => setFilterQuery(event.target.value)}\n placeholder={searchLabel ?? \"Filter tree\"}\n className=\"h-5 w-full min-w-0 border-0 bg-transparent p-0 text-xs outline-none placeholder:text-placeholder [&::-webkit-search-cancel-button]:appearance-none\"\n aria-label={searchLabel ?? \"Filter tree nodes\"}\n />\n {filterQuery && (\n <button\n type=\"button\"\n onClick={() => setFilterQuery(\"\")}\n className=\"inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n aria-label=\"Clear tree filter\"\n title=\"Clear tree filter\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n </label>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {(showControlButtons || actions != null) && (\n <div className=\"ml-auto flex items-center gap-1\">\n {actions}\n {showControlButtons && (\n <>\n <button\n type=\"button\"\n onClick={() => setExpandAll(true)}\n aria-pressed={effectiveExpandAll === true}\n aria-label=\"Expand all\"\n title=\"Expand all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === true && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiExpandAll} className=\"text-sm\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setExpandAll(false)}\n aria-pressed={effectiveExpandAll === false}\n aria-label=\"Collapse all\"\n title=\"Collapse all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === false && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiCollapseAll} className=\"text-sm\" />\n </button>\n </>\n )}\n </div>\n )}\n </div>\n )}\n\n {treeRoots.length === 0 ? (\n (effectiveEmpty ?? null)\n ) : (\n <>\n {treeRoots.map((root) => (\n <TreeNode<T>\n {...nodeProps}\n key={nodeProps.getKey(root)}\n node={root}\n expandAll={effectiveExpandAll}\n forcedOpenKeys={forcedOpenKeys}\n getChildren={effectiveGetChildren}\n />\n ))}\n </>\n )}\n </div>\n </div>\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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"}
@@ -28,7 +28,7 @@ function ChatFab({
28
28
  },
29
29
  ...!persistent ? { style: { zIndex: zIndex.zIndex.chatFab } } : {},
30
30
  className: utils.cn(
31
- "fixed bottom-4 right-4 flex h-12 w-12 items-center justify-center",
31
+ "fixed bottom-[calc(1rem+env(safe-area-inset-bottom))] right-4 flex h-12 w-12 items-center justify-center",
32
32
  "rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90",
33
33
  className
34
34
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"ChatFab.cjs","sources":["../../../src/data/ai/ChatFab.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { UiSparkles } from \"../../icons\";\nimport { useChatWindowManager } from \"./chat-window-context\";\nimport type { ChatContextItem } from \"./context\";\nimport { zIndex } from \"../../overlay/zIndex\";\n\nexport type ChatFabProps = {\n /** Iconify name or imported icon component shown in the button. */\n icon?: string | StaticIconComponent;\n /** Accessible label / tooltip. */\n label?: string;\n /** Keep navbar chrome visible and focus an existing window when clicked. */\n persistent?: boolean;\n /** Context the page offers the chat: attached to a newly opened window, or\n * merged (by id) into the window a persistent launcher focuses. */\n contextItems?: ChatContextItem[];\n className?: string;\n};\n\n/** A fixed bottom-right chat launcher. By default it hides while a window is\n * open; persistent launchers remain visible and focus the existing window. */\nexport function ChatFab({\n icon = UiSparkles,\n label = \"Open chat\",\n persistent = false,\n contextItems,\n className,\n}: ChatFabProps) {\n const { panels, openPanel, findOrCreatePanel } = useChatWindowManager();\n if (!persistent && panels.length > 0) return null;\n\n return (\n <button\n type=\"button\"\n data-testid=\"chat-fab\"\n title={label}\n aria-label={label}\n onClick={() => {\n const opts = contextItems ? { contextItems } : {};\n return persistent ? findOrCreatePanel(opts) : openPanel(opts);\n }}\n {...(!persistent ? { style: { zIndex: zIndex.chatFab } } : {})}\n className={cn(\n \"fixed bottom-4 right-4 flex h-12 w-12 items-center justify-center\",\n \"rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90\",\n className,\n )}\n >\n <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} className=\"size-5\" />\n </button>\n );\n}\n"],"names":["UiSparkles","useChatWindowManager","jsx","zIndex","cn","Icon"],"mappings":";;;;;;;;AAsBO,SAAS,QAAQ;AAAA,EACtB,OAAOA,WAAAA;AAAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb;AAAA,EACA;AACF,GAAiB;AACf,QAAM,EAAE,QAAQ,WAAW,kBAAA,IAAsBC,kBAAAA,qBAAA;AACjD,MAAI,CAAC,cAAc,OAAO,SAAS,EAAG,QAAO;AAE7C,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,SAAS,MAAM;AACb,cAAM,OAAO,eAAe,EAAE,aAAA,IAAiB,CAAA;AAC/C,eAAO,aAAa,kBAAkB,IAAI,IAAI,UAAU,IAAI;AAAA,MAC9D;AAAA,MACC,GAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQC,cAAO,QAAA,EAAQ,IAAM,CAAA;AAAA,MAC3D,WAAWC,MAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAAF,2BAAAA,IAACG,KAAAA,MAAA,EAAM,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAA,IAAS,EAAE,KAAA,GAAS,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAG3F;;"}
1
+ {"version":3,"file":"ChatFab.cjs","sources":["../../../src/data/ai/ChatFab.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { UiSparkles } from \"../../icons\";\nimport { useChatWindowManager } from \"./chat-window-context\";\nimport type { ChatContextItem } from \"./context\";\nimport { zIndex } from \"../../overlay/zIndex\";\n\nexport type ChatFabProps = {\n /** Iconify name or imported icon component shown in the button. */\n icon?: string | StaticIconComponent;\n /** Accessible label / tooltip. */\n label?: string;\n /** Keep navbar chrome visible and focus an existing window when clicked. */\n persistent?: boolean;\n /** Context the page offers the chat: attached to a newly opened window, or\n * merged (by id) into the window a persistent launcher focuses. */\n contextItems?: ChatContextItem[];\n className?: string;\n};\n\n/** A fixed bottom-right chat launcher. By default it hides while a window is\n * open; persistent launchers remain visible and focus the existing window. */\nexport function ChatFab({\n icon = UiSparkles,\n label = \"Open chat\",\n persistent = false,\n contextItems,\n className,\n}: ChatFabProps) {\n const { panels, openPanel, findOrCreatePanel } = useChatWindowManager();\n if (!persistent && panels.length > 0) return null;\n\n return (\n <button\n type=\"button\"\n data-testid=\"chat-fab\"\n title={label}\n aria-label={label}\n onClick={() => {\n const opts = contextItems ? { contextItems } : {};\n return persistent ? findOrCreatePanel(opts) : openPanel(opts);\n }}\n {...(!persistent ? { style: { zIndex: zIndex.chatFab } } : {})}\n className={cn(\n \"fixed bottom-[calc(1rem+env(safe-area-inset-bottom))] right-4 flex h-12 w-12 items-center justify-center\",\n \"rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90\",\n className,\n )}\n >\n <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} className=\"size-5\" />\n </button>\n );\n}\n"],"names":["UiSparkles","useChatWindowManager","jsx","zIndex","cn","Icon"],"mappings":";;;;;;;;AAsBO,SAAS,QAAQ;AAAA,EACtB,OAAOA,WAAAA;AAAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb;AAAA,EACA;AACF,GAAiB;AACf,QAAM,EAAE,QAAQ,WAAW,kBAAA,IAAsBC,kBAAAA,qBAAA;AACjD,MAAI,CAAC,cAAc,OAAO,SAAS,EAAG,QAAO;AAE7C,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,SAAS,MAAM;AACb,cAAM,OAAO,eAAe,EAAE,aAAA,IAAiB,CAAA;AAC/C,eAAO,aAAa,kBAAkB,IAAI,IAAI,UAAU,IAAI;AAAA,MAC9D;AAAA,MACC,GAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQC,cAAO,QAAA,EAAQ,IAAM,CAAA;AAAA,MAC3D,WAAWC,MAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAAF,2BAAAA,IAACG,KAAAA,MAAA,EAAM,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAA,IAAS,EAAE,KAAA,GAAS,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAG3F;;"}
@@ -26,7 +26,7 @@ function ChatFab({
26
26
  },
27
27
  ...!persistent ? { style: { zIndex: zIndex.chatFab } } : {},
28
28
  className: cn(
29
- "fixed bottom-4 right-4 flex h-12 w-12 items-center justify-center",
29
+ "fixed bottom-[calc(1rem+env(safe-area-inset-bottom))] right-4 flex h-12 w-12 items-center justify-center",
30
30
  "rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90",
31
31
  className
32
32
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"ChatFab.js","sources":["../../../src/data/ai/ChatFab.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { UiSparkles } from \"../../icons\";\nimport { useChatWindowManager } from \"./chat-window-context\";\nimport type { ChatContextItem } from \"./context\";\nimport { zIndex } from \"../../overlay/zIndex\";\n\nexport type ChatFabProps = {\n /** Iconify name or imported icon component shown in the button. */\n icon?: string | StaticIconComponent;\n /** Accessible label / tooltip. */\n label?: string;\n /** Keep navbar chrome visible and focus an existing window when clicked. */\n persistent?: boolean;\n /** Context the page offers the chat: attached to a newly opened window, or\n * merged (by id) into the window a persistent launcher focuses. */\n contextItems?: ChatContextItem[];\n className?: string;\n};\n\n/** A fixed bottom-right chat launcher. By default it hides while a window is\n * open; persistent launchers remain visible and focus the existing window. */\nexport function ChatFab({\n icon = UiSparkles,\n label = \"Open chat\",\n persistent = false,\n contextItems,\n className,\n}: ChatFabProps) {\n const { panels, openPanel, findOrCreatePanel } = useChatWindowManager();\n if (!persistent && panels.length > 0) return null;\n\n return (\n <button\n type=\"button\"\n data-testid=\"chat-fab\"\n title={label}\n aria-label={label}\n onClick={() => {\n const opts = contextItems ? { contextItems } : {};\n return persistent ? findOrCreatePanel(opts) : openPanel(opts);\n }}\n {...(!persistent ? { style: { zIndex: zIndex.chatFab } } : {})}\n className={cn(\n \"fixed bottom-4 right-4 flex h-12 w-12 items-center justify-center\",\n \"rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90\",\n className,\n )}\n >\n <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} className=\"size-5\" />\n </button>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsBO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb;AAAA,EACA;AACF,GAAiB;AACf,QAAM,EAAE,QAAQ,WAAW,kBAAA,IAAsB,qBAAA;AACjD,MAAI,CAAC,cAAc,OAAO,SAAS,EAAG,QAAO;AAE7C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,SAAS,MAAM;AACb,cAAM,OAAO,eAAe,EAAE,aAAA,IAAiB,CAAA;AAC/C,eAAO,aAAa,kBAAkB,IAAI,IAAI,UAAU,IAAI;AAAA,MAC9D;AAAA,MACC,GAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,OAAO,QAAA,EAAQ,IAAM,CAAA;AAAA,MAC3D,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA,oBAAC,MAAA,EAAM,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAA,IAAS,EAAE,KAAA,GAAS,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAG3F;"}
1
+ {"version":3,"file":"ChatFab.js","sources":["../../../src/data/ai/ChatFab.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { UiSparkles } from \"../../icons\";\nimport { useChatWindowManager } from \"./chat-window-context\";\nimport type { ChatContextItem } from \"./context\";\nimport { zIndex } from \"../../overlay/zIndex\";\n\nexport type ChatFabProps = {\n /** Iconify name or imported icon component shown in the button. */\n icon?: string | StaticIconComponent;\n /** Accessible label / tooltip. */\n label?: string;\n /** Keep navbar chrome visible and focus an existing window when clicked. */\n persistent?: boolean;\n /** Context the page offers the chat: attached to a newly opened window, or\n * merged (by id) into the window a persistent launcher focuses. */\n contextItems?: ChatContextItem[];\n className?: string;\n};\n\n/** A fixed bottom-right chat launcher. By default it hides while a window is\n * open; persistent launchers remain visible and focus the existing window. */\nexport function ChatFab({\n icon = UiSparkles,\n label = \"Open chat\",\n persistent = false,\n contextItems,\n className,\n}: ChatFabProps) {\n const { panels, openPanel, findOrCreatePanel } = useChatWindowManager();\n if (!persistent && panels.length > 0) return null;\n\n return (\n <button\n type=\"button\"\n data-testid=\"chat-fab\"\n title={label}\n aria-label={label}\n onClick={() => {\n const opts = contextItems ? { contextItems } : {};\n return persistent ? findOrCreatePanel(opts) : openPanel(opts);\n }}\n {...(!persistent ? { style: { zIndex: zIndex.chatFab } } : {})}\n className={cn(\n \"fixed bottom-[calc(1rem+env(safe-area-inset-bottom))] right-4 flex h-12 w-12 items-center justify-center\",\n \"rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:bg-primary/90\",\n className,\n )}\n >\n <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} className=\"size-5\" />\n </button>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsBO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb;AAAA,EACA;AACF,GAAiB;AACf,QAAM,EAAE,QAAQ,WAAW,kBAAA,IAAsB,qBAAA;AACjD,MAAI,CAAC,cAAc,OAAO,SAAS,EAAG,QAAO;AAE7C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,SAAS,MAAM;AACb,cAAM,OAAO,eAAe,EAAE,aAAA,IAAiB,CAAA;AAC/C,eAAO,aAAa,kBAAkB,IAAI,IAAI,UAAU,IAAI;AAAA,MAC9D;AAAA,MACC,GAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,OAAO,QAAA,EAAQ,IAAM,CAAA;AAAA,MAC3D,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA,oBAAC,MAAA,EAAM,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAA,IAAS,EAAE,KAAA,GAAS,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAG3F;"}