@burtson-labs/ui 0.13.1 → 0.14.1

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 (460) hide show
  1. package/LICENSES/DEPENDENCIES.txt +2745 -0
  2. package/LICENSES/Radix-MIT.txt +21 -0
  3. package/LICENSES/cmdk-MIT.txt +21 -0
  4. package/LICENSES/overrides/react-remove-scroll-bar.txt +26 -0
  5. package/LICENSES/shadcn-MIT.txt +21 -0
  6. package/README.md +20 -12
  7. package/THIRD_PARTY_NOTICES.md +15 -3
  8. package/dist/components/accordion.d.ts +4 -4
  9. package/dist/components/accordion.js +22 -18
  10. package/dist/components/accordion.js.map +1 -1
  11. package/dist/components/alert-dialog.d.ts +13 -11
  12. package/dist/components/alert-dialog.js +41 -31
  13. package/dist/components/alert-dialog.js.map +1 -1
  14. package/dist/components/alert.d.ts +5 -3
  15. package/dist/components/alert.js +12 -7
  16. package/dist/components/alert.js.map +1 -1
  17. package/dist/components/app-shell.d.ts +7 -6
  18. package/dist/components/app-shell.js +22 -14
  19. package/dist/components/app-shell.js.map +1 -1
  20. package/dist/components/attachment.d.ts +3 -3
  21. package/dist/components/attachment.js +11 -7
  22. package/dist/components/attachment.js.map +1 -1
  23. package/dist/components/audio-player.d.ts +2 -2
  24. package/dist/components/audio-player.js +9 -6
  25. package/dist/components/audio-player.js.map +1 -1
  26. package/dist/components/avatar.d.ts +4 -4
  27. package/dist/components/avatar.js +16 -12
  28. package/dist/components/avatar.js.map +1 -1
  29. package/dist/components/badge.d.ts +1 -1
  30. package/dist/components/badge.js +15 -13
  31. package/dist/components/badge.js.map +1 -1
  32. package/dist/components/breadcrumb.d.ts +8 -8
  33. package/dist/components/breadcrumb.js +27 -20
  34. package/dist/components/breadcrumb.js.map +1 -1
  35. package/dist/components/button.d.ts +9 -2
  36. package/dist/components/button.js +15 -6
  37. package/dist/components/button.js.map +1 -1
  38. package/dist/components/card.d.ts +9 -9
  39. package/dist/components/card.js +33 -23
  40. package/dist/components/card.js.map +1 -1
  41. package/dist/components/chat-history.d.ts +1 -1
  42. package/dist/components/chat-history.js +11 -9
  43. package/dist/components/chat-history.js.map +1 -1
  44. package/dist/components/chat-layout.d.ts +1 -1
  45. package/dist/components/chat-layout.js +8 -3
  46. package/dist/components/chat-layout.js.map +1 -1
  47. package/dist/components/checkbox-card.d.ts +3 -3
  48. package/dist/components/checkbox-card.js +12 -10
  49. package/dist/components/checkbox-card.js.map +1 -1
  50. package/dist/components/checkbox.d.ts +24 -3
  51. package/dist/components/checkbox.js +52 -9
  52. package/dist/components/checkbox.js.map +1 -1
  53. package/dist/components/collapsible.d.ts +3 -3
  54. package/dist/components/collapsible.js +14 -11
  55. package/dist/components/collapsible.js.map +1 -1
  56. package/dist/components/combobox.d.ts +14 -4
  57. package/dist/components/combobox.js +19 -8
  58. package/dist/components/combobox.js.map +1 -1
  59. package/dist/components/command.d.ts +68 -12
  60. package/dist/components/command.js +48 -34
  61. package/dist/components/command.js.map +1 -1
  62. package/dist/components/composer.d.ts +2 -2
  63. package/dist/components/composer.js +10 -7
  64. package/dist/components/composer.js.map +1 -1
  65. package/dist/components/connection-status.d.ts +3 -3
  66. package/dist/components/connection-status.js +19 -14
  67. package/dist/components/connection-status.js.map +1 -1
  68. package/dist/components/context-menu.d.ts +15 -14
  69. package/dist/components/context-menu.js +52 -41
  70. package/dist/components/context-menu.js.map +1 -1
  71. package/dist/components/conversation.d.ts +1 -1
  72. package/dist/components/conversation.js +7 -5
  73. package/dist/components/conversation.js.map +1 -1
  74. package/dist/components/copy-button.d.ts +1 -1
  75. package/dist/components/copy-button.js +4 -2
  76. package/dist/components/copy-button.js.map +1 -1
  77. package/dist/components/data-table.d.ts +7 -2
  78. package/dist/components/data-table.js +17 -42
  79. package/dist/components/data-table.js.map +1 -1
  80. package/dist/components/dialog.d.ts +19 -10
  81. package/dist/components/dialog.js +50 -33
  82. package/dist/components/dialog.js.map +1 -1
  83. package/dist/components/dropdown-menu.d.ts +15 -14
  84. package/dist/components/dropdown-menu.js +52 -41
  85. package/dist/components/dropdown-menu.js.map +1 -1
  86. package/dist/components/editor-tabs.d.ts +1 -1
  87. package/dist/components/editor-tabs.js +7 -5
  88. package/dist/components/editor-tabs.js.map +1 -1
  89. package/dist/components/empty-state.d.ts +5 -5
  90. package/dist/components/empty-state.js +17 -11
  91. package/dist/components/empty-state.js.map +1 -1
  92. package/dist/components/error-summary.d.ts +21 -0
  93. package/dist/components/error-summary.js +62 -0
  94. package/dist/components/error-summary.js.map +1 -0
  95. package/dist/components/field.d.ts +66 -7
  96. package/dist/components/field.js +152 -20
  97. package/dist/components/field.js.map +1 -1
  98. package/dist/components/form-actions.d.ts +18 -0
  99. package/dist/components/form-actions.js +30 -0
  100. package/dist/components/form-actions.js.map +1 -0
  101. package/dist/components/icon-button.d.ts +3 -1
  102. package/dist/components/icon-button.js +6 -3
  103. package/dist/components/icon-button.js.map +1 -1
  104. package/dist/components/input.d.ts +42 -7
  105. package/dist/components/input.js +54 -10
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/kbd.d.ts +2 -2
  108. package/dist/components/kbd.js +8 -5
  109. package/dist/components/kbd.js.map +1 -1
  110. package/dist/components/label.d.ts +7 -2
  111. package/dist/components/label.js +14 -7
  112. package/dist/components/label.js.map +1 -1
  113. package/dist/components/markdown.d.ts +4 -3
  114. package/dist/components/markdown.js +8 -6
  115. package/dist/components/markdown.js.map +1 -1
  116. package/dist/components/menubar.d.ts +17 -15
  117. package/dist/components/menubar.js +58 -45
  118. package/dist/components/menubar.js.map +1 -1
  119. package/dist/components/message-actions.d.ts +3 -3
  120. package/dist/components/message-actions.js +16 -12
  121. package/dist/components/message-actions.js.map +1 -1
  122. package/dist/components/message.d.ts +1 -1
  123. package/dist/components/message.js +6 -3
  124. package/dist/components/message.js.map +1 -1
  125. package/dist/components/mobile-nav.d.ts +1 -1
  126. package/dist/components/mobile-nav.js +7 -5
  127. package/dist/components/mobile-nav.js.map +1 -1
  128. package/dist/components/native-select.d.ts +16 -0
  129. package/dist/components/native-select.js +33 -0
  130. package/dist/components/native-select.js.map +1 -0
  131. package/dist/components/navigation-menu.d.ts +13 -12
  132. package/dist/components/navigation-menu.js +43 -33
  133. package/dist/components/navigation-menu.js.map +1 -1
  134. package/dist/components/number-input.d.ts +23 -0
  135. package/dist/components/number-input.js +45 -0
  136. package/dist/components/number-input.js.map +1 -0
  137. package/dist/components/onboarding-checklist.d.ts +1 -1
  138. package/dist/components/onboarding-checklist.js +5 -3
  139. package/dist/components/onboarding-checklist.js.map +1 -1
  140. package/dist/components/page-header.d.ts +7 -6
  141. package/dist/components/page-header.js +23 -15
  142. package/dist/components/page-header.js.map +1 -1
  143. package/dist/components/pagination.d.ts +1 -1
  144. package/dist/components/pagination.js +4 -2
  145. package/dist/components/pagination.js.map +1 -1
  146. package/dist/components/popover.d.ts +14 -11
  147. package/dist/components/popover.js +30 -23
  148. package/dist/components/popover.js.map +1 -1
  149. package/dist/components/progress.d.ts +2 -2
  150. package/dist/components/progress.js +11 -9
  151. package/dist/components/progress.js.map +1 -1
  152. package/dist/components/radio-group.d.ts +23 -4
  153. package/dist/components/radio-group.js +56 -14
  154. package/dist/components/radio-group.js.map +1 -1
  155. package/dist/components/reasoning.d.ts +3 -3
  156. package/dist/components/reasoning.js +14 -11
  157. package/dist/components/reasoning.js.map +1 -1
  158. package/dist/components/resizable.js +1 -0
  159. package/dist/components/resizable.js.map +1 -1
  160. package/dist/components/scroll-area.d.ts +4 -3
  161. package/dist/components/scroll-area.js +18 -14
  162. package/dist/components/scroll-area.js.map +1 -1
  163. package/dist/components/secret-input.d.ts +3 -2
  164. package/dist/components/secret-input.js +7 -4
  165. package/dist/components/secret-input.js.map +1 -1
  166. package/dist/components/select.d.ts +12 -7
  167. package/dist/components/select.js +42 -35
  168. package/dist/components/select.js.map +1 -1
  169. package/dist/components/separator.d.ts +2 -2
  170. package/dist/components/separator.js +8 -6
  171. package/dist/components/separator.js.map +1 -1
  172. package/dist/components/sheet.d.ts +11 -9
  173. package/dist/components/sheet.js +41 -29
  174. package/dist/components/sheet.js.map +1 -1
  175. package/dist/components/skeleton.d.ts +2 -1
  176. package/dist/components/skeleton.js +7 -4
  177. package/dist/components/skeleton.js.map +1 -1
  178. package/dist/components/slider.d.ts +10 -6
  179. package/dist/components/slider.js +16 -7
  180. package/dist/components/slider.js.map +1 -1
  181. package/dist/components/source.d.ts +2 -2
  182. package/dist/components/source.js +10 -7
  183. package/dist/components/source.js.map +1 -1
  184. package/dist/components/spinner.d.ts +4 -2
  185. package/dist/components/spinner.js +5 -3
  186. package/dist/components/spinner.js.map +1 -1
  187. package/dist/components/stat-card.d.ts +27 -2
  188. package/dist/components/stat-card.js +70 -9
  189. package/dist/components/stat-card.js.map +1 -1
  190. package/dist/components/status.d.ts +7 -6
  191. package/dist/components/status.js +12 -5
  192. package/dist/components/status.js.map +1 -1
  193. package/dist/components/steps.d.ts +1 -1
  194. package/dist/components/steps.js +5 -3
  195. package/dist/components/steps.js.map +1 -1
  196. package/dist/components/switch.d.ts +47 -3
  197. package/dist/components/switch.js +89 -11
  198. package/dist/components/switch.js.map +1 -1
  199. package/dist/components/table.d.ts +29 -12
  200. package/dist/components/table.js +80 -34
  201. package/dist/components/table.js.map +1 -1
  202. package/dist/components/tabs.d.ts +10 -5
  203. package/dist/components/tabs.js +29 -19
  204. package/dist/components/tabs.js.map +1 -1
  205. package/dist/components/textarea.d.ts +6 -1
  206. package/dist/components/textarea.js +4 -3
  207. package/dist/components/textarea.js.map +1 -1
  208. package/dist/components/toast.d.ts +11 -7
  209. package/dist/components/toast.js +37 -27
  210. package/dist/components/toast.js.map +1 -1
  211. package/dist/components/toaster.d.ts +4 -2
  212. package/dist/components/toaster.js +7 -2
  213. package/dist/components/toaster.js.map +1 -1
  214. package/dist/components/tool-call.d.ts +2 -2
  215. package/dist/components/tool-call.js +15 -12
  216. package/dist/components/tool-call.js.map +1 -1
  217. package/dist/components/toolbar.d.ts +9 -4
  218. package/dist/components/toolbar.js +15 -9
  219. package/dist/components/toolbar.js.map +1 -1
  220. package/dist/components/tooltip.d.ts +4 -3
  221. package/dist/components/tooltip.js +17 -13
  222. package/dist/components/tooltip.js.map +1 -1
  223. package/dist/components/tour.js +10 -8
  224. package/dist/components/tour.js.map +1 -1
  225. package/dist/components/tree-view.d.ts +1 -1
  226. package/dist/components/tree-view.js +10 -5
  227. package/dist/components/tree-view.js.map +1 -1
  228. package/dist/components/voice-recorder.d.ts +1 -1
  229. package/dist/components/voice-recorder.js +5 -2
  230. package/dist/components/voice-recorder.js.map +1 -1
  231. package/dist/index.d.ts +6 -1
  232. package/dist/index.js +17 -12
  233. package/dist/lib/media.d.ts +18 -0
  234. package/dist/lib/media.js +34 -0
  235. package/dist/lib/media.js.map +1 -0
  236. package/dist/lib/utils.d.ts +32 -0
  237. package/dist/lib/utils.js +33 -1
  238. package/dist/lib/utils.js.map +1 -1
  239. package/dist/mui/index.js +20 -8
  240. package/dist/mui/index.js.map +1 -1
  241. package/dist/primitives/vendor/cmdk/command-score.d.ts +1 -0
  242. package/dist/primitives/vendor/cmdk/command-score.js +64 -0
  243. package/dist/primitives/vendor/cmdk/command-score.js.map +1 -0
  244. package/dist/primitives/vendor/cmdk/index.d.ts +384 -0
  245. package/dist/primitives/vendor/cmdk/index.js +687 -0
  246. package/dist/primitives/vendor/cmdk/index.js.map +1 -0
  247. package/dist/primitives/vendor/radix/number/index.d.ts +1 -0
  248. package/dist/primitives/vendor/radix/number/number.d.ts +2 -0
  249. package/dist/primitives/vendor/radix/number/number.js +9 -0
  250. package/dist/primitives/vendor/radix/number/number.js.map +1 -0
  251. package/dist/primitives/vendor/radix/primitive/index.d.ts +2 -0
  252. package/dist/primitives/vendor/radix/primitive/is-development.d.ts +1 -0
  253. package/dist/primitives/vendor/radix/primitive/is-development.js +7 -0
  254. package/dist/primitives/vendor/radix/primitive/is-development.js.map +1 -0
  255. package/dist/primitives/vendor/radix/primitive/primitive.d.ts +14 -0
  256. package/dist/primitives/vendor/radix/primitive/primitive.js +12 -0
  257. package/dist/primitives/vendor/radix/primitive/primitive.js.map +1 -0
  258. package/dist/primitives/vendor/radix/primitive/types.d.ts +5 -0
  259. package/dist/primitives/vendor/radix/react-accordion/accordion.d.ts +112 -0
  260. package/dist/primitives/vendor/radix/react-accordion/accordion.js +266 -0
  261. package/dist/primitives/vendor/radix/react-accordion/accordion.js.map +1 -0
  262. package/dist/primitives/vendor/radix/react-accordion/index.d.ts +2 -0
  263. package/dist/primitives/vendor/radix/react-alert-dialog/alert-dialog.d.ts +49 -0
  264. package/dist/primitives/vendor/radix/react-alert-dialog/alert-dialog.js +122 -0
  265. package/dist/primitives/vendor/radix/react-alert-dialog/alert-dialog.js.map +1 -0
  266. package/dist/primitives/vendor/radix/react-alert-dialog/index.d.ts +2 -0
  267. package/dist/primitives/vendor/radix/react-arrow/arrow.d.ts +9 -0
  268. package/dist/primitives/vendor/radix/react-arrow/arrow.js +22 -0
  269. package/dist/primitives/vendor/radix/react-arrow/arrow.js.map +1 -0
  270. package/dist/primitives/vendor/radix/react-arrow/index.d.ts +2 -0
  271. package/dist/primitives/vendor/radix/react-avatar/avatar.d.ts +19 -0
  272. package/dist/primitives/vendor/radix/react-avatar/avatar.js +132 -0
  273. package/dist/primitives/vendor/radix/react-avatar/avatar.js.map +1 -0
  274. package/dist/primitives/vendor/radix/react-avatar/index.d.ts +2 -0
  275. package/dist/primitives/vendor/radix/react-checkbox/checkbox.d.ts +47 -0
  276. package/dist/primitives/vendor/radix/react-checkbox/checkbox.js +197 -0
  277. package/dist/primitives/vendor/radix/react-checkbox/checkbox.js.map +1 -0
  278. package/dist/primitives/vendor/radix/react-checkbox/index.d.ts +2 -0
  279. package/dist/primitives/vendor/radix/react-collapsible/collapsible.d.ts +31 -0
  280. package/dist/primitives/vendor/radix/react-collapsible/collapsible.js +133 -0
  281. package/dist/primitives/vendor/radix/react-collapsible/collapsible.js.map +1 -0
  282. package/dist/primitives/vendor/radix/react-collapsible/index.d.ts +2 -0
  283. package/dist/primitives/vendor/radix/react-collection/collection-legacy.d.ts +21 -0
  284. package/dist/primitives/vendor/radix/react-collection/collection-legacy.js +84 -0
  285. package/dist/primitives/vendor/radix/react-collection/collection-legacy.js.map +1 -0
  286. package/dist/primitives/vendor/radix/react-collection/collection.d.ts +32 -0
  287. package/dist/primitives/vendor/radix/react-collection/index.d.ts +4 -0
  288. package/dist/primitives/vendor/radix/react-collection/ordered-dictionary.d.ts +51 -0
  289. package/dist/primitives/vendor/radix/react-compose-refs/compose-refs.d.ts +13 -0
  290. package/dist/primitives/vendor/radix/react-compose-refs/compose-refs.js +43 -0
  291. package/dist/primitives/vendor/radix/react-compose-refs/compose-refs.js.map +1 -0
  292. package/dist/primitives/vendor/radix/react-compose-refs/index.d.ts +1 -0
  293. package/dist/primitives/vendor/radix/react-context/create-context.d.ts +45 -0
  294. package/dist/primitives/vendor/radix/react-context/create-context.js +74 -0
  295. package/dist/primitives/vendor/radix/react-context/create-context.js.map +1 -0
  296. package/dist/primitives/vendor/radix/react-context/index.d.ts +2 -0
  297. package/dist/primitives/vendor/radix/react-context-menu/context-menu.d.ts +95 -0
  298. package/dist/primitives/vendor/radix/react-context-menu/context-menu.js +279 -0
  299. package/dist/primitives/vendor/radix/react-context-menu/context-menu.js.map +1 -0
  300. package/dist/primitives/vendor/radix/react-context-menu/index.d.ts +2 -0
  301. package/dist/primitives/vendor/radix/react-dialog/dialog.d.ts +97 -0
  302. package/dist/primitives/vendor/radix/react-dialog/dialog.js +262 -0
  303. package/dist/primitives/vendor/radix/react-dialog/dialog.js.map +1 -0
  304. package/dist/primitives/vendor/radix/react-dialog/index.d.ts +4 -0
  305. package/dist/primitives/vendor/radix/react-direction/direction.d.ts +10 -0
  306. package/dist/primitives/vendor/radix/react-direction/direction.js +13 -0
  307. package/dist/primitives/vendor/radix/react-direction/direction.js.map +1 -0
  308. package/dist/primitives/vendor/radix/react-direction/index.d.ts +1 -0
  309. package/dist/primitives/vendor/radix/react-dismissable-layer/dismissable-layer.d.ts +61 -0
  310. package/dist/primitives/vendor/radix/react-dismissable-layer/dismissable-layer.js +336 -0
  311. package/dist/primitives/vendor/radix/react-dismissable-layer/dismissable-layer.js.map +1 -0
  312. package/dist/primitives/vendor/radix/react-dismissable-layer/index.d.ts +4 -0
  313. package/dist/primitives/vendor/radix/react-dropdown-menu/dropdown-menu.d.ts +95 -0
  314. package/dist/primitives/vendor/radix/react-dropdown-menu/dropdown-menu.js +257 -0
  315. package/dist/primitives/vendor/radix/react-dropdown-menu/dropdown-menu.js.map +1 -0
  316. package/dist/primitives/vendor/radix/react-dropdown-menu/index.d.ts +2 -0
  317. package/dist/primitives/vendor/radix/react-focus-guards/focus-guards.d.ts +11 -0
  318. package/dist/primitives/vendor/radix/react-focus-guards/focus-guards.js +49 -0
  319. package/dist/primitives/vendor/radix/react-focus-guards/focus-guards.js.map +1 -0
  320. package/dist/primitives/vendor/radix/react-focus-guards/index.d.ts +1 -0
  321. package/dist/primitives/vendor/radix/react-focus-scope/focus-scope.d.ts +31 -0
  322. package/dist/primitives/vendor/radix/react-focus-scope/focus-scope.js +221 -0
  323. package/dist/primitives/vendor/radix/react-focus-scope/focus-scope.js.map +1 -0
  324. package/dist/primitives/vendor/radix/react-focus-scope/index.d.ts +1 -0
  325. package/dist/primitives/vendor/radix/react-id/id.d.ts +2 -0
  326. package/dist/primitives/vendor/radix/react-id/id.js +17 -0
  327. package/dist/primitives/vendor/radix/react-id/id.js.map +1 -0
  328. package/dist/primitives/vendor/radix/react-id/index.d.ts +1 -0
  329. package/dist/primitives/vendor/radix/react-label/index.d.ts +2 -0
  330. package/dist/primitives/vendor/radix/react-label/label.d.ts +9 -0
  331. package/dist/primitives/vendor/radix/react-label/label.js +21 -0
  332. package/dist/primitives/vendor/radix/react-label/label.js.map +1 -0
  333. package/dist/primitives/vendor/radix/react-menu/index.d.ts +2 -0
  334. package/dist/primitives/vendor/radix/react-menu/menu.d.ts +173 -0
  335. package/dist/primitives/vendor/radix/react-menu/menu.js +806 -0
  336. package/dist/primitives/vendor/radix/react-menu/menu.js.map +1 -0
  337. package/dist/primitives/vendor/radix/react-menubar/index.d.ts +2 -0
  338. package/dist/primitives/vendor/radix/react-menubar/menubar.d.ts +106 -0
  339. package/dist/primitives/vendor/radix/react-menubar/menubar.js +377 -0
  340. package/dist/primitives/vendor/radix/react-menubar/menubar.js.map +1 -0
  341. package/dist/primitives/vendor/radix/react-navigation-menu/index.d.ts +2 -0
  342. package/dist/primitives/vendor/radix/react-navigation-menu/navigation-menu.d.ts +124 -0
  343. package/dist/primitives/vendor/radix/react-navigation-menu/navigation-menu.js +743 -0
  344. package/dist/primitives/vendor/radix/react-navigation-menu/navigation-menu.js.map +1 -0
  345. package/dist/primitives/vendor/radix/react-popover/index.d.ts +1 -0
  346. package/dist/primitives/vendor/radix/react-popover/popover.d.ts +83 -0
  347. package/dist/primitives/vendor/radix/react-popover/popover.js +236 -0
  348. package/dist/primitives/vendor/radix/react-popover/popover.js.map +1 -0
  349. package/dist/primitives/vendor/radix/react-popper/index.d.ts +2 -0
  350. package/dist/primitives/vendor/radix/react-popper/popper.d.ts +44 -0
  351. package/dist/primitives/vendor/radix/react-popper/popper.js +276 -0
  352. package/dist/primitives/vendor/radix/react-popper/popper.js.map +1 -0
  353. package/dist/primitives/vendor/radix/react-portal/index.d.ts +2 -0
  354. package/dist/primitives/vendor/radix/react-portal/portal.d.ts +13 -0
  355. package/dist/primitives/vendor/radix/react-portal/portal.js +21 -0
  356. package/dist/primitives/vendor/radix/react-portal/portal.js.map +1 -0
  357. package/dist/primitives/vendor/radix/react-presence/index.d.ts +2 -0
  358. package/dist/primitives/vendor/radix/react-presence/presence.d.ts +11 -0
  359. package/dist/primitives/vendor/radix/react-presence/presence.js +152 -0
  360. package/dist/primitives/vendor/radix/react-presence/presence.js.map +1 -0
  361. package/dist/primitives/vendor/radix/react-presence/use-state-machine.d.ts +10 -0
  362. package/dist/primitives/vendor/radix/react-presence/use-state-machine.js +12 -0
  363. package/dist/primitives/vendor/radix/react-presence/use-state-machine.js.map +1 -0
  364. package/dist/primitives/vendor/radix/react-primitive/index.d.ts +2 -0
  365. package/dist/primitives/vendor/radix/react-primitive/primitive.d.ts +56 -0
  366. package/dist/primitives/vendor/radix/react-primitive/primitive.js +84 -0
  367. package/dist/primitives/vendor/radix/react-primitive/primitive.js.map +1 -0
  368. package/dist/primitives/vendor/radix/react-progress/index.d.ts +2 -0
  369. package/dist/primitives/vendor/radix/react-progress/progress.d.ts +17 -0
  370. package/dist/primitives/vendor/radix/react-progress/progress.js +79 -0
  371. package/dist/primitives/vendor/radix/react-progress/progress.js.map +1 -0
  372. package/dist/primitives/vendor/radix/react-radio-group/index.d.ts +2 -0
  373. package/dist/primitives/vendor/radix/react-radio-group/radio-group.d.ts +55 -0
  374. package/dist/primitives/vendor/radix/react-radio-group/radio-group.js +173 -0
  375. package/dist/primitives/vendor/radix/react-radio-group/radio-group.js.map +1 -0
  376. package/dist/primitives/vendor/radix/react-radio-group/radio.d.ts +62 -0
  377. package/dist/primitives/vendor/radix/react-radio-group/radio.js +150 -0
  378. package/dist/primitives/vendor/radix/react-radio-group/radio.js.map +1 -0
  379. package/dist/primitives/vendor/radix/react-roving-focus/index.d.ts +2 -0
  380. package/dist/primitives/vendor/radix/react-roving-focus/roving-focus-group.d.ts +47 -0
  381. package/dist/primitives/vendor/radix/react-roving-focus/roving-focus-group.js +218 -0
  382. package/dist/primitives/vendor/radix/react-roving-focus/roving-focus-group.js.map +1 -0
  383. package/dist/primitives/vendor/radix/react-scroll-area/index.d.ts +2 -0
  384. package/dist/primitives/vendor/radix/react-scroll-area/scroll-area.d.ts +121 -0
  385. package/dist/primitives/vendor/radix/react-scroll-area/scroll-area.js +713 -0
  386. package/dist/primitives/vendor/radix/react-scroll-area/scroll-area.js.map +1 -0
  387. package/dist/primitives/vendor/radix/react-scroll-area/use-state-machine.d.ts +10 -0
  388. package/dist/primitives/vendor/radix/react-scroll-area/use-state-machine.js +12 -0
  389. package/dist/primitives/vendor/radix/react-scroll-area/use-state-machine.js.map +1 -0
  390. package/dist/primitives/vendor/radix/react-select/index.d.ts +2 -0
  391. package/dist/primitives/vendor/radix/react-select/select.d.ts +163 -0
  392. package/dist/primitives/vendor/radix/react-select/select.js +1040 -0
  393. package/dist/primitives/vendor/radix/react-select/select.js.map +1 -0
  394. package/dist/primitives/vendor/radix/react-separator/index.d.ts +2 -0
  395. package/dist/primitives/vendor/radix/react-separator/separator.d.ts +20 -0
  396. package/dist/primitives/vendor/radix/react-separator/separator.js +29 -0
  397. package/dist/primitives/vendor/radix/react-separator/separator.js.map +1 -0
  398. package/dist/primitives/vendor/radix/react-slot/index.d.ts +1 -0
  399. package/dist/primitives/vendor/radix/react-slot/slot.d.ts +29 -0
  400. package/dist/primitives/vendor/radix/react-slot/slot.js +107 -0
  401. package/dist/primitives/vendor/radix/react-slot/slot.js.map +1 -0
  402. package/dist/primitives/vendor/radix/react-switch/index.d.ts +2 -0
  403. package/dist/primitives/vendor/radix/react-switch/switch.d.ts +41 -0
  404. package/dist/primitives/vendor/radix/react-switch/switch.js +185 -0
  405. package/dist/primitives/vendor/radix/react-switch/switch.js.map +1 -0
  406. package/dist/primitives/vendor/radix/react-tabs/index.d.ts +2 -0
  407. package/dist/primitives/vendor/radix/react-tabs/tabs.d.ts +54 -0
  408. package/dist/primitives/vendor/radix/react-tabs/tabs.js +145 -0
  409. package/dist/primitives/vendor/radix/react-tabs/tabs.js.map +1 -0
  410. package/dist/primitives/vendor/radix/react-toast/index.d.ts +2 -0
  411. package/dist/primitives/vendor/radix/react-toast/toast.d.ts +125 -0
  412. package/dist/primitives/vendor/radix/react-toast/toast.js +557 -0
  413. package/dist/primitives/vendor/radix/react-toast/toast.js.map +1 -0
  414. package/dist/primitives/vendor/radix/react-tooltip/index.d.ts +2 -0
  415. package/dist/primitives/vendor/radix/react-tooltip/tooltip.d.ts +99 -0
  416. package/dist/primitives/vendor/radix/react-tooltip/tooltip.js +503 -0
  417. package/dist/primitives/vendor/radix/react-tooltip/tooltip.js.map +1 -0
  418. package/dist/primitives/vendor/radix/react-use-callback-ref/index.d.ts +1 -0
  419. package/dist/primitives/vendor/radix/react-use-callback-ref/use-callback-ref.d.ts +6 -0
  420. package/dist/primitives/vendor/radix/react-use-callback-ref/use-callback-ref.js +18 -0
  421. package/dist/primitives/vendor/radix/react-use-callback-ref/use-callback-ref.js.map +1 -0
  422. package/dist/primitives/vendor/radix/react-use-controllable-state/index.d.ts +2 -0
  423. package/dist/primitives/vendor/radix/react-use-controllable-state/use-controllable-state-reducer.d.ts +28 -0
  424. package/dist/primitives/vendor/radix/react-use-controllable-state/use-controllable-state.d.ts +11 -0
  425. package/dist/primitives/vendor/radix/react-use-controllable-state/use-controllable-state.js +59 -0
  426. package/dist/primitives/vendor/radix/react-use-controllable-state/use-controllable-state.js.map +1 -0
  427. package/dist/primitives/vendor/radix/react-use-effect-event/index.d.ts +1 -0
  428. package/dist/primitives/vendor/radix/react-use-effect-event/use-effect-event.d.ts +7 -0
  429. package/dist/primitives/vendor/radix/react-use-is-hydrated/index.d.ts +1 -0
  430. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated-legacy.d.ts +1 -0
  431. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated-legacy.js +18 -0
  432. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated-legacy.js.map +1 -0
  433. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated.d.ts +6 -0
  434. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated.js +19 -0
  435. package/dist/primitives/vendor/radix/react-use-is-hydrated/use-is-hydrated.js.map +1 -0
  436. package/dist/primitives/vendor/radix/react-use-layout-effect/index.d.ts +1 -0
  437. package/dist/primitives/vendor/radix/react-use-layout-effect/use-layout-effect.d.ts +10 -0
  438. package/dist/primitives/vendor/radix/react-use-layout-effect/use-layout-effect.js +15 -0
  439. package/dist/primitives/vendor/radix/react-use-layout-effect/use-layout-effect.js.map +1 -0
  440. package/dist/primitives/vendor/radix/react-use-previous/index.d.ts +1 -0
  441. package/dist/primitives/vendor/radix/react-use-previous/use-previous.d.ts +2 -0
  442. package/dist/primitives/vendor/radix/react-use-previous/use-previous.js +20 -0
  443. package/dist/primitives/vendor/radix/react-use-previous/use-previous.js.map +1 -0
  444. package/dist/primitives/vendor/radix/react-use-size/index.d.ts +1 -0
  445. package/dist/primitives/vendor/radix/react-use-size/use-size.d.ts +5 -0
  446. package/dist/primitives/vendor/radix/react-use-size/use-size.js +42 -0
  447. package/dist/primitives/vendor/radix/react-use-size/use-size.js.map +1 -0
  448. package/dist/primitives/vendor/radix/react-visually-hidden/index.d.ts +2 -0
  449. package/dist/primitives/vendor/radix/react-visually-hidden/visually-hidden.d.ts +21 -0
  450. package/dist/primitives/vendor/radix/react-visually-hidden/visually-hidden.js +32 -0
  451. package/dist/primitives/vendor/radix/react-visually-hidden/visually-hidden.js.map +1 -0
  452. package/dist/primitives/vendor/radix/rect/index.d.ts +2 -0
  453. package/dist/primitives/vendor/radix/rect/observe-element-rect.d.ts +16 -0
  454. package/dist/styles.css +1 -1
  455. package/dist/theme.css +36 -17
  456. package/dist/tokens.d.ts +78 -36
  457. package/dist/tokens.js +63 -32
  458. package/dist/tokens.js.map +1 -1
  459. package/package.json +25 -13
  460. package/vendor-manifest.json +1101 -0
@@ -1,14 +1,16 @@
1
- import { cn } from "../lib/utils.js";
1
+ "use client";
2
+ import { cn, focusRingClasses, touchTargetClasses } from "../lib/utils.js";
3
+ import { Root as Slot } from "../primitives/vendor/radix/react-slot/slot.js";
2
4
  import { Button } from "./button.js";
5
+ import { Anchor, Content, Portal, Root } from "../primitives/vendor/radix/react-popover/popover.js";
3
6
  import { surfaceClasses } from "./popover.js";
4
- import { Popover, Slot } from "radix-ui";
5
7
  import * as React from "react";
6
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
9
  import X from "@burtson-labs/icons/react/x";
8
10
  //#region src/components/tour.tsx
9
11
  /** Marks an element as a tour target. Merges onto its one child. */
10
12
  function TourAnchor({ id, children }) {
11
- return /* @__PURE__ */ jsx(Slot.Root, {
13
+ return /* @__PURE__ */ jsx(Slot, {
12
14
  "data-tour-target": id,
13
15
  children
14
16
  });
@@ -205,10 +207,10 @@ function Tour({ steps, open, step, onStepChange, onEnd, missingTarget = "center"
205
207
  return /* @__PURE__ */ jsxs(Fragment, { children: [ready && rect && /* @__PURE__ */ jsx(TourHighlight, {
206
208
  rect,
207
209
  dim
208
- }), /* @__PURE__ */ jsxs(Popover.Root, {
210
+ }), /* @__PURE__ */ jsxs(Root, {
209
211
  open: shown,
210
212
  modal: false,
211
- children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: { current: { getBoundingClientRect: () => anchor } } }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
213
+ children: [/* @__PURE__ */ jsx(Anchor, { virtualRef: { current: { getBoundingClientRect: () => anchor } } }), /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(Content, {
212
214
  ref: (el) => {
213
215
  contentRef.current = el;
214
216
  setContentEl(el);
@@ -247,7 +249,7 @@ function Tour({ steps, open, step, onStepChange, onEnd, missingTarget = "center"
247
249
  type: "button",
248
250
  "aria-label": labels.close,
249
251
  onClick: () => onEnd("close", step),
250
- className: "-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]",
252
+ className: cn("-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground", focusRingClasses, touchTargetClasses),
251
253
  children: /* @__PURE__ */ jsx(X, {
252
254
  "aria-hidden": true,
253
255
  className: "size-4"
@@ -313,10 +315,10 @@ function Spotlight({ target, open, onDismiss, title, children, side = "bottom",
313
315
  return /* @__PURE__ */ jsxs(Fragment, { children: [highlight && /* @__PURE__ */ jsx(TourHighlight, {
314
316
  rect,
315
317
  dim: false
316
- }), /* @__PURE__ */ jsxs(Popover.Root, {
318
+ }), /* @__PURE__ */ jsxs(Root, {
317
319
  open: true,
318
320
  modal: false,
319
- children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: { current: { getBoundingClientRect: () => rect } } }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
321
+ children: [/* @__PURE__ */ jsx(Anchor, { virtualRef: { current: { getBoundingClientRect: () => rect } } }), /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(Content, {
320
322
  ref: contentRef,
321
323
  "data-slot": "spotlight",
322
324
  role: "note",
@@ -1 +1 @@
1
- {"version":3,"file":"tour.js","names":["_Fragment","PopoverPrimitive"],"sources":["../../src/components/tour.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport { Popover as PopoverPrimitive, Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { surfaceClasses } from './popover';\n\n/**\n * A registered target id (see TourAnchor), or a function returning the\n * element. Ids are stable; never target generated class names or nth-child.\n */\nexport type TourTarget = string | (() => Element | null);\n\n/** Marks an element as a tour target. Merges onto its one child. */\nfunction TourAnchor({ id, children }: { id: string; children: React.ReactElement }) {\n return <Slot.Root data-tour-target={id}>{children}</Slot.Root>;\n}\n\n/** The same marker as TourAnchor, for elements you render yourself. */\nexport const tourTarget = (id: string) => ({ 'data-tour-target': id });\n\nconst isVisible = (el: Element | null): el is Element =>\n Boolean(el?.isConnected && el.getClientRects().length > 0);\n\nexport function findTourTarget(target: TourTarget | undefined): Element | null {\n if (!target) return null;\n const el =\n typeof target === 'function'\n ? target()\n : (Array.from(document.querySelectorAll('[data-tour-target]')).find(\n (node) => node.getAttribute('data-tour-target') === target,\n ) ?? null);\n return isVisible(el) ? el : null;\n}\n\n/**\n * Resolves when the target is mounted and visible, or with null after\n * `timeoutMs` or when `signal` aborts. Handles targets that render late\n * (lazy panels, async data) without polling forever.\n */\nexport function waitForTourTarget(\n target: TourTarget | undefined,\n timeoutMs: number,\n signal: AbortSignal,\n): Promise<Element | null> {\n return new Promise((resolve) => {\n const now = findTourTarget(target);\n if (now || !target || timeoutMs <= 0 || signal.aborted) return resolve(now);\n let done = false;\n const finish = (el: Element | null) => {\n if (done) return;\n done = true;\n observer.disconnect();\n clearInterval(poll);\n clearTimeout(timer);\n signal.removeEventListener('abort', abort);\n resolve(el);\n };\n const check = () => {\n const el = findTourTarget(target);\n if (el) finish(el);\n };\n const abort = () => finish(null);\n const observer = new MutationObserver(check);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true });\n // Visibility can change through CSS alone, which no mutation reports.\n const poll = setInterval(check, 150);\n const timer = setTimeout(() => finish(null), timeoutMs);\n signal.addEventListener('abort', abort);\n });\n}\n\n/**\n * Escape belongs to whatever the person is typing in (a field, CodeMirror's\n * contenteditable, xterm's hidden textarea), not to the tour.\n */\nconst typingElsewhere = (container: HTMLElement | null) => {\n const active = document.activeElement;\n if (!(active instanceof HTMLElement) || container?.contains(active)) return false;\n return active.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);\n};\n\nconst prefersReducedMotion = () =>\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n/** Follows an element's box across scroll and resize; null until measured. */\nfunction useRect(el: Element | null) {\n const [measured, setMeasured] = React.useState<{ el: Element; rect: DOMRect } | null>(null);\n React.useEffect(() => {\n if (!el) return;\n let frame = 0;\n const update = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => setMeasured({ el, rect: el.getBoundingClientRect() }));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n window.addEventListener('scroll', update, true);\n window.addEventListener('resize', update);\n return () => {\n cancelAnimationFrame(frame);\n ro.disconnect();\n window.removeEventListener('scroll', update, true);\n window.removeEventListener('resize', update);\n };\n }, [el]);\n return measured && measured.el === el ? measured.rect : null;\n}\n\n/** A ring around the target, optionally dimming the rest of the page. Never blocks clicks. */\nfunction TourHighlight({ rect, dim }: { rect: DOMRect; dim: boolean }) {\n const pad = 4;\n return (\n <div\n aria-hidden\n data-slot=\"tour-highlight\"\n className={cn(\n 'pointer-events-none fixed z-40 rounded-md ring-2 ring-brand motion-safe:transition-[top,left,width,height] motion-safe:duration-200',\n dim && 'shadow-[0_0_0_9999px_rgb(10_8_14/0.45)]',\n )}\n style={{\n top: rect.top - pad,\n left: rect.left - pad,\n width: rect.width + pad * 2,\n height: rect.height + pad * 2,\n }}\n />\n );\n}\n\n/** A zero-size box a third of the way down the viewport, for steps without a target. */\nconst centerRect = () =>\n new DOMRect(window.innerWidth / 2, Math.max(80, window.innerHeight / 3), 0, 0);\n\nconst contentClasses = cn(\n surfaceClasses,\n 'w-80 max-w-[calc(100vw-2rem)] p-4 text-[13px] outline-none origin-(--radix-popover-content-transform-origin)',\n);\n\nexport interface TourStep {\n id: string;\n /** Omit for a centred step with no anchor. */\n target?: TourTarget;\n title: React.ReactNode;\n content: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /**\n * Reveal what the step points at (open a panel, switch a route) before it\n * shows. Aborted if the person leaves the step first; a rejection shows the\n * step centred rather than stranding the tour.\n */\n prepare?: (signal: AbortSignal) => void | Promise<void>;\n}\n\nexport type TourEndReason = 'complete' | 'skip' | 'close' | 'escape';\n\nexport interface TourLabels {\n next: string;\n back: string;\n skip: string;\n done: string;\n close: string;\n progress: (step: number, total: number) => string;\n}\n\nconst defaultLabels: TourLabels = {\n next: 'Next',\n back: 'Back',\n skip: 'Skip tour',\n done: 'Done',\n close: 'Close tour',\n progress: (step, total) => `${step} of ${total}`,\n};\n\nexport interface TourProps {\n steps: TourStep[];\n open: boolean;\n /** Controlled, zero-based. */\n step: number;\n onStepChange: (step: number) => void;\n /** The tour is over. Store dismissal or completion yourself, keyed by tour id and revision. */\n onEnd: (reason: TourEndReason, step: number) => void;\n /** `center` (default) shows a step whose target never appears in the middle; `skip` moves past it. */\n missingTarget?: 'center' | 'skip';\n /** How long to wait for a late target. Default 2000ms. */\n waitMs?: number;\n /** Dim the page outside the target. Default true. */\n dim?: boolean;\n labels?: Partial<TourLabels>;\n /** Where focus goes if the element that started the tour is gone. */\n fallbackFocus?: () => HTMLElement | null;\n}\n\n/**\n * A guided sequence of anchored steps. It is non-modal: the page stays\n * usable and outside clicks do not end it. Focus moves into each step,\n * Escape ends the tour (unless the person is typing in a field or editor\n * outside it), and focus returns to where it was when the tour started.\n * The app owns the copy, the eligibility rules and what it persists.\n */\nfunction Tour({\n steps,\n open,\n step,\n onStepChange,\n onEnd,\n missingTarget = 'center',\n waitMs = 2000,\n dim = true,\n labels: labelOverrides,\n fallbackFocus,\n}: TourProps) {\n const labels = { ...defaultLabels, ...labelOverrides };\n const current = steps[step];\n // The resolved step: which step it is for, and its target (null: centred).\n const stepKey = current ? `${step}:${current.id}` : '';\n const [resolved, setResolved] = React.useState<{ key: string; el: Element | null } | null>(null);\n const ready = open && resolved?.key === stepKey;\n const target = ready ? (resolved?.el ?? null) : null;\n const rect = useRect(target);\n const contentRef = React.useRef<HTMLDivElement>(null);\n // Radix mounts the content after the popper measures, so focus follows the node.\n const [contentEl, setContentEl] = React.useState<HTMLDivElement | null>(null);\n const returnFocus = React.useRef<HTMLElement | null>(null);\n const direction = React.useRef(1);\n const titleId = React.useId();\n const bodyId = React.useId();\n\n // Remember who started the tour; hand focus back when it ends.\n React.useLayoutEffect(() => {\n if (!open) return;\n const active = document.activeElement;\n returnFocus.current = active instanceof HTMLElement && active !== document.body ? active : null;\n return () => {\n const back = returnFocus.current;\n const el = back && isVisible(back) ? back : (fallbackFocus?.() ?? null);\n el?.focus();\n };\n // fallbackFocus is read at close time; re-running on its identity would steal focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n React.useEffect(() => {\n if (!open || !current) return;\n const controller = new AbortController();\n const { signal } = controller;\n void (async () => {\n let prepared = true;\n try {\n await current.prepare?.(signal);\n } catch {\n prepared = false;\n }\n if (signal.aborted) return;\n const el = prepared ? await waitForTourTarget(current.target, waitMs, signal) : null;\n if (signal.aborted) return;\n if (!el && current.target && missingTarget === 'skip') {\n const next = step + direction.current;\n if (next >= 0 && next < steps.length) return onStepChange(next);\n }\n if (el)\n el.scrollIntoView?.({\n block: 'nearest',\n behavior: prefersReducedMotion() ? 'auto' : 'smooth',\n });\n setResolved({ key: stepKey, el });\n })();\n return () => controller.abort();\n // steps/onStepChange identity changes should not restart a step in progress.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, stepKey, waitMs, missingTarget]);\n\n // Shown once resolved and, for an anchored step, measured.\n const shown = ready && (!target || rect !== null);\n React.useEffect(() => {\n if (shown) contentEl?.focus();\n }, [shown, step, contentEl]);\n\n if (!open || !current) return null;\n const last = step === steps.length - 1;\n const go = (to: number) => {\n direction.current = to < step ? -1 : 1;\n onStepChange(to);\n };\n // Wait one frame for the target's box rather than flashing the step centred.\n if (!shown && ready) return null;\n const anchor = rect ?? centerRect();\n\n return (\n <>\n {ready && rect && <TourHighlight rect={rect} dim={dim} />}\n <PopoverPrimitive.Root open={shown} modal={false}>\n <PopoverPrimitive.Anchor\n virtualRef={{ current: { getBoundingClientRect: () => anchor } }}\n />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={(el) => {\n contentRef.current = el;\n setContentEl(el);\n }}\n data-slot=\"tour\"\n data-step={current.id}\n tabIndex={-1}\n aria-labelledby={titleId}\n aria-describedby={bodyId}\n side={rect ? (current.side ?? 'bottom') : 'bottom'}\n align={rect ? (current.align ?? 'center') : 'center'}\n sideOffset={10}\n collisionPadding={12}\n className={contentClasses}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onEnd('escape', step);\n }}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-xs text-muted-foreground\">\n {labels.progress(step + 1, steps.length)}\n </p>\n <h2 id={titleId} className=\"mt-0.5 text-sm font-semibold text-foreground\">\n {current.title}\n </h2>\n </div>\n <button\n type=\"button\"\n aria-label={labels.close}\n onClick={() => onEnd('close', step)}\n className=\"-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]\"\n >\n <X aria-hidden className=\"size-4\" />\n </button>\n </div>\n <div id={bodyId} className=\"mt-2 text-muted-foreground\">\n {current.content}\n </div>\n <div className=\"mt-4 flex items-center gap-2\">\n {!last && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ms-2\"\n onClick={() => onEnd('skip', step)}\n >\n {labels.skip}\n </Button>\n )}\n <div className=\"ms-auto flex gap-2\">\n {step > 0 && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => go(step - 1)}>\n {labels.back}\n </Button>\n )}\n <Button size=\"sm\" onClick={() => (last ? onEnd('complete', step) : go(step + 1))}>\n {last ? labels.done : labels.next}\n </Button>\n </div>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport interface SpotlightProps {\n target: TourTarget;\n open: boolean;\n onDismiss: (reason: 'dismiss' | 'escape') => void;\n title: React.ReactNode;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /** Default \"Got it\". */\n dismissLabel?: string;\n /** An extra button, e.g. \"Add a provider\". */\n action?: React.ReactNode;\n /** Ring the target. Default true. */\n highlight?: boolean;\n className?: string;\n}\n\n/**\n * One contextual hint beside a target: a new feature, a setup nudge. It is\n * informational, so it does not take focus or block the page. Escape (unless\n * the person is typing elsewhere) or the dismiss button closes it. If the target is\n * not on screen, nothing renders.\n */\nfunction Spotlight({\n target,\n open,\n onDismiss,\n title,\n children,\n side = 'bottom',\n align = 'center',\n dismissLabel = 'Got it',\n action,\n highlight = true,\n className,\n}: SpotlightProps) {\n const key = typeof target === 'string' ? target : 'fn';\n const [found, setFound] = React.useState<{ key: string; el: Element | null } | null>(null);\n const el = open && found?.key === key ? found.el : null;\n const rect = useRect(el);\n const titleId = React.useId();\n const contentRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const controller = new AbortController();\n void waitForTourTarget(target, 2000, controller.signal).then((found) => {\n if (!controller.signal.aborted) setFound({ key, el: found });\n });\n return () => controller.abort();\n // A new function identity for `target` should not re-run the lookup.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, key]);\n\n if (!open || !el || !rect) return null;\n return (\n <>\n {highlight && <TourHighlight rect={rect} dim={false} />}\n <PopoverPrimitive.Root open modal={false}>\n <PopoverPrimitive.Anchor virtualRef={{ current: { getBoundingClientRect: () => rect } }} />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={contentRef}\n data-slot=\"spotlight\"\n role=\"note\"\n aria-labelledby={titleId}\n side={side}\n align={align}\n sideOffset={10}\n collisionPadding={12}\n className={cn(contentClasses, 'w-72', className)}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onDismiss('escape');\n }}\n >\n <span className=\"sr-only\" aria-live=\"polite\">\n Tip: {title}\n </span>\n <p id={titleId} className=\"text-sm font-semibold text-foreground\">\n {title}\n </p>\n <div className=\"mt-1.5 text-muted-foreground\">{children}</div>\n <div className=\"mt-3 flex justify-end gap-2\">\n {action}\n <Button\n size=\"sm\"\n variant={action ? 'ghost' : 'default'}\n onClick={() => onDismiss('dismiss')}\n >\n {dismissLabel}\n </Button>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport { Spotlight, Tour, TourAnchor };\n"],"mappings":"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"}
1
+ {"version":3,"file":"tour.js","names":["Slot.Root","_Fragment","PopoverPrimitive.Root","PopoverPrimitive.Anchor","PopoverPrimitive.Portal","PopoverPrimitive.Content"],"sources":["../../src/components/tour.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses, touchTargetClasses } from '../lib/utils';\nimport * as PopoverPrimitive from '../primitives/vendor/radix/react-popover';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nimport { Button } from './button';\nimport { surfaceClasses } from './popover';\n\n/**\n * A registered target id (see TourAnchor), or a function returning the\n * element. Ids are stable; never target generated class names or nth-child.\n */\nexport type TourTarget = string | (() => Element | null);\n\n/** Marks an element as a tour target. Merges onto its one child. */\nfunction TourAnchor({ id, children }: { id: string; children: React.ReactElement }) {\n return <Slot.Root data-tour-target={id}>{children}</Slot.Root>;\n}\n\n/** The same marker as TourAnchor, for elements you render yourself. */\nexport const tourTarget = (id: string) => ({ 'data-tour-target': id });\n\nconst isVisible = (el: Element | null): el is Element =>\n Boolean(el?.isConnected && el.getClientRects().length > 0);\n\nexport function findTourTarget(target: TourTarget | undefined): Element | null {\n if (!target) return null;\n const el =\n typeof target === 'function'\n ? target()\n : (Array.from(document.querySelectorAll('[data-tour-target]')).find(\n (node) => node.getAttribute('data-tour-target') === target,\n ) ?? null);\n return isVisible(el) ? el : null;\n}\n\n/**\n * Resolves when the target is mounted and visible, or with null after\n * `timeoutMs` or when `signal` aborts. Handles targets that render late\n * (lazy panels, async data) without polling forever.\n */\nexport function waitForTourTarget(\n target: TourTarget | undefined,\n timeoutMs: number,\n signal: AbortSignal,\n): Promise<Element | null> {\n return new Promise((resolve) => {\n const now = findTourTarget(target);\n if (now || !target || timeoutMs <= 0 || signal.aborted) return resolve(now);\n let done = false;\n const finish = (el: Element | null) => {\n if (done) return;\n done = true;\n observer.disconnect();\n clearInterval(poll);\n clearTimeout(timer);\n signal.removeEventListener('abort', abort);\n resolve(el);\n };\n const check = () => {\n const el = findTourTarget(target);\n if (el) finish(el);\n };\n const abort = () => finish(null);\n const observer = new MutationObserver(check);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true });\n // Visibility can change through CSS alone, which no mutation reports.\n const poll = setInterval(check, 150);\n const timer = setTimeout(() => finish(null), timeoutMs);\n signal.addEventListener('abort', abort);\n });\n}\n\n/**\n * Escape belongs to whatever the person is typing in (a field, CodeMirror's\n * contenteditable, xterm's hidden textarea), not to the tour.\n */\nconst typingElsewhere = (container: HTMLElement | null) => {\n const active = document.activeElement;\n if (!(active instanceof HTMLElement) || container?.contains(active)) return false;\n return active.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);\n};\n\nconst prefersReducedMotion = () =>\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n/** Follows an element's box across scroll and resize; null until measured. */\nfunction useRect(el: Element | null) {\n const [measured, setMeasured] = React.useState<{ el: Element; rect: DOMRect } | null>(null);\n React.useEffect(() => {\n if (!el) return;\n let frame = 0;\n const update = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => setMeasured({ el, rect: el.getBoundingClientRect() }));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n window.addEventListener('scroll', update, true);\n window.addEventListener('resize', update);\n return () => {\n cancelAnimationFrame(frame);\n ro.disconnect();\n window.removeEventListener('scroll', update, true);\n window.removeEventListener('resize', update);\n };\n }, [el]);\n return measured && measured.el === el ? measured.rect : null;\n}\n\n/** A ring around the target, optionally dimming the rest of the page. Never blocks clicks. */\nfunction TourHighlight({ rect, dim }: { rect: DOMRect; dim: boolean }) {\n const pad = 4;\n return (\n <div\n aria-hidden\n data-slot=\"tour-highlight\"\n className={cn(\n 'pointer-events-none fixed z-40 rounded-md ring-2 ring-brand motion-safe:transition-[top,left,width,height] motion-safe:duration-200',\n dim && 'shadow-[0_0_0_9999px_rgb(10_8_14/0.45)]',\n )}\n style={{\n top: rect.top - pad,\n left: rect.left - pad,\n width: rect.width + pad * 2,\n height: rect.height + pad * 2,\n }}\n />\n );\n}\n\n/** A zero-size box a third of the way down the viewport, for steps without a target. */\nconst centerRect = () =>\n new DOMRect(window.innerWidth / 2, Math.max(80, window.innerHeight / 3), 0, 0);\n\nconst contentClasses = cn(\n surfaceClasses,\n 'w-80 max-w-[calc(100vw-2rem)] p-4 text-[13px] outline-none origin-(--radix-popover-content-transform-origin)',\n);\n\nexport interface TourStep {\n id: string;\n /** Omit for a centred step with no anchor. */\n target?: TourTarget;\n title: React.ReactNode;\n content: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /**\n * Reveal what the step points at (open a panel, switch a route) before it\n * shows. Aborted if the person leaves the step first; a rejection shows the\n * step centred rather than stranding the tour.\n */\n prepare?: (signal: AbortSignal) => void | Promise<void>;\n}\n\nexport type TourEndReason = 'complete' | 'skip' | 'close' | 'escape';\n\nexport interface TourLabels {\n next: string;\n back: string;\n skip: string;\n done: string;\n close: string;\n progress: (step: number, total: number) => string;\n}\n\nconst defaultLabels: TourLabels = {\n next: 'Next',\n back: 'Back',\n skip: 'Skip tour',\n done: 'Done',\n close: 'Close tour',\n progress: (step, total) => `${step} of ${total}`,\n};\n\nexport interface TourProps {\n steps: TourStep[];\n open: boolean;\n /** Controlled, zero-based. */\n step: number;\n onStepChange: (step: number) => void;\n /** The tour is over. Store dismissal or completion yourself, keyed by tour id and revision. */\n onEnd: (reason: TourEndReason, step: number) => void;\n /** `center` (default) shows a step whose target never appears in the middle; `skip` moves past it. */\n missingTarget?: 'center' | 'skip';\n /** How long to wait for a late target. Default 2000ms. */\n waitMs?: number;\n /** Dim the page outside the target. Default true. */\n dim?: boolean;\n labels?: Partial<TourLabels>;\n /** Where focus goes if the element that started the tour is gone. */\n fallbackFocus?: () => HTMLElement | null;\n}\n\n/**\n * A guided sequence of anchored steps. It is non-modal: the page stays\n * usable and outside clicks do not end it. Focus moves into each step,\n * Escape ends the tour (unless the person is typing in a field or editor\n * outside it), and focus returns to where it was when the tour started.\n * The app owns the copy, the eligibility rules and what it persists.\n */\nfunction Tour({\n steps,\n open,\n step,\n onStepChange,\n onEnd,\n missingTarget = 'center',\n waitMs = 2000,\n dim = true,\n labels: labelOverrides,\n fallbackFocus,\n}: TourProps) {\n const labels = { ...defaultLabels, ...labelOverrides };\n const current = steps[step];\n // The resolved step: which step it is for, and its target (null: centred).\n const stepKey = current ? `${step}:${current.id}` : '';\n const [resolved, setResolved] = React.useState<{ key: string; el: Element | null } | null>(null);\n const ready = open && resolved?.key === stepKey;\n const target = ready ? (resolved?.el ?? null) : null;\n const rect = useRect(target);\n const contentRef = React.useRef<HTMLDivElement>(null);\n // Radix mounts the content after the popper measures, so focus follows the node.\n const [contentEl, setContentEl] = React.useState<HTMLDivElement | null>(null);\n const returnFocus = React.useRef<HTMLElement | null>(null);\n const direction = React.useRef(1);\n const titleId = React.useId();\n const bodyId = React.useId();\n\n // Remember who started the tour; hand focus back when it ends.\n React.useLayoutEffect(() => {\n if (!open) return;\n const active = document.activeElement;\n returnFocus.current = active instanceof HTMLElement && active !== document.body ? active : null;\n return () => {\n const back = returnFocus.current;\n const el = back && isVisible(back) ? back : (fallbackFocus?.() ?? null);\n el?.focus();\n };\n // fallbackFocus is read at close time; re-running on its identity would steal focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n React.useEffect(() => {\n if (!open || !current) return;\n const controller = new AbortController();\n const { signal } = controller;\n void (async () => {\n let prepared = true;\n try {\n await current.prepare?.(signal);\n } catch {\n prepared = false;\n }\n if (signal.aborted) return;\n const el = prepared ? await waitForTourTarget(current.target, waitMs, signal) : null;\n if (signal.aborted) return;\n if (!el && current.target && missingTarget === 'skip') {\n const next = step + direction.current;\n if (next >= 0 && next < steps.length) return onStepChange(next);\n }\n if (el)\n el.scrollIntoView?.({\n block: 'nearest',\n behavior: prefersReducedMotion() ? 'auto' : 'smooth',\n });\n setResolved({ key: stepKey, el });\n })();\n return () => controller.abort();\n // steps/onStepChange identity changes should not restart a step in progress.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, stepKey, waitMs, missingTarget]);\n\n // Shown once resolved and, for an anchored step, measured.\n const shown = ready && (!target || rect !== null);\n React.useEffect(() => {\n if (shown) contentEl?.focus();\n }, [shown, step, contentEl]);\n\n if (!open || !current) return null;\n const last = step === steps.length - 1;\n const go = (to: number) => {\n direction.current = to < step ? -1 : 1;\n onStepChange(to);\n };\n // Wait one frame for the target's box rather than flashing the step centred.\n if (!shown && ready) return null;\n const anchor = rect ?? centerRect();\n\n return (\n <>\n {ready && rect && <TourHighlight rect={rect} dim={dim} />}\n <PopoverPrimitive.Root open={shown} modal={false}>\n <PopoverPrimitive.Anchor\n virtualRef={{ current: { getBoundingClientRect: () => anchor } }}\n />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={(el) => {\n contentRef.current = el;\n setContentEl(el);\n }}\n data-slot=\"tour\"\n data-step={current.id}\n tabIndex={-1}\n aria-labelledby={titleId}\n aria-describedby={bodyId}\n side={rect ? (current.side ?? 'bottom') : 'bottom'}\n align={rect ? (current.align ?? 'center') : 'center'}\n sideOffset={10}\n collisionPadding={12}\n className={contentClasses}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onEnd('escape', step);\n }}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-xs text-muted-foreground\">\n {labels.progress(step + 1, steps.length)}\n </p>\n <h2 id={titleId} className=\"mt-0.5 text-sm font-semibold text-foreground\">\n {current.title}\n </h2>\n </div>\n <button\n type=\"button\"\n aria-label={labels.close}\n onClick={() => onEnd('close', step)}\n className={cn(\n '-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground',\n focusRingClasses,\n touchTargetClasses,\n )}\n >\n <X aria-hidden className=\"size-4\" />\n </button>\n </div>\n <div id={bodyId} className=\"mt-2 text-muted-foreground\">\n {current.content}\n </div>\n <div className=\"mt-4 flex items-center gap-2\">\n {!last && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ms-2\"\n onClick={() => onEnd('skip', step)}\n >\n {labels.skip}\n </Button>\n )}\n <div className=\"ms-auto flex gap-2\">\n {step > 0 && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => go(step - 1)}>\n {labels.back}\n </Button>\n )}\n <Button size=\"sm\" onClick={() => (last ? onEnd('complete', step) : go(step + 1))}>\n {last ? labels.done : labels.next}\n </Button>\n </div>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport interface SpotlightProps {\n target: TourTarget;\n open: boolean;\n onDismiss: (reason: 'dismiss' | 'escape') => void;\n title: React.ReactNode;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /** Default \"Got it\". */\n dismissLabel?: string;\n /** An extra button, e.g. \"Add a provider\". */\n action?: React.ReactNode;\n /** Ring the target. Default true. */\n highlight?: boolean;\n className?: string;\n}\n\n/**\n * One contextual hint beside a target: a new feature, a setup nudge. It is\n * informational, so it does not take focus or block the page. Escape (unless\n * the person is typing elsewhere) or the dismiss button closes it. If the target is\n * not on screen, nothing renders.\n */\nfunction Spotlight({\n target,\n open,\n onDismiss,\n title,\n children,\n side = 'bottom',\n align = 'center',\n dismissLabel = 'Got it',\n action,\n highlight = true,\n className,\n}: SpotlightProps) {\n const key = typeof target === 'string' ? target : 'fn';\n const [found, setFound] = React.useState<{ key: string; el: Element | null } | null>(null);\n const el = open && found?.key === key ? found.el : null;\n const rect = useRect(el);\n const titleId = React.useId();\n const contentRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const controller = new AbortController();\n void waitForTourTarget(target, 2000, controller.signal).then((found) => {\n if (!controller.signal.aborted) setFound({ key, el: found });\n });\n return () => controller.abort();\n // A new function identity for `target` should not re-run the lookup.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, key]);\n\n if (!open || !el || !rect) return null;\n return (\n <>\n {highlight && <TourHighlight rect={rect} dim={false} />}\n <PopoverPrimitive.Root open modal={false}>\n <PopoverPrimitive.Anchor virtualRef={{ current: { getBoundingClientRect: () => rect } }} />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={contentRef}\n data-slot=\"spotlight\"\n role=\"note\"\n aria-labelledby={titleId}\n side={side}\n align={align}\n sideOffset={10}\n collisionPadding={12}\n className={cn(contentClasses, 'w-72', className)}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onDismiss('escape');\n }}\n >\n <span className=\"sr-only\" aria-live=\"polite\">\n Tip: {title}\n </span>\n <p id={titleId} className=\"text-sm font-semibold text-foreground\">\n {title}\n </p>\n <div className=\"mt-1.5 text-muted-foreground\">{children}</div>\n <div className=\"mt-3 flex justify-end gap-2\">\n {action}\n <Button\n size=\"sm\"\n variant={action ? 'ghost' : 'default'}\n onClick={() => onDismiss('dismiss')}\n >\n {dismissLabel}\n </Button>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport { Spotlight, Tour, TourAnchor };\n"],"mappings":"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"}
@@ -72,5 +72,5 @@ export declare function flattenTree(nodes: TreeNode[], expanded: ReadonlySet<str
72
72
  * render flat (role="treeitem" with aria-level), one tab stop for the tree,
73
73
  * arrows to move, Right/Left to open and close, Home/End, typeahead.
74
74
  */
75
- declare function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChange, selectionMode, selectionFollowsFocus, onAction, actionOnClick, expandOnClick, indentGuides, onLoadChildren, renderLabel, density, empty, onRowContextMenu, className, ...props }: TreeViewProps): React.JSX.Element;
75
+ declare const TreeView: React.ForwardRefExoticComponent<Omit<TreeViewProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
76
76
  export { TreeView };
@@ -1,4 +1,5 @@
1
- import { cn } from "../lib/utils.js";
1
+ "use client";
2
+ import { cn, focusRingClasses } from "../lib/utils.js";
2
3
  import { Spinner } from "./spinner.js";
3
4
  import * as React from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -34,7 +35,7 @@ var isTypingTarget = (target) => target instanceof HTMLElement && (target.isCont
34
35
  * render flat (role="treeitem" with aria-level), one tab stop for the tree,
35
36
  * arrows to move, Right/Left to open and close, Home/End, typeahead.
36
37
  */
37
- function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChange, selectionMode = "single", selectionFollowsFocus = false, onAction, actionOnClick = false, expandOnClick, indentGuides = false, onLoadChildren, renderLabel, density = "default", empty, onRowContextMenu, className, ...props }) {
38
+ var TreeView = React.forwardRef(function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChange, selectionMode = "single", selectionFollowsFocus = false, onAction, actionOnClick = false, expandOnClick, indentGuides = false, onLoadChildren, renderLabel, density = "default", empty, onRowContextMenu, className, ...props }, ref) {
38
39
  const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);
39
40
  const selectedSet = React.useMemo(() => new Set(selected), [selected]);
40
41
  const rows = React.useMemo(() => flattenTree(nodes, expandedSet), [nodes, expandedSet]);
@@ -180,7 +181,11 @@ function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChang
180
181
  children: empty
181
182
  });
182
183
  return /* @__PURE__ */ jsx("div", {
183
- ref: treeRef,
184
+ ref: (node) => {
185
+ treeRef.current = node;
186
+ if (typeof ref === "function") ref(node);
187
+ else if (ref) ref.current = node;
188
+ },
184
189
  role: "tree",
185
190
  tabIndex: -1,
186
191
  "data-slot": "tree-view",
@@ -220,7 +225,7 @@ function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChang
220
225
  tabIndex: node.id === tabStopId ? 0 : -1,
221
226
  "data-state": isSelected ? "selected" : void 0,
222
227
  style: { paddingInlineStart: `${(row.level - 1) * 12 + 4}px` },
223
- className: cn("relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 outline-none group-data-[density=compact]/tree:h-6", "hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground", "focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] aria-disabled:opacity-50", node.className),
228
+ className: cn("relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 group-data-[density=compact]/tree:h-6 pointer-coarse:h-11", "hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground", focusRingClasses, "focus-visible:outline-offset-[-2px] aria-disabled:opacity-50", node.className),
224
229
  onFocus: () => setFocusedId(node.id),
225
230
  onClick: (event) => {
226
231
  if (isTypingTarget(event.target)) return;
@@ -279,7 +284,7 @@ function TreeView({ nodes, expanded, onExpandedChange, selected, onSelectedChang
279
284
  })] }, node.id);
280
285
  })
281
286
  });
282
- }
287
+ });
283
288
  //#endregion
284
289
  export { TreeView, flattenTree };
285
290
 
@@ -1 +1 @@
1
- {"version":3,"file":"tree-view.js","names":[],"sources":["../../src/components/tree-view.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Spinner } from './spinner';\n\nexport interface TreeNode {\n /** Stable identity. Keep it across renders (a path, a database id). */\n id: string;\n /** Plain text: the accessible name and the typeahead key. */\n label: string;\n icon?: React.ReactNode;\n /** Undefined: a leaf, unless `hasChildren` says the children load later. */\n children?: TreeNode[];\n /** Expandable before its children are known; expanding calls onLoadChildren. */\n hasChildren?: boolean;\n /** Children are being fetched; shows a loading row while expanded. */\n loading?: boolean;\n disabled?: boolean;\n /** Trailing detail, such as a git status letter or a count. */\n meta?: React.ReactNode;\n /** Extra classes on the row, for app states such as an ignored file or a highlight. */\n className?: string;\n}\n\nexport interface TreeViewProps extends Omit<\n React.ComponentProps<'div'>,\n 'children' | 'onSelect' | 'aria-label'\n> {\n nodes: TreeNode[];\n /** Required: what the tree holds, e.g. \"Files\". */\n 'aria-label': string;\n expanded: string[];\n onExpandedChange: (ids: string[]) => void;\n selected: string[];\n onSelectedChange: (ids: string[]) => void;\n /** `multiple`: Shift extends a range, Ctrl/⌘ toggles, Space toggles. */\n selectionMode?: 'single' | 'multiple';\n /** Move selection with focus on arrow keys (a file explorer); default off. */\n selectionFollowsFocus?: boolean;\n /** Enter or double-click: open the file, run the item. */\n onAction?: (id: string) => void;\n /**\n * A plain click on a row without children also runs `onAction`, the way a\n * file explorer opens a file on one click. Default off.\n */\n actionOnClick?: boolean;\n /**\n * A plain click on an expandable row toggles it. Defaults to on for single\n * selection; turn it on for a multi-select explorer, where Shift and Ctrl/⌘\n * clicks still only select.\n */\n expandOnClick?: boolean;\n /** Draw a line per ancestor level while the pointer is over the tree. */\n indentGuides?: boolean;\n /** Called once when a node with `hasChildren` and no children expands. */\n onLoadChildren?: (id: string) => void;\n /** Replace the label, e.g. with a rename input. Keys typed in an input never move focus. */\n renderLabel?: (node: TreeNode) => React.ReactNode;\n /** `compact` 24px rows for desktop explorers, `default` 28px. */\n density?: 'compact' | 'default';\n /** Shown when `nodes` is empty. */\n empty?: React.ReactNode;\n /** Right-click or the context-menu key on a row. */\n onRowContextMenu?: (id: string, event: React.MouseEvent | React.KeyboardEvent) => void;\n}\n\ninterface Row {\n node: TreeNode;\n level: number;\n parentId: string | null;\n setSize: number;\n posInSet: number;\n expandable: boolean;\n expanded: boolean;\n}\n\nconst isExpandable = (n: TreeNode) => (n.children?.length ?? 0) > 0 || Boolean(n.hasChildren);\n\n/** The rows a person can see, in order, with the ARIA position data for each. */\nexport function flattenTree(nodes: TreeNode[], expanded: ReadonlySet<string>): Row[] {\n const rows: Row[] = [];\n const walk = (list: TreeNode[], level: number, parentId: string | null) => {\n list.forEach((node, i) => {\n const expandable = isExpandable(node);\n const open = expandable && expanded.has(node.id);\n rows.push({\n node,\n level,\n parentId,\n setSize: list.length,\n posInSet: i + 1,\n expandable,\n expanded: open,\n });\n if (open && node.children) walk(node.children, level + 1, node.id);\n });\n };\n walk(nodes, 1, null);\n return rows;\n}\n\nconst isTypingTarget = (target: EventTarget | null) =>\n target instanceof HTMLElement &&\n (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));\n\n/**\n * A file-explorer style tree. Everything is controlled: the app owns which\n * nodes are expanded and selected, and file operations are callbacks. Rows\n * render flat (role=\"treeitem\" with aria-level), one tab stop for the tree,\n * arrows to move, Right/Left to open and close, Home/End, typeahead.\n */\nfunction TreeView({\n nodes,\n expanded,\n onExpandedChange,\n selected,\n onSelectedChange,\n selectionMode = 'single',\n selectionFollowsFocus = false,\n onAction,\n actionOnClick = false,\n expandOnClick,\n indentGuides = false,\n onLoadChildren,\n renderLabel,\n density = 'default',\n empty,\n onRowContextMenu,\n className,\n ...props\n}: TreeViewProps) {\n const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);\n const selectedSet = React.useMemo(() => new Set(selected), [selected]);\n const rows = React.useMemo(() => flattenTree(nodes, expandedSet), [nodes, expandedSet]);\n const index = React.useMemo(() => new Map(rows.map((r, i) => [r.node.id, i])), [rows]);\n\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const anchorId = React.useRef<string | null>(null);\n const rowRefs = React.useRef(new Map<string, HTMLDivElement>());\n const treeRef = React.useRef<HTMLDivElement>(null);\n const hadFocus = React.useRef(false);\n const lastIds = React.useRef<string[]>([]);\n const lastParents = React.useRef(new Map<string, string | null>());\n const typeahead = React.useRef({ text: '', at: 0 });\n\n // The tab stop: the focused row if it is still visible, else the first\n // selected visible row, else the first row.\n const tabStopId =\n (focusedId && index.has(focusedId) ? focusedId : null) ??\n rows.find((r) => selectedSet.has(r.node.id))?.node.id ??\n rows[0]?.node.id ??\n null;\n\n const focusRow = React.useCallback((id: string) => {\n setFocusedId(id);\n rowRefs.current.get(id)?.focus();\n rowRefs.current.get(id)?.scrollIntoView?.({ block: 'nearest' });\n }, []);\n\n // Focus recovery: when the focused row disappears (deleted, or a parent\n // collapsed), move to its nearest visible ancestor, else the row now at its\n // old position. Only takes DOM focus if the tree had it.\n React.useLayoutEffect(() => {\n if (focusedId && !index.has(focusedId)) {\n let next: string | null = null;\n let parent = lastParents.current.get(focusedId) ?? null;\n while (parent && !index.has(parent)) parent = lastParents.current.get(parent) ?? null;\n const was = Math.max(0, lastIds.current.indexOf(focusedId));\n next = parent ?? rows[Math.min(was, rows.length - 1)]?.node.id ?? null;\n setFocusedId(next);\n const active = document.activeElement;\n if (next && hadFocus.current && (!active || active === document.body))\n rowRefs.current.get(next)?.focus();\n }\n lastIds.current = rows.map((r) => r.node.id);\n lastParents.current = new Map(rows.map((r) => [r.node.id, r.parentId]));\n }, [rows, index, focusedId]);\n\n const setExpanded = (row: Row, open: boolean) => {\n if (!row.expandable || row.expanded === open) return;\n const id = row.node.id;\n onExpandedChange(open ? [...expanded, id] : expanded.filter((e) => e !== id));\n if (open && !row.node.children && row.node.hasChildren) onLoadChildren?.(id);\n };\n\n const selectRange = (toId: string) => {\n const from = index.get(anchorId.current ?? toId) ?? 0;\n const to = index.get(toId) ?? 0;\n const [a, b] = from < to ? [from, to] : [to, from];\n onSelectedChange(\n rows\n .slice(a, b + 1)\n .filter((r) => !r.node.disabled)\n .map((r) => r.node.id),\n );\n };\n\n const select = (id: string, mode: 'replace' | 'toggle' | 'range') => {\n if (rows[index.get(id) ?? -1]?.node.disabled) return;\n if (selectionMode === 'single' || mode === 'replace') {\n anchorId.current = id;\n onSelectedChange([id]);\n } else if (mode === 'toggle') {\n anchorId.current = id;\n onSelectedChange(selectedSet.has(id) ? selected.filter((s) => s !== id) : [...selected, id]);\n } else selectRange(id);\n };\n\n const moveTo = (i: number, extend: boolean) => {\n const row = rows[Math.max(0, Math.min(rows.length - 1, i))];\n if (!row) return;\n focusRow(row.node.id);\n if (selectionMode === 'multiple' && extend) selectRange(row.node.id);\n else if (selectionFollowsFocus && !row.node.disabled) select(row.node.id, 'replace');\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n props.onKeyDown?.(event);\n if (event.defaultPrevented || isTypingTarget(event.target)) return;\n const id = tabStopId;\n if (!id) return;\n const i = index.get(id) ?? 0;\n const row = rows[i];\n if (!row) return;\n const mod = event.metaKey || event.ctrlKey;\n let handled = true;\n switch (event.key) {\n case 'ArrowDown':\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowUp':\n moveTo(i - 1, event.shiftKey);\n break;\n case 'Home':\n moveTo(0, event.shiftKey);\n break;\n case 'End':\n moveTo(rows.length - 1, event.shiftKey);\n break;\n case 'ArrowRight':\n if (row.expandable && !row.expanded) setExpanded(row, true);\n else if (row.expanded && rows[i + 1]?.parentId === row.node.id)\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowLeft':\n if (row.expanded) setExpanded(row, false);\n else if (row.parentId) moveTo(index.get(row.parentId) ?? i, event.shiftKey);\n break;\n case 'Enter':\n if (row.node.disabled) break;\n select(row.node.id, 'replace');\n onAction?.(row.node.id);\n break;\n case ' ':\n select(\n row.node.id,\n selectionMode === 'multiple' ? (event.shiftKey ? 'range' : 'toggle') : 'replace',\n );\n break;\n case 'a':\n if (mod && selectionMode === 'multiple') {\n onSelectedChange(rows.filter((r) => !r.node.disabled).map((r) => r.node.id));\n } else handled = false;\n break;\n case 'ContextMenu':\n onRowContextMenu?.(row.node.id, event);\n break;\n default:\n handled = false;\n }\n if (!handled && event.key.length === 1 && !mod && !event.altKey) {\n // Typeahead: jump to the next row whose label starts with what was typed.\n const now = event.timeStamp;\n const t = typeahead.current;\n t.text = now - t.at > 600 ? event.key : t.text + event.key;\n t.at = now;\n const query = t.text.toLowerCase();\n const start = t.text.length === 1 ? i + 1 : i;\n for (let k = 0; k < rows.length; k++) {\n const r = rows[(start + k) % rows.length];\n if (r && r.node.label.toLowerCase().startsWith(query)) {\n moveTo(index.get(r.node.id) ?? 0, false);\n break;\n }\n }\n handled = true;\n }\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n };\n\n if (rows.length === 0 && empty) {\n return (\n <div data-slot=\"tree-view\" data-empty className={cn('text-[13px]', className)} {...props}>\n {empty}\n </div>\n );\n }\n\n return (\n <div\n ref={treeRef}\n role=\"tree\"\n tabIndex={-1}\n data-slot=\"tree-view\"\n data-density={density}\n aria-multiselectable={selectionMode === 'multiple' || undefined}\n className={cn('group/tree flex flex-col py-1 text-[13px] select-none', className)}\n {...props}\n onKeyDown={onKeyDown}\n onFocus={(event) => {\n hadFocus.current = true;\n // Focus landing on the tree itself (a click on empty space) goes to the tab stop.\n if (event.target === event.currentTarget && tabStopId)\n rowRefs.current.get(tabStopId)?.focus();\n props.onFocus?.(event);\n }}\n onBlur={(event) => {\n if (!treeRef.current?.contains(event.relatedTarget)) hadFocus.current = false;\n props.onBlur?.(event);\n }}\n >\n {rows.map((row) => {\n const { node } = row;\n const isSelected = selectedSet.has(node.id);\n return (\n <React.Fragment key={node.id}>\n {/* Keys are handled once on the tree, for whichever row has focus. */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}\n <div\n ref={(el) => {\n if (el) rowRefs.current.set(node.id, el);\n else rowRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n data-slot=\"tree-item\"\n data-id={node.id}\n aria-level={row.level}\n aria-setsize={row.setSize}\n aria-posinset={row.posInSet}\n aria-expanded={row.expandable ? row.expanded : undefined}\n aria-selected={isSelected}\n aria-disabled={node.disabled || undefined}\n aria-busy={(row.expanded && node.loading) || undefined}\n aria-label={node.label}\n tabIndex={node.id === tabStopId ? 0 : -1}\n data-state={isSelected ? 'selected' : undefined}\n style={{ paddingInlineStart: `${(row.level - 1) * 12 + 4}px` }}\n className={cn(\n 'relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 outline-none group-data-[density=compact]/tree:h-6',\n 'hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground',\n 'focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] aria-disabled:opacity-50',\n node.className,\n )}\n onFocus={() => setFocusedId(node.id)}\n onClick={(event) => {\n if (isTypingTarget(event.target)) return;\n select(\n node.id,\n event.shiftKey ? 'range' : event.metaKey || event.ctrlKey ? 'toggle' : 'replace',\n );\n const plain = !event.shiftKey && !event.metaKey && !event.ctrlKey;\n if (plain && row.expandable && (expandOnClick ?? selectionMode === 'single'))\n setExpanded(row, !row.expanded);\n if (plain && !row.expandable && actionOnClick && !node.disabled)\n onAction?.(node.id);\n }}\n onDoubleClick={(event) => {\n if (!isTypingTarget(event.target) && !node.disabled) onAction?.(node.id);\n }}\n onContextMenu={\n onRowContextMenu ? (event) => onRowContextMenu(node.id, event) : undefined\n }\n >\n {indentGuides &&\n Array.from({ length: row.level - 1 }, (_, level) => (\n <span\n key={level}\n aria-hidden\n data-slot=\"tree-guide\"\n style={{ insetInlineStart: `${level * 12 + 12}px` }}\n className=\"pointer-events-none absolute inset-y-0 w-px bg-border opacity-0 transition-opacity group-hover/tree:opacity-100\"\n />\n ))}\n {/* A pointer shortcut only: Right and Left expand and collapse from the keyboard. */}\n <span\n aria-hidden\n className=\"flex size-4 shrink-0 items-center justify-center text-muted-foreground\"\n onClick={(event) => {\n if (!row.expandable) return;\n event.stopPropagation();\n setExpanded(row, !row.expanded);\n }}\n >\n {row.expandable && (\n <ChevronRight\n className={cn(\n 'size-3.5 motion-safe:transition-transform',\n row.expanded && 'rotate-90',\n )}\n />\n )}\n </span>\n {node.icon && (\n <span aria-hidden className=\"flex shrink-0 text-muted-foreground [&_svg]:size-4\">\n {node.icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">\n {renderLabel ? renderLabel(node) : node.label}\n </span>\n {node.meta && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{node.meta}</span>\n )}\n </div>\n {row.expanded && node.loading && !node.children?.length && (\n <div\n role=\"presentation\"\n data-slot=\"tree-loading\"\n style={{ paddingInlineStart: `${row.level * 12 + 8}px` }}\n className=\"flex h-7 items-center gap-2 text-muted-foreground group-data-[density=compact]/tree:h-6\"\n >\n <Spinner className=\"size-3.5\" label={`Loading ${node.label}`} />\n <span aria-hidden>Loading…</span>\n </div>\n )}\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n\nexport { TreeView };\n"],"mappings":"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"}
1
+ {"version":3,"file":"tree-view.js","names":[],"sources":["../../src/components/tree-view.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses } from '../lib/utils';\n\nimport { Spinner } from './spinner';\n\nexport interface TreeNode {\n /** Stable identity. Keep it across renders (a path, a database id). */\n id: string;\n /** Plain text: the accessible name and the typeahead key. */\n label: string;\n icon?: React.ReactNode;\n /** Undefined: a leaf, unless `hasChildren` says the children load later. */\n children?: TreeNode[];\n /** Expandable before its children are known; expanding calls onLoadChildren. */\n hasChildren?: boolean;\n /** Children are being fetched; shows a loading row while expanded. */\n loading?: boolean;\n disabled?: boolean;\n /** Trailing detail, such as a git status letter or a count. */\n meta?: React.ReactNode;\n /** Extra classes on the row, for app states such as an ignored file or a highlight. */\n className?: string;\n}\n\nexport interface TreeViewProps extends Omit<\n React.ComponentProps<'div'>,\n 'children' | 'onSelect' | 'aria-label'\n> {\n nodes: TreeNode[];\n /** Required: what the tree holds, e.g. \"Files\". */\n 'aria-label': string;\n expanded: string[];\n onExpandedChange: (ids: string[]) => void;\n selected: string[];\n onSelectedChange: (ids: string[]) => void;\n /** `multiple`: Shift extends a range, Ctrl/⌘ toggles, Space toggles. */\n selectionMode?: 'single' | 'multiple';\n /** Move selection with focus on arrow keys (a file explorer); default off. */\n selectionFollowsFocus?: boolean;\n /** Enter or double-click: open the file, run the item. */\n onAction?: (id: string) => void;\n /**\n * A plain click on a row without children also runs `onAction`, the way a\n * file explorer opens a file on one click. Default off.\n */\n actionOnClick?: boolean;\n /**\n * A plain click on an expandable row toggles it. Defaults to on for single\n * selection; turn it on for a multi-select explorer, where Shift and Ctrl/⌘\n * clicks still only select.\n */\n expandOnClick?: boolean;\n /** Draw a line per ancestor level while the pointer is over the tree. */\n indentGuides?: boolean;\n /** Called once when a node with `hasChildren` and no children expands. */\n onLoadChildren?: (id: string) => void;\n /** Replace the label, e.g. with a rename input. Keys typed in an input never move focus. */\n renderLabel?: (node: TreeNode) => React.ReactNode;\n /** `compact` 24px rows for desktop explorers, `default` 28px. */\n density?: 'compact' | 'default';\n /** Shown when `nodes` is empty. */\n empty?: React.ReactNode;\n /** Right-click or the context-menu key on a row. */\n onRowContextMenu?: (id: string, event: React.MouseEvent | React.KeyboardEvent) => void;\n}\n\ninterface Row {\n node: TreeNode;\n level: number;\n parentId: string | null;\n setSize: number;\n posInSet: number;\n expandable: boolean;\n expanded: boolean;\n}\n\nconst isExpandable = (n: TreeNode) => (n.children?.length ?? 0) > 0 || Boolean(n.hasChildren);\n\n/** The rows a person can see, in order, with the ARIA position data for each. */\nexport function flattenTree(nodes: TreeNode[], expanded: ReadonlySet<string>): Row[] {\n const rows: Row[] = [];\n const walk = (list: TreeNode[], level: number, parentId: string | null) => {\n list.forEach((node, i) => {\n const expandable = isExpandable(node);\n const open = expandable && expanded.has(node.id);\n rows.push({\n node,\n level,\n parentId,\n setSize: list.length,\n posInSet: i + 1,\n expandable,\n expanded: open,\n });\n if (open && node.children) walk(node.children, level + 1, node.id);\n });\n };\n walk(nodes, 1, null);\n return rows;\n}\n\nconst isTypingTarget = (target: EventTarget | null) =>\n target instanceof HTMLElement &&\n (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));\n\n/**\n * A file-explorer style tree. Everything is controlled: the app owns which\n * nodes are expanded and selected, and file operations are callbacks. Rows\n * render flat (role=\"treeitem\" with aria-level), one tab stop for the tree,\n * arrows to move, Right/Left to open and close, Home/End, typeahead.\n */\nconst TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(function TreeView(\n {\n nodes,\n expanded,\n onExpandedChange,\n selected,\n onSelectedChange,\n selectionMode = 'single',\n selectionFollowsFocus = false,\n onAction,\n actionOnClick = false,\n expandOnClick,\n indentGuides = false,\n onLoadChildren,\n renderLabel,\n density = 'default',\n empty,\n onRowContextMenu,\n className,\n ...props\n },\n ref,\n) {\n const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);\n const selectedSet = React.useMemo(() => new Set(selected), [selected]);\n const rows = React.useMemo(() => flattenTree(nodes, expandedSet), [nodes, expandedSet]);\n const index = React.useMemo(() => new Map(rows.map((r, i) => [r.node.id, i])), [rows]);\n\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const anchorId = React.useRef<string | null>(null);\n const rowRefs = React.useRef(new Map<string, HTMLDivElement>());\n const treeRef = React.useRef<HTMLDivElement>(null);\n const hadFocus = React.useRef(false);\n const lastIds = React.useRef<string[]>([]);\n const lastParents = React.useRef(new Map<string, string | null>());\n const typeahead = React.useRef({ text: '', at: 0 });\n\n // The tab stop: the focused row if it is still visible, else the first\n // selected visible row, else the first row.\n const tabStopId =\n (focusedId && index.has(focusedId) ? focusedId : null) ??\n rows.find((r) => selectedSet.has(r.node.id))?.node.id ??\n rows[0]?.node.id ??\n null;\n\n const focusRow = React.useCallback((id: string) => {\n setFocusedId(id);\n rowRefs.current.get(id)?.focus();\n rowRefs.current.get(id)?.scrollIntoView?.({ block: 'nearest' });\n }, []);\n\n // Focus recovery: when the focused row disappears (deleted, or a parent\n // collapsed), move to its nearest visible ancestor, else the row now at its\n // old position. Only takes DOM focus if the tree had it.\n React.useLayoutEffect(() => {\n if (focusedId && !index.has(focusedId)) {\n let next: string | null = null;\n let parent = lastParents.current.get(focusedId) ?? null;\n while (parent && !index.has(parent)) parent = lastParents.current.get(parent) ?? null;\n const was = Math.max(0, lastIds.current.indexOf(focusedId));\n next = parent ?? rows[Math.min(was, rows.length - 1)]?.node.id ?? null;\n setFocusedId(next);\n const active = document.activeElement;\n if (next && hadFocus.current && (!active || active === document.body))\n rowRefs.current.get(next)?.focus();\n }\n lastIds.current = rows.map((r) => r.node.id);\n lastParents.current = new Map(rows.map((r) => [r.node.id, r.parentId]));\n }, [rows, index, focusedId]);\n\n const setExpanded = (row: Row, open: boolean) => {\n if (!row.expandable || row.expanded === open) return;\n const id = row.node.id;\n onExpandedChange(open ? [...expanded, id] : expanded.filter((e) => e !== id));\n if (open && !row.node.children && row.node.hasChildren) onLoadChildren?.(id);\n };\n\n const selectRange = (toId: string) => {\n const from = index.get(anchorId.current ?? toId) ?? 0;\n const to = index.get(toId) ?? 0;\n const [a, b] = from < to ? [from, to] : [to, from];\n onSelectedChange(\n rows\n .slice(a, b + 1)\n .filter((r) => !r.node.disabled)\n .map((r) => r.node.id),\n );\n };\n\n const select = (id: string, mode: 'replace' | 'toggle' | 'range') => {\n if (rows[index.get(id) ?? -1]?.node.disabled) return;\n if (selectionMode === 'single' || mode === 'replace') {\n anchorId.current = id;\n onSelectedChange([id]);\n } else if (mode === 'toggle') {\n anchorId.current = id;\n onSelectedChange(selectedSet.has(id) ? selected.filter((s) => s !== id) : [...selected, id]);\n } else selectRange(id);\n };\n\n const moveTo = (i: number, extend: boolean) => {\n const row = rows[Math.max(0, Math.min(rows.length - 1, i))];\n if (!row) return;\n focusRow(row.node.id);\n if (selectionMode === 'multiple' && extend) selectRange(row.node.id);\n else if (selectionFollowsFocus && !row.node.disabled) select(row.node.id, 'replace');\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n props.onKeyDown?.(event);\n if (event.defaultPrevented || isTypingTarget(event.target)) return;\n const id = tabStopId;\n if (!id) return;\n const i = index.get(id) ?? 0;\n const row = rows[i];\n if (!row) return;\n const mod = event.metaKey || event.ctrlKey;\n let handled = true;\n switch (event.key) {\n case 'ArrowDown':\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowUp':\n moveTo(i - 1, event.shiftKey);\n break;\n case 'Home':\n moveTo(0, event.shiftKey);\n break;\n case 'End':\n moveTo(rows.length - 1, event.shiftKey);\n break;\n case 'ArrowRight':\n if (row.expandable && !row.expanded) setExpanded(row, true);\n else if (row.expanded && rows[i + 1]?.parentId === row.node.id)\n moveTo(i + 1, event.shiftKey);\n break;\n case 'ArrowLeft':\n if (row.expanded) setExpanded(row, false);\n else if (row.parentId) moveTo(index.get(row.parentId) ?? i, event.shiftKey);\n break;\n case 'Enter':\n if (row.node.disabled) break;\n select(row.node.id, 'replace');\n onAction?.(row.node.id);\n break;\n case ' ':\n select(\n row.node.id,\n selectionMode === 'multiple' ? (event.shiftKey ? 'range' : 'toggle') : 'replace',\n );\n break;\n case 'a':\n if (mod && selectionMode === 'multiple') {\n onSelectedChange(rows.filter((r) => !r.node.disabled).map((r) => r.node.id));\n } else handled = false;\n break;\n case 'ContextMenu':\n onRowContextMenu?.(row.node.id, event);\n break;\n default:\n handled = false;\n }\n if (!handled && event.key.length === 1 && !mod && !event.altKey) {\n // Typeahead: jump to the next row whose label starts with what was typed.\n const now = event.timeStamp;\n const t = typeahead.current;\n t.text = now - t.at > 600 ? event.key : t.text + event.key;\n t.at = now;\n const query = t.text.toLowerCase();\n const start = t.text.length === 1 ? i + 1 : i;\n for (let k = 0; k < rows.length; k++) {\n const r = rows[(start + k) % rows.length];\n if (r && r.node.label.toLowerCase().startsWith(query)) {\n moveTo(index.get(r.node.id) ?? 0, false);\n break;\n }\n }\n handled = true;\n }\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n };\n\n if (rows.length === 0 && empty) {\n return (\n <div data-slot=\"tree-view\" data-empty className={cn('text-[13px]', className)} {...props}>\n {empty}\n </div>\n );\n }\n\n return (\n <div\n ref={(node) => {\n treeRef.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n }}\n role=\"tree\"\n tabIndex={-1}\n data-slot=\"tree-view\"\n data-density={density}\n aria-multiselectable={selectionMode === 'multiple' || undefined}\n className={cn('group/tree flex flex-col py-1 text-[13px] select-none', className)}\n {...props}\n onKeyDown={onKeyDown}\n onFocus={(event) => {\n hadFocus.current = true;\n // Focus landing on the tree itself (a click on empty space) goes to the tab stop.\n if (event.target === event.currentTarget && tabStopId)\n rowRefs.current.get(tabStopId)?.focus();\n props.onFocus?.(event);\n }}\n onBlur={(event) => {\n if (!treeRef.current?.contains(event.relatedTarget)) hadFocus.current = false;\n props.onBlur?.(event);\n }}\n >\n {rows.map((row) => {\n const { node } = row;\n const isSelected = selectedSet.has(node.id);\n return (\n <React.Fragment key={node.id}>\n {/* Keys are handled once on the tree, for whichever row has focus. */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}\n <div\n ref={(el) => {\n if (el) rowRefs.current.set(node.id, el);\n else rowRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n data-slot=\"tree-item\"\n data-id={node.id}\n aria-level={row.level}\n aria-setsize={row.setSize}\n aria-posinset={row.posInSet}\n aria-expanded={row.expandable ? row.expanded : undefined}\n aria-selected={isSelected}\n aria-disabled={node.disabled || undefined}\n aria-busy={(row.expanded && node.loading) || undefined}\n aria-label={node.label}\n tabIndex={node.id === tabStopId ? 0 : -1}\n data-state={isSelected ? 'selected' : undefined}\n style={{ paddingInlineStart: `${(row.level - 1) * 12 + 4}px` }}\n className={cn(\n 'relative flex h-7 shrink-0 cursor-default items-center gap-1 rounded-sm pe-2 group-data-[density=compact]/tree:h-6 pointer-coarse:h-11',\n 'hover:bg-muted/70 data-[state=selected]:bg-brand-soft/70 data-[state=selected]:text-foreground',\n focusRingClasses,\n 'focus-visible:outline-offset-[-2px] aria-disabled:opacity-50',\n node.className,\n )}\n onFocus={() => setFocusedId(node.id)}\n onClick={(event) => {\n if (isTypingTarget(event.target)) return;\n select(\n node.id,\n event.shiftKey ? 'range' : event.metaKey || event.ctrlKey ? 'toggle' : 'replace',\n );\n const plain = !event.shiftKey && !event.metaKey && !event.ctrlKey;\n if (plain && row.expandable && (expandOnClick ?? selectionMode === 'single'))\n setExpanded(row, !row.expanded);\n if (plain && !row.expandable && actionOnClick && !node.disabled)\n onAction?.(node.id);\n }}\n onDoubleClick={(event) => {\n if (!isTypingTarget(event.target) && !node.disabled) onAction?.(node.id);\n }}\n onContextMenu={\n onRowContextMenu ? (event) => onRowContextMenu(node.id, event) : undefined\n }\n >\n {indentGuides &&\n Array.from({ length: row.level - 1 }, (_, level) => (\n <span\n key={level}\n aria-hidden\n data-slot=\"tree-guide\"\n style={{ insetInlineStart: `${level * 12 + 12}px` }}\n className=\"pointer-events-none absolute inset-y-0 w-px bg-border opacity-0 transition-opacity group-hover/tree:opacity-100\"\n />\n ))}\n {/* A pointer shortcut only: Right and Left expand and collapse from the keyboard. */}\n <span\n aria-hidden\n className=\"flex size-4 shrink-0 items-center justify-center text-muted-foreground\"\n onClick={(event) => {\n if (!row.expandable) return;\n event.stopPropagation();\n setExpanded(row, !row.expanded);\n }}\n >\n {row.expandable && (\n <ChevronRight\n className={cn(\n 'size-3.5 motion-safe:transition-transform',\n row.expanded && 'rotate-90',\n )}\n />\n )}\n </span>\n {node.icon && (\n <span aria-hidden className=\"flex shrink-0 text-muted-foreground [&_svg]:size-4\">\n {node.icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">\n {renderLabel ? renderLabel(node) : node.label}\n </span>\n {node.meta && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{node.meta}</span>\n )}\n </div>\n {row.expanded && node.loading && !node.children?.length && (\n <div\n role=\"presentation\"\n data-slot=\"tree-loading\"\n style={{ paddingInlineStart: `${row.level * 12 + 8}px` }}\n className=\"flex h-7 items-center gap-2 text-muted-foreground group-data-[density=compact]/tree:h-6\"\n >\n <Spinner className=\"size-3.5\" label={`Loading ${node.label}`} />\n <span aria-hidden>Loading…</span>\n </div>\n )}\n </React.Fragment>\n );\n })}\n </div>\n );\n});\n\nexport { TreeView };\n"],"mappings":"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"}
@@ -24,5 +24,5 @@ export interface VoiceRecorderProps extends React.ComponentProps<'div'> {
24
24
  * When the browser can't record or the microphone is blocked it says so and
25
25
  * how to fix it.
26
26
  */
27
- declare function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs, mimeType, label, className, ...props }: VoiceRecorderProps): React.JSX.Element;
27
+ declare const VoiceRecorder: React.ForwardRefExoticComponent<Omit<VoiceRecorderProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
28
28
  export { VoiceRecorder };
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { cn } from "../lib/utils.js";
2
3
  import { Button } from "./button.js";
3
4
  import { formatDuration } from "./audio-player.js";
@@ -20,7 +21,7 @@ function recordingSupported() {
20
21
  * When the browser can't record or the microphone is blocked it says so and
21
22
  * how to fix it.
22
23
  */
23
- function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e5, mimeType, label = "Record a voice message", className, ...props }) {
24
+ var VoiceRecorder = React.forwardRef(function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e5, mimeType, label = "Record a voice message", className, ...props }, ref) {
24
25
  const [state, setStateRaw] = React.useState("idle");
25
26
  const [elapsed, setElapsed] = React.useState(0);
26
27
  const [levels, setLevels] = React.useState(() => Array.from({ length: LEVEL_BARS }, () => 0));
@@ -158,6 +159,7 @@ function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e
158
159
  if (!(state === "recording" || state === "paused")) {
159
160
  const note = state === "denied" ? "Microphone access is blocked. Allow it in your browser’s site settings, then try again." : state === "unsupported" ? "This browser can’t record audio. Try a current Chrome, Edge, Firefox or Safari." : state === "error" ? "The microphone could not start. Check that one is connected and not in use." : null;
160
161
  return /* @__PURE__ */ jsxs("div", {
162
+ ref,
161
163
  "data-slot": "voice-recorder",
162
164
  "data-state": state,
163
165
  className: cn("inline-flex items-center gap-2", className),
@@ -180,6 +182,7 @@ function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e
180
182
  });
181
183
  }
182
184
  return /* @__PURE__ */ jsxs("div", {
185
+ ref,
183
186
  "data-slot": "voice-recorder",
184
187
  "data-state": state,
185
188
  role: "group",
@@ -233,7 +236,7 @@ function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e
233
236
  })
234
237
  ]
235
238
  });
236
- }
239
+ });
237
240
  //#endregion
238
241
  export { VoiceRecorder };
239
242
 
@@ -1 +1 @@
1
- {"version":3,"file":"voice-recorder.js","names":[],"sources":["../../src/components/voice-recorder.tsx"],"sourcesContent":["import Mic from '@burtson-labs/icons/react/mic';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport Square from '@burtson-labs/icons/react/square';\nimport Trash from '@burtson-labs/icons/react/trash';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { formatDuration } from './audio-player';\nimport { Button } from './button';\n\nexport type VoiceRecorderState =\n 'idle' | 'requesting' | 'recording' | 'paused' | 'denied' | 'unsupported' | 'error';\n\nexport interface VoiceRecording {\n blob: Blob;\n mimeType: string;\n durationMs: number;\n}\n\nexport interface VoiceRecorderProps extends React.ComponentProps<'div'> {\n /** The finished recording, after Stop. */\n onRecorded: (recording: VoiceRecording) => void;\n onCancel?: () => void;\n onStateChange?: (state: VoiceRecorderState) => void;\n /** Stops by itself at this length. Default 5 minutes. */\n maxDurationMs?: number;\n /** Preferred container, used when the browser supports it (e.g. \"audio/webm\"). */\n mimeType?: string;\n /** Accessible name of the record button. */\n label?: string;\n}\n\nconst LEVEL_BARS = 12;\n\nfunction recordingSupported() {\n return (\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function' &&\n typeof (globalThis as { MediaRecorder?: unknown }).MediaRecorder === 'function'\n );\n}\n\n/**\n * A microphone control for a composer: tap to record, then pause, stop to\n * keep the clip or discard it. While recording it shows a live level meter\n * and the elapsed time, in the recording colour (a state, not an error).\n * When the browser can't record or the microphone is blocked it says so and\n * how to fix it.\n */\nfunction VoiceRecorder({\n onRecorded,\n onCancel,\n onStateChange,\n maxDurationMs = 5 * 60 * 1000,\n mimeType,\n label = 'Record a voice message',\n className,\n ...props\n}: VoiceRecorderProps) {\n const [state, setStateRaw] = React.useState<VoiceRecorderState>('idle');\n const [elapsed, setElapsed] = React.useState(0);\n const [levels, setLevels] = React.useState<number[]>(() =>\n Array.from({ length: LEVEL_BARS }, () => 0),\n );\n const recorder = React.useRef<MediaRecorder | null>(null);\n const stream = React.useRef<MediaStream | null>(null);\n const chunks = React.useRef<Blob[]>([]);\n const started = React.useRef(0);\n const pausedTotal = React.useRef(0);\n const pausedAt = React.useRef(0);\n const discard = React.useRef(false);\n const frame = React.useRef(0);\n const audioCtx = React.useRef<AudioContext | null>(null);\n const tick = React.useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n const setState = React.useCallback(\n (s: VoiceRecorderState) => {\n setStateRaw(s);\n onStateChange?.(s);\n },\n [onStateChange],\n );\n\n const cleanup = React.useCallback(() => {\n cancelAnimationFrame(frame.current);\n clearInterval(tick.current);\n stream.current?.getTracks().forEach((t) => t.stop());\n stream.current = null;\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n setLevels(Array.from({ length: LEVEL_BARS }, () => 0));\n }, []);\n\n React.useEffect(() => cleanup, [cleanup]);\n\n const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n const recordedMs = () =>\n now() -\n started.current -\n pausedTotal.current -\n (pausedAt.current ? now() - pausedAt.current : 0);\n\n const meter = (s: MediaStream) => {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n try {\n const ctx = new Ctx();\n audioCtx.current = ctx;\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n ctx.createMediaStreamSource(s).connect(analyser);\n const data = new Uint8Array(analyser.frequencyBinCount);\n const draw = () => {\n analyser.getByteFrequencyData(data);\n const per = Math.floor(data.length / LEVEL_BARS) || 1;\n setLevels(\n Array.from({ length: LEVEL_BARS }, (_, i) => {\n let sum = 0;\n for (let j = i * per; j < (i + 1) * per; j++) sum += data[j] ?? 0;\n return Math.min(1, sum / per / 180);\n }),\n );\n frame.current = requestAnimationFrame(draw);\n };\n draw();\n } catch {\n // No meter is fine; recording still works. Drop the half-made context.\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n }\n };\n\n const start = async () => {\n if (!recordingSupported()) {\n setState('unsupported');\n return;\n }\n setState('requesting');\n let s: MediaStream;\n try {\n s = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n const name = (err as { name?: string })?.name;\n setState(name === 'NotAllowedError' || name === 'SecurityError' ? 'denied' : 'error');\n return;\n }\n stream.current = s;\n const type = mimeType && MediaRecorder.isTypeSupported?.(mimeType) ? mimeType : undefined;\n let rec: MediaRecorder;\n try {\n rec = new MediaRecorder(s, type ? { mimeType: type } : undefined);\n } catch {\n cleanup();\n setState('error');\n return;\n }\n recorder.current = rec;\n chunks.current = [];\n discard.current = false;\n rec.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) chunks.current.push(e.data);\n };\n rec.onstop = () => {\n const durationMs = Math.round(recordedMs());\n const mime = rec.mimeType || type || 'audio/webm';\n cleanup();\n setElapsed(0);\n setState('idle');\n if (!discard.current)\n onRecorded({ blob: new Blob(chunks.current, { type: mime }), mimeType: mime, durationMs });\n chunks.current = [];\n };\n started.current = now();\n pausedTotal.current = 0;\n pausedAt.current = 0;\n rec.start(250);\n setState('recording');\n meter(s);\n tick.current = setInterval(() => {\n const ms = recordedMs();\n setElapsed(ms);\n if (ms >= maxDurationMs && rec.state !== 'inactive') rec.stop();\n }, 200);\n };\n\n const pause = () => {\n const rec = recorder.current;\n if (!rec) return;\n if (rec.state === 'recording') {\n rec.pause();\n pausedAt.current = now();\n setState('paused');\n } else if (rec.state === 'paused') {\n rec.resume();\n pausedTotal.current += now() - pausedAt.current;\n pausedAt.current = 0;\n setState('recording');\n }\n };\n\n const stop = (keep: boolean) => {\n const rec = recorder.current;\n discard.current = !keep;\n if (rec && rec.state !== 'inactive') rec.stop();\n else {\n cleanup();\n setState('idle');\n }\n if (!keep) onCancel?.();\n };\n\n const live = state === 'recording' || state === 'paused';\n\n if (!live) {\n const note =\n state === 'denied'\n ? 'Microphone access is blocked. Allow it in your browser’s site settings, then try again.'\n : state === 'unsupported'\n ? 'This browser can’t record audio. Try a current Chrome, Edge, Firefox or Safari.'\n : state === 'error'\n ? 'The microphone could not start. Check that one is connected and not in use.'\n : null;\n return (\n <div\n data-slot=\"voice-recorder\"\n data-state={state}\n className={cn('inline-flex items-center gap-2', className)}\n {...props}\n >\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={state === 'requesting' || state === 'unsupported'}\n onClick={() => void start()}\n className=\"pointer-coarse:size-11\"\n >\n <Mic />\n </Button>\n {note && (\n <p role=\"alert\" className=\"max-w-xs text-xs text-muted-foreground\">\n {note}\n </p>\n )}\n </div>\n );\n }\n\n return (\n <div\n data-slot=\"voice-recorder\"\n data-state={state}\n role=\"group\"\n aria-label=\"Recording\"\n className={cn(\n 'inline-flex items-center gap-2 rounded-full border border-recording/30 bg-recording/8 py-1 pr-1 pl-3',\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n 'size-2 shrink-0 rounded-full bg-recording',\n state === 'recording' && 'motion-safe:animate-pulse',\n )}\n />\n <span aria-hidden className=\"flex h-5 items-center gap-[2px]\">\n {levels.map((l, i) => (\n <span\n key={i}\n className=\"w-[3px] rounded-full bg-recording/80 transition-[height] duration-[var(--duration-fast)] ease-[var(--ease-standard)]\"\n style={{ height: `${Math.max(15, Math.round(l * 100))}%` }}\n />\n ))}\n </span>\n <span\n role=\"timer\"\n aria-label={`${state === 'paused' ? 'Paused at' : 'Recording'} ${formatDuration(elapsed / 1000)}`}\n className=\"font-mono text-xs text-foreground tabular-nums\"\n >\n {formatDuration(elapsed / 1000)}\n </span>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={state === 'paused' ? 'Resume recording' : 'Pause recording'}\n onClick={pause}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n {state === 'paused' ? (\n <Play className=\"fill-current\" />\n ) : (\n <Pause className=\"fill-current\" />\n )}\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label=\"Discard recording\"\n onClick={() => stop(false)}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n <Trash />\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n aria-label=\"Stop and attach recording\"\n onClick={() => stop(true)}\n className=\"rounded-full bg-recording text-recording-foreground hover:bg-recording/90 pointer-coarse:size-11\"\n >\n <Square className=\"fill-current\" />\n </Button>\n </div>\n );\n}\n\nexport { VoiceRecorder };\n"],"mappings":"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"}
1
+ {"version":3,"file":"voice-recorder.js","names":[],"sources":["../../src/components/voice-recorder.tsx"],"sourcesContent":["import Mic from '@burtson-labs/icons/react/mic';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport Square from '@burtson-labs/icons/react/square';\nimport Trash from '@burtson-labs/icons/react/trash';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { formatDuration } from './audio-player';\nimport { Button } from './button';\n\nexport type VoiceRecorderState =\n 'idle' | 'requesting' | 'recording' | 'paused' | 'denied' | 'unsupported' | 'error';\n\nexport interface VoiceRecording {\n blob: Blob;\n mimeType: string;\n durationMs: number;\n}\n\nexport interface VoiceRecorderProps extends React.ComponentProps<'div'> {\n /** The finished recording, after Stop. */\n onRecorded: (recording: VoiceRecording) => void;\n onCancel?: () => void;\n onStateChange?: (state: VoiceRecorderState) => void;\n /** Stops by itself at this length. Default 5 minutes. */\n maxDurationMs?: number;\n /** Preferred container, used when the browser supports it (e.g. \"audio/webm\"). */\n mimeType?: string;\n /** Accessible name of the record button. */\n label?: string;\n}\n\nconst LEVEL_BARS = 12;\n\nfunction recordingSupported() {\n return (\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function' &&\n typeof (globalThis as { MediaRecorder?: unknown }).MediaRecorder === 'function'\n );\n}\n\n/**\n * A microphone control for a composer: tap to record, then pause, stop to\n * keep the clip or discard it. While recording it shows a live level meter\n * and the elapsed time, in the recording colour (a state, not an error).\n * When the browser can't record or the microphone is blocked it says so and\n * how to fix it.\n */\nconst VoiceRecorder = React.forwardRef<HTMLDivElement, VoiceRecorderProps>(function VoiceRecorder(\n {\n onRecorded,\n onCancel,\n onStateChange,\n maxDurationMs = 5 * 60 * 1000,\n mimeType,\n label = 'Record a voice message',\n className,\n ...props\n },\n ref,\n) {\n const [state, setStateRaw] = React.useState<VoiceRecorderState>('idle');\n const [elapsed, setElapsed] = React.useState(0);\n const [levels, setLevels] = React.useState<number[]>(() =>\n Array.from({ length: LEVEL_BARS }, () => 0),\n );\n const recorder = React.useRef<MediaRecorder | null>(null);\n const stream = React.useRef<MediaStream | null>(null);\n const chunks = React.useRef<Blob[]>([]);\n const started = React.useRef(0);\n const pausedTotal = React.useRef(0);\n const pausedAt = React.useRef(0);\n const discard = React.useRef(false);\n const frame = React.useRef(0);\n const audioCtx = React.useRef<AudioContext | null>(null);\n const tick = React.useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n const setState = React.useCallback(\n (s: VoiceRecorderState) => {\n setStateRaw(s);\n onStateChange?.(s);\n },\n [onStateChange],\n );\n\n const cleanup = React.useCallback(() => {\n cancelAnimationFrame(frame.current);\n clearInterval(tick.current);\n stream.current?.getTracks().forEach((t) => t.stop());\n stream.current = null;\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n setLevels(Array.from({ length: LEVEL_BARS }, () => 0));\n }, []);\n\n React.useEffect(() => cleanup, [cleanup]);\n\n const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n const recordedMs = () =>\n now() -\n started.current -\n pausedTotal.current -\n (pausedAt.current ? now() - pausedAt.current : 0);\n\n const meter = (s: MediaStream) => {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n try {\n const ctx = new Ctx();\n audioCtx.current = ctx;\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n ctx.createMediaStreamSource(s).connect(analyser);\n const data = new Uint8Array(analyser.frequencyBinCount);\n const draw = () => {\n analyser.getByteFrequencyData(data);\n const per = Math.floor(data.length / LEVEL_BARS) || 1;\n setLevels(\n Array.from({ length: LEVEL_BARS }, (_, i) => {\n let sum = 0;\n for (let j = i * per; j < (i + 1) * per; j++) sum += data[j] ?? 0;\n return Math.min(1, sum / per / 180);\n }),\n );\n frame.current = requestAnimationFrame(draw);\n };\n draw();\n } catch {\n // No meter is fine; recording still works. Drop the half-made context.\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n }\n };\n\n const start = async () => {\n if (!recordingSupported()) {\n setState('unsupported');\n return;\n }\n setState('requesting');\n let s: MediaStream;\n try {\n s = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n const name = (err as { name?: string })?.name;\n setState(name === 'NotAllowedError' || name === 'SecurityError' ? 'denied' : 'error');\n return;\n }\n stream.current = s;\n const type = mimeType && MediaRecorder.isTypeSupported?.(mimeType) ? mimeType : undefined;\n let rec: MediaRecorder;\n try {\n rec = new MediaRecorder(s, type ? { mimeType: type } : undefined);\n } catch {\n cleanup();\n setState('error');\n return;\n }\n recorder.current = rec;\n chunks.current = [];\n discard.current = false;\n rec.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) chunks.current.push(e.data);\n };\n rec.onstop = () => {\n const durationMs = Math.round(recordedMs());\n const mime = rec.mimeType || type || 'audio/webm';\n cleanup();\n setElapsed(0);\n setState('idle');\n if (!discard.current)\n onRecorded({ blob: new Blob(chunks.current, { type: mime }), mimeType: mime, durationMs });\n chunks.current = [];\n };\n started.current = now();\n pausedTotal.current = 0;\n pausedAt.current = 0;\n rec.start(250);\n setState('recording');\n meter(s);\n tick.current = setInterval(() => {\n const ms = recordedMs();\n setElapsed(ms);\n if (ms >= maxDurationMs && rec.state !== 'inactive') rec.stop();\n }, 200);\n };\n\n const pause = () => {\n const rec = recorder.current;\n if (!rec) return;\n if (rec.state === 'recording') {\n rec.pause();\n pausedAt.current = now();\n setState('paused');\n } else if (rec.state === 'paused') {\n rec.resume();\n pausedTotal.current += now() - pausedAt.current;\n pausedAt.current = 0;\n setState('recording');\n }\n };\n\n const stop = (keep: boolean) => {\n const rec = recorder.current;\n discard.current = !keep;\n if (rec && rec.state !== 'inactive') rec.stop();\n else {\n cleanup();\n setState('idle');\n }\n if (!keep) onCancel?.();\n };\n\n const live = state === 'recording' || state === 'paused';\n\n if (!live) {\n const note =\n state === 'denied'\n ? 'Microphone access is blocked. Allow it in your browser’s site settings, then try again.'\n : state === 'unsupported'\n ? 'This browser can’t record audio. Try a current Chrome, Edge, Firefox or Safari.'\n : state === 'error'\n ? 'The microphone could not start. Check that one is connected and not in use.'\n : null;\n return (\n <div\n ref={ref}\n data-slot=\"voice-recorder\"\n data-state={state}\n className={cn('inline-flex items-center gap-2', className)}\n {...props}\n >\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={state === 'requesting' || state === 'unsupported'}\n onClick={() => void start()}\n className=\"pointer-coarse:size-11\"\n >\n <Mic />\n </Button>\n {note && (\n <p role=\"alert\" className=\"max-w-xs text-xs text-muted-foreground\">\n {note}\n </p>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n data-slot=\"voice-recorder\"\n data-state={state}\n role=\"group\"\n aria-label=\"Recording\"\n className={cn(\n 'inline-flex items-center gap-2 rounded-full border border-recording/30 bg-recording/8 py-1 pr-1 pl-3',\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n 'size-2 shrink-0 rounded-full bg-recording',\n state === 'recording' && 'motion-safe:animate-pulse',\n )}\n />\n <span aria-hidden className=\"flex h-5 items-center gap-[2px]\">\n {levels.map((l, i) => (\n <span\n key={i}\n className=\"w-[3px] rounded-full bg-recording/80 transition-[height] duration-[var(--duration-fast)] ease-[var(--ease-standard)]\"\n style={{ height: `${Math.max(15, Math.round(l * 100))}%` }}\n />\n ))}\n </span>\n <span\n role=\"timer\"\n aria-label={`${state === 'paused' ? 'Paused at' : 'Recording'} ${formatDuration(elapsed / 1000)}`}\n className=\"font-mono text-xs text-foreground tabular-nums\"\n >\n {formatDuration(elapsed / 1000)}\n </span>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={state === 'paused' ? 'Resume recording' : 'Pause recording'}\n onClick={pause}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n {state === 'paused' ? (\n <Play className=\"fill-current\" />\n ) : (\n <Pause className=\"fill-current\" />\n )}\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label=\"Discard recording\"\n onClick={() => stop(false)}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n <Trash />\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n aria-label=\"Stop and attach recording\"\n onClick={() => stop(true)}\n className=\"rounded-full bg-recording text-recording-foreground hover:bg-recording/90 pointer-coarse:size-11\"\n >\n <Square className=\"fill-current\" />\n </Button>\n </div>\n );\n});\n\nexport { VoiceRecorder };\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { cn } from './lib/utils';
1
+ export { cn, focusRingClasses, noOutlineClasses, touchTargetClasses, touchTargetRowClasses, } from './lib/utils';
2
+ export { breakpoints, type Breakpoint, useMediaQuery, useNarrowerThan } from './lib/media';
2
3
  export { isApplePlatform, modKeyLabel, shortcutLabel } from './lib/platform';
3
4
  export * from './tokens';
4
5
  export * from './components/accordion';
@@ -29,7 +30,9 @@ export * from './components/dialog';
29
30
  export * from './components/dropdown-menu';
30
31
  export * from './components/editor-tabs';
31
32
  export * from './components/empty-state';
33
+ export * from './components/error-summary';
32
34
  export * from './components/field';
35
+ export * from './components/form-actions';
33
36
  export * from './components/icon-button';
34
37
  export * from './components/input';
35
38
  export * from './components/kbd';
@@ -39,7 +42,9 @@ export * from './components/menubar';
39
42
  export * from './components/message';
40
43
  export * from './components/message-actions';
41
44
  export * from './components/mobile-nav';
45
+ export * from './components/native-select';
42
46
  export * from './components/navigation-menu';
47
+ export * from './components/number-input';
43
48
  export * from './components/onboarding-checklist';
44
49
  export * from './components/page-header';
45
50
  export * from './components/pagination';