@hypoth-ui/cli 0.0.1 → 0.1.0

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 (375) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -115
  3. package/dist/{add-PDBC4JTE.js → add-V5PW73GC.js} +29 -17
  4. package/dist/{chunk-5LTQ2XVL.js → chunk-27CLUUVC.js} +0 -2
  5. package/dist/{chunk-YPKFYE45.js → chunk-NWIRSZUQ.js} +6 -13
  6. package/dist/{chunk-GJ6JOQ3Q.js → chunk-PBK72SJJ.js} +1 -1
  7. package/dist/{diff-BQEXG7HU.js → diff-776UATCA.js} +2 -2
  8. package/dist/index.js +5 -5
  9. package/dist/{init-7AZXYAPJ.js → init-GDU2PW7K.js} +10 -13
  10. package/dist/{list-X6ZLM2NQ.js → list-XDP5I537.js} +3 -3
  11. package/package.json +16 -12
  12. package/registry/components.json +1820 -206
  13. package/templates/accordion/index.tsx +266 -0
  14. package/templates/accordion/wc/accordion-content.ts +113 -0
  15. package/templates/accordion/wc/accordion-item.ts +111 -0
  16. package/templates/accordion/wc/accordion-trigger.ts +105 -0
  17. package/templates/accordion/wc/accordion.ts +213 -0
  18. package/templates/accordion/wc/index.ts +12 -0
  19. package/templates/alert/index.tsx +177 -0
  20. package/templates/alert/wc/alert.ts +167 -0
  21. package/templates/alert/wc/index.ts +1 -0
  22. package/templates/alert-dialog/index.tsx +360 -0
  23. package/templates/alert-dialog/wc/alert-dialog-action.ts +43 -0
  24. package/templates/alert-dialog/wc/alert-dialog-cancel.ts +43 -0
  25. package/templates/alert-dialog/wc/alert-dialog-content.ts +42 -0
  26. package/templates/alert-dialog/wc/alert-dialog-description.ts +34 -0
  27. package/templates/alert-dialog/wc/alert-dialog-footer.ts +25 -0
  28. package/templates/alert-dialog/wc/alert-dialog-header.ts +25 -0
  29. package/templates/alert-dialog/wc/alert-dialog-title.ts +34 -0
  30. package/templates/alert-dialog/wc/alert-dialog-trigger.ts +46 -0
  31. package/templates/alert-dialog/wc/alert-dialog.ts +302 -0
  32. package/templates/alert-dialog/wc/index.ts +13 -0
  33. package/templates/aspect-ratio/index.tsx +50 -0
  34. package/templates/aspect-ratio/wc/aspect-ratio.ts +78 -0
  35. package/templates/aspect-ratio/wc/index.ts +5 -0
  36. package/templates/avatar/avatar-group.tsx +88 -0
  37. package/templates/avatar/avatar.tsx +124 -0
  38. package/templates/avatar/index.tsx +33 -0
  39. package/templates/avatar/wc/avatar-group.ts +112 -0
  40. package/templates/avatar/wc/avatar.ts +184 -0
  41. package/templates/avatar/wc/index.ts +5 -0
  42. package/templates/badge/index.tsx +140 -0
  43. package/templates/badge/wc/badge.ts +119 -0
  44. package/templates/badge/wc/index.ts +9 -0
  45. package/templates/breadcrumb/index.tsx +157 -0
  46. package/templates/breadcrumb/wc/breadcrumb-item.ts +30 -0
  47. package/templates/breadcrumb/wc/breadcrumb-link.ts +70 -0
  48. package/templates/breadcrumb/wc/breadcrumb-list.ts +30 -0
  49. package/templates/breadcrumb/wc/breadcrumb-page.ts +32 -0
  50. package/templates/breadcrumb/wc/breadcrumb-separator.ts +31 -0
  51. package/templates/breadcrumb/wc/breadcrumb.ts +55 -0
  52. package/templates/breadcrumb/wc/index.ts +10 -0
  53. package/templates/button/button.tsx +119 -0
  54. package/templates/button/index.ts +1 -0
  55. package/templates/button/wc/button.ts +169 -0
  56. package/templates/calendar/index.tsx +149 -0
  57. package/templates/calendar/wc/calendar.ts +316 -0
  58. package/templates/calendar/wc/index.ts +4 -0
  59. package/templates/card/index.tsx +108 -0
  60. package/templates/card/wc/card-content.ts +25 -0
  61. package/templates/card/wc/card-footer.ts +25 -0
  62. package/templates/card/wc/card-header.ts +25 -0
  63. package/templates/card/wc/card.ts +43 -0
  64. package/templates/card/wc/index.ts +8 -0
  65. package/templates/checkbox/checkbox.tsx +85 -0
  66. package/templates/checkbox/wc/checkbox.ts +247 -0
  67. package/templates/collapsible/index.tsx +172 -0
  68. package/templates/collapsible/wc/collapsible-content.ts +97 -0
  69. package/templates/collapsible/wc/collapsible-trigger.ts +39 -0
  70. package/templates/collapsible/wc/collapsible.ts +143 -0
  71. package/templates/collapsible/wc/index.ts +7 -0
  72. package/templates/combobox/combobox-content.tsx +141 -0
  73. package/templates/combobox/combobox-context.ts +36 -0
  74. package/templates/combobox/combobox-empty.tsx +38 -0
  75. package/templates/combobox/combobox-input.tsx +159 -0
  76. package/templates/combobox/combobox-loading.tsx +38 -0
  77. package/templates/combobox/combobox-option.tsx +99 -0
  78. package/templates/combobox/combobox-root.tsx +207 -0
  79. package/templates/combobox/combobox-tag.tsx +62 -0
  80. package/templates/combobox/index.ts +62 -0
  81. package/templates/combobox/wc/combobox-content.ts +97 -0
  82. package/templates/combobox/wc/combobox-input.ts +134 -0
  83. package/templates/combobox/wc/combobox-option.ts +111 -0
  84. package/templates/combobox/wc/combobox-tag.ts +103 -0
  85. package/templates/combobox/wc/combobox.ts +981 -0
  86. package/templates/combobox/wc/index.ts +5 -0
  87. package/templates/command/index.tsx +279 -0
  88. package/templates/command/wc/command-empty.ts +24 -0
  89. package/templates/command/wc/command-group.ts +60 -0
  90. package/templates/command/wc/command-input.ts +136 -0
  91. package/templates/command/wc/command-item.ts +78 -0
  92. package/templates/command/wc/command-list.ts +103 -0
  93. package/templates/command/wc/command-loading.ts +24 -0
  94. package/templates/command/wc/command-separator.ts +23 -0
  95. package/templates/command/wc/command.ts +176 -0
  96. package/templates/context-menu/index.tsx +262 -0
  97. package/templates/context-menu/wc/context-menu-content.ts +41 -0
  98. package/templates/context-menu/wc/context-menu-item.ts +83 -0
  99. package/templates/context-menu/wc/context-menu-label.ts +30 -0
  100. package/templates/context-menu/wc/context-menu-separator.ts +28 -0
  101. package/templates/context-menu/wc/context-menu.ts +324 -0
  102. package/templates/context-menu/wc/index.ts +9 -0
  103. package/templates/data-table/index.tsx +263 -0
  104. package/templates/data-table/wc/data-table.ts +405 -0
  105. package/templates/data-table/wc/index.ts +10 -0
  106. package/templates/date-picker/date-picker-calendar.tsx +352 -0
  107. package/templates/date-picker/date-picker-content.tsx +121 -0
  108. package/templates/date-picker/date-picker-context.ts +46 -0
  109. package/templates/date-picker/date-picker-root.tsx +201 -0
  110. package/templates/date-picker/date-picker-trigger.tsx +95 -0
  111. package/templates/date-picker/index.ts +44 -0
  112. package/templates/date-picker/wc/date-picker-calendar.ts +457 -0
  113. package/templates/date-picker/wc/date-picker.ts +592 -0
  114. package/templates/date-picker/wc/date-utils.ts +467 -0
  115. package/templates/date-picker/wc/index.ts +3 -0
  116. package/templates/dialog/dialog-close.tsx +57 -0
  117. package/templates/dialog/dialog-content.tsx +106 -0
  118. package/templates/dialog/dialog-context.ts +24 -0
  119. package/templates/dialog/dialog-description.tsx +51 -0
  120. package/templates/dialog/dialog-root.tsx +104 -0
  121. package/templates/dialog/dialog-title.tsx +38 -0
  122. package/templates/dialog/dialog-trigger.tsx +94 -0
  123. package/templates/dialog/index.ts +52 -0
  124. package/templates/dialog/wc/dialog-content.ts +59 -0
  125. package/templates/dialog/wc/dialog-description.ts +58 -0
  126. package/templates/dialog/wc/dialog-title.ts +56 -0
  127. package/templates/dialog/wc/dialog.ts +411 -0
  128. package/templates/drawer/index.tsx +263 -0
  129. package/templates/drawer/wc/drawer-content.ts +150 -0
  130. package/templates/drawer/wc/drawer-description.ts +34 -0
  131. package/templates/drawer/wc/drawer-footer.ts +25 -0
  132. package/templates/drawer/wc/drawer-header.ts +25 -0
  133. package/templates/drawer/wc/drawer-title.ts +34 -0
  134. package/templates/drawer/wc/drawer.ts +348 -0
  135. package/templates/drawer/wc/index.ts +10 -0
  136. package/templates/dropdown-menu/index.tsx +454 -0
  137. package/templates/dropdown-menu/wc/dropdown-menu-checkbox-item.ts +93 -0
  138. package/templates/dropdown-menu/wc/dropdown-menu-content.ts +43 -0
  139. package/templates/dropdown-menu/wc/dropdown-menu-item.ts +85 -0
  140. package/templates/dropdown-menu/wc/dropdown-menu-label.ts +31 -0
  141. package/templates/dropdown-menu/wc/dropdown-menu-radio-group.ts +80 -0
  142. package/templates/dropdown-menu/wc/dropdown-menu-radio-item.ts +101 -0
  143. package/templates/dropdown-menu/wc/dropdown-menu-separator.ts +28 -0
  144. package/templates/dropdown-menu/wc/dropdown-menu.ts +358 -0
  145. package/templates/dropdown-menu/wc/index.ts +12 -0
  146. package/templates/field/field-description.tsx +39 -0
  147. package/templates/field/field-error.tsx +37 -0
  148. package/templates/field/field.tsx +46 -0
  149. package/templates/field/index.ts +4 -0
  150. package/templates/field/label.tsx +40 -0
  151. package/templates/field/wc/field-description.ts +42 -0
  152. package/templates/field/wc/field-error.ts +46 -0
  153. package/templates/field/wc/field.ts +210 -0
  154. package/templates/field/wc/label.ts +54 -0
  155. package/templates/file-upload/file-upload-context.ts +26 -0
  156. package/templates/file-upload/file-upload-dropzone.tsx +111 -0
  157. package/templates/file-upload/file-upload-input.tsx +86 -0
  158. package/templates/file-upload/file-upload-item.tsx +105 -0
  159. package/templates/file-upload/file-upload-root.tsx +115 -0
  160. package/templates/file-upload/index.ts +50 -0
  161. package/templates/file-upload/wc/file-upload.ts +380 -0
  162. package/templates/file-upload/wc/index.ts +1 -0
  163. package/templates/hover-card/index.tsx +203 -0
  164. package/templates/hover-card/wc/hover-card-content.ts +50 -0
  165. package/templates/hover-card/wc/hover-card.ts +382 -0
  166. package/templates/hover-card/wc/index.ts +6 -0
  167. package/templates/icon/icon.tsx +76 -0
  168. package/templates/icon/wc/icon-adapter.ts +108 -0
  169. package/templates/icon/wc/icon.ts +161 -0
  170. package/templates/input/input.tsx +130 -0
  171. package/templates/input/wc/input.ts +216 -0
  172. package/templates/layout/app-shell.tsx +177 -0
  173. package/templates/layout/box.tsx +53 -0
  174. package/templates/layout/center.tsx +42 -0
  175. package/templates/layout/container.tsx +43 -0
  176. package/templates/layout/flow.tsx +83 -0
  177. package/templates/layout/grid.tsx +79 -0
  178. package/templates/layout/index.ts +33 -0
  179. package/templates/layout/inline.tsx +16 -0
  180. package/templates/layout/page.tsx +43 -0
  181. package/templates/layout/section.tsx +39 -0
  182. package/templates/layout/spacer.tsx +30 -0
  183. package/templates/layout/split.tsx +47 -0
  184. package/templates/layout/stack.tsx +16 -0
  185. package/templates/layout/wc/app-shell.ts +58 -0
  186. package/templates/layout/wc/box.ts +117 -0
  187. package/templates/layout/wc/center.ts +78 -0
  188. package/templates/layout/wc/container.ts +77 -0
  189. package/templates/layout/wc/flow.ts +149 -0
  190. package/templates/layout/wc/footer.ts +57 -0
  191. package/templates/layout/wc/grid.ts +142 -0
  192. package/templates/layout/wc/header.ts +57 -0
  193. package/templates/layout/wc/index.ts +41 -0
  194. package/templates/layout/wc/main.ts +46 -0
  195. package/templates/layout/wc/page.ts +81 -0
  196. package/templates/layout/wc/section.ts +65 -0
  197. package/templates/layout/wc/spacer.ts +77 -0
  198. package/templates/layout/wc/split.ts +94 -0
  199. package/templates/layout/wc/wrap.ts +93 -0
  200. package/templates/layout/wrap.tsx +46 -0
  201. package/templates/link/link.tsx +109 -0
  202. package/templates/link/wc/link.ts +124 -0
  203. package/templates/list/index.tsx +55 -0
  204. package/templates/list/list-item.tsx +117 -0
  205. package/templates/list/list.tsx +115 -0
  206. package/templates/list/wc/index.ts +5 -0
  207. package/templates/list/wc/list-item.ts +127 -0
  208. package/templates/list/wc/list.ts +114 -0
  209. package/templates/menu/index.ts +49 -0
  210. package/templates/menu/menu-content.tsx +109 -0
  211. package/templates/menu/menu-context.ts +17 -0
  212. package/templates/menu/menu-item.tsx +108 -0
  213. package/templates/menu/menu-label.tsx +32 -0
  214. package/templates/menu/menu-root.tsx +108 -0
  215. package/templates/menu/menu-separator.tsx +24 -0
  216. package/templates/menu/menu-trigger.tsx +104 -0
  217. package/templates/menu/wc/menu-content.ts +67 -0
  218. package/templates/menu/wc/menu-item.ts +109 -0
  219. package/templates/menu/wc/menu.ts +449 -0
  220. package/templates/navigation-menu/index.tsx +328 -0
  221. package/templates/navigation-menu/wc/index.ts +12 -0
  222. package/templates/navigation-menu/wc/navigation-menu-content.ts +30 -0
  223. package/templates/navigation-menu/wc/navigation-menu-indicator.ts +30 -0
  224. package/templates/navigation-menu/wc/navigation-menu-item.ts +60 -0
  225. package/templates/navigation-menu/wc/navigation-menu-link.ts +97 -0
  226. package/templates/navigation-menu/wc/navigation-menu-list.ts +30 -0
  227. package/templates/navigation-menu/wc/navigation-menu-trigger.ts +110 -0
  228. package/templates/navigation-menu/wc/navigation-menu-viewport.ts +85 -0
  229. package/templates/navigation-menu/wc/navigation-menu.ts +272 -0
  230. package/templates/number-input/index.ts +46 -0
  231. package/templates/number-input/number-input-context.ts +38 -0
  232. package/templates/number-input/number-input-decrement.tsx +53 -0
  233. package/templates/number-input/number-input-field.tsx +93 -0
  234. package/templates/number-input/number-input-increment.tsx +53 -0
  235. package/templates/number-input/number-input-root.tsx +137 -0
  236. package/templates/number-input/wc/index.ts +1 -0
  237. package/templates/number-input/wc/number-input.ts +283 -0
  238. package/templates/pagination/index.tsx +198 -0
  239. package/templates/pagination/wc/index.ts +11 -0
  240. package/templates/pagination/wc/pagination-content.ts +30 -0
  241. package/templates/pagination/wc/pagination-ellipsis.ts +28 -0
  242. package/templates/pagination/wc/pagination-item.ts +30 -0
  243. package/templates/pagination/wc/pagination-link.ts +76 -0
  244. package/templates/pagination/wc/pagination-next.ts +69 -0
  245. package/templates/pagination/wc/pagination-previous.ts +69 -0
  246. package/templates/pagination/wc/pagination.ts +156 -0
  247. package/templates/pin-input/index.ts +39 -0
  248. package/templates/pin-input/pin-input-context.ts +30 -0
  249. package/templates/pin-input/pin-input-field.tsx +186 -0
  250. package/templates/pin-input/pin-input-root.tsx +120 -0
  251. package/templates/pin-input/wc/index.ts +1 -0
  252. package/templates/pin-input/wc/pin-input.ts +259 -0
  253. package/templates/popover/popover.tsx +121 -0
  254. package/templates/popover/wc/popover-content.ts +66 -0
  255. package/templates/popover/wc/popover.ts +343 -0
  256. package/templates/progress/index.tsx +117 -0
  257. package/templates/progress/wc/index.ts +4 -0
  258. package/templates/progress/wc/progress.ts +174 -0
  259. package/templates/radio/radio.tsx +43 -0
  260. package/templates/radio/wc/radio-group.ts +261 -0
  261. package/templates/radio/wc/radio.ts +145 -0
  262. package/templates/scroll-area/index.tsx +144 -0
  263. package/templates/scroll-area/wc/index.ts +8 -0
  264. package/templates/scroll-area/wc/scroll-area-scrollbar.ts +143 -0
  265. package/templates/scroll-area/wc/scroll-area-thumb.ts +225 -0
  266. package/templates/scroll-area/wc/scroll-area-viewport.ts +120 -0
  267. package/templates/scroll-area/wc/scroll-area.ts +63 -0
  268. package/templates/select/index.ts +57 -0
  269. package/templates/select/select-content.tsx +243 -0
  270. package/templates/select/select-context.ts +30 -0
  271. package/templates/select/select-group.tsx +53 -0
  272. package/templates/select/select-label.tsx +34 -0
  273. package/templates/select/select-option.tsx +97 -0
  274. package/templates/select/select-root.tsx +153 -0
  275. package/templates/select/select-separator.tsx +27 -0
  276. package/templates/select/select-trigger.tsx +112 -0
  277. package/templates/select/select-value.tsx +48 -0
  278. package/templates/select/wc/index.ts +6 -0
  279. package/templates/select/wc/select-content.ts +89 -0
  280. package/templates/select/wc/select-group.ts +82 -0
  281. package/templates/select/wc/select-label.ts +49 -0
  282. package/templates/select/wc/select-option.ts +111 -0
  283. package/templates/select/wc/select-trigger.ts +101 -0
  284. package/templates/select/wc/select.ts +840 -0
  285. package/templates/separator/index.tsx +49 -0
  286. package/templates/separator/wc/index.ts +5 -0
  287. package/templates/separator/wc/separator.ts +60 -0
  288. package/templates/sheet/index.tsx +291 -0
  289. package/templates/sheet/wc/index.ts +12 -0
  290. package/templates/sheet/wc/sheet-close.ts +43 -0
  291. package/templates/sheet/wc/sheet-content.ts +47 -0
  292. package/templates/sheet/wc/sheet-description.ts +34 -0
  293. package/templates/sheet/wc/sheet-footer.ts +25 -0
  294. package/templates/sheet/wc/sheet-header.ts +25 -0
  295. package/templates/sheet/wc/sheet-overlay.ts +23 -0
  296. package/templates/sheet/wc/sheet-title.ts +34 -0
  297. package/templates/sheet/wc/sheet.ts +336 -0
  298. package/templates/skeleton/index.tsx +131 -0
  299. package/templates/skeleton/wc/index.ts +10 -0
  300. package/templates/skeleton/wc/skeleton.ts +107 -0
  301. package/templates/slider/index.ts +41 -0
  302. package/templates/slider/slider-context.ts +36 -0
  303. package/templates/slider/slider-range.tsx +59 -0
  304. package/templates/slider/slider-root.tsx +166 -0
  305. package/templates/slider/slider-thumb.tsx +213 -0
  306. package/templates/slider/slider-track.tsx +113 -0
  307. package/templates/slider/wc/index.ts +1 -0
  308. package/templates/slider/wc/slider.ts +465 -0
  309. package/templates/spinner/spinner.tsx +64 -0
  310. package/templates/spinner/wc/spinner.ts +70 -0
  311. package/templates/stepper/index.tsx +230 -0
  312. package/templates/stepper/wc/index.ts +12 -0
  313. package/templates/stepper/wc/stepper-content.ts +30 -0
  314. package/templates/stepper/wc/stepper-description.ts +25 -0
  315. package/templates/stepper/wc/stepper-indicator.ts +30 -0
  316. package/templates/stepper/wc/stepper-item.ts +55 -0
  317. package/templates/stepper/wc/stepper-separator.ts +29 -0
  318. package/templates/stepper/wc/stepper-title.ts +25 -0
  319. package/templates/stepper/wc/stepper-trigger.ts +67 -0
  320. package/templates/stepper/wc/stepper.ts +164 -0
  321. package/templates/switch/switch.tsx +90 -0
  322. package/templates/switch/wc/switch.ts +228 -0
  323. package/templates/table/body.tsx +21 -0
  324. package/templates/table/cell.tsx +44 -0
  325. package/templates/table/head.tsx +112 -0
  326. package/templates/table/header.tsx +21 -0
  327. package/templates/table/index.tsx +93 -0
  328. package/templates/table/root.tsx +82 -0
  329. package/templates/table/row.tsx +36 -0
  330. package/templates/table/wc/index.ts +9 -0
  331. package/templates/table/wc/table-body.ts +32 -0
  332. package/templates/table/wc/table-cell.ts +58 -0
  333. package/templates/table/wc/table-head.ts +129 -0
  334. package/templates/table/wc/table-header.ts +32 -0
  335. package/templates/table/wc/table-row.ts +50 -0
  336. package/templates/table/wc/table.ts +93 -0
  337. package/templates/tabs/index.tsx +222 -0
  338. package/templates/tabs/wc/index.ts +8 -0
  339. package/templates/tabs/wc/tabs-content.ts +82 -0
  340. package/templates/tabs/wc/tabs-list.ts +56 -0
  341. package/templates/tabs/wc/tabs-trigger.ts +136 -0
  342. package/templates/tabs/wc/tabs.ts +202 -0
  343. package/templates/tag/index.tsx +186 -0
  344. package/templates/tag/wc/index.ts +4 -0
  345. package/templates/tag/wc/tag.ts +166 -0
  346. package/templates/text/text.tsx +100 -0
  347. package/templates/text/wc/text.ts +94 -0
  348. package/templates/textarea/textarea.tsx +134 -0
  349. package/templates/textarea/wc/textarea.ts +280 -0
  350. package/templates/time-picker/index.ts +42 -0
  351. package/templates/time-picker/time-picker-context.ts +28 -0
  352. package/templates/time-picker/time-picker-root.tsx +113 -0
  353. package/templates/time-picker/time-picker-segment.tsx +91 -0
  354. package/templates/time-picker/wc/index.ts +1 -0
  355. package/templates/time-picker/wc/time-picker.ts +221 -0
  356. package/templates/toast/index.tsx +71 -0
  357. package/templates/toast/provider.tsx +228 -0
  358. package/templates/toast/toast.tsx +142 -0
  359. package/templates/toast/use-toast.ts +89 -0
  360. package/templates/toast/wc/index.ts +15 -0
  361. package/templates/toast/wc/toast-controller.ts +282 -0
  362. package/templates/toast/wc/toast-provider.ts +161 -0
  363. package/templates/toast/wc/toast.ts +165 -0
  364. package/templates/tooltip/tooltip.tsx +62 -0
  365. package/templates/tooltip/wc/tooltip-content.ts +64 -0
  366. package/templates/tooltip/wc/tooltip.ts +289 -0
  367. package/templates/tree/index.tsx +60 -0
  368. package/templates/tree/tree-item.tsx +131 -0
  369. package/templates/tree/tree.tsx +138 -0
  370. package/templates/tree/wc/index.ts +11 -0
  371. package/templates/tree/wc/tree-item.ts +273 -0
  372. package/templates/tree/wc/tree-utils.ts +143 -0
  373. package/templates/tree/wc/tree.ts +139 -0
  374. package/templates/visually-hidden/visually-hidden.tsx +45 -0
  375. package/templates/visually-hidden/wc/visually-hidden.ts +64 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * ScrollAreaThumb component - draggable scrollbar thumb.
3
+ *
4
+ * @element ds-scroll-area-thumb
5
+ */
6
+
7
+ import { html } from "lit";
8
+ import { DSElement } from "../../base/ds-element.js";
9
+ import { define } from "../../registry/define.js";
10
+
11
+ /**
12
+ * Cached dimensions for scrollbar track to avoid layout thrashing.
13
+ * Updated via ResizeObserver instead of per-frame getBoundingClientRect calls.
14
+ */
15
+ interface CachedDimensions {
16
+ width: number;
17
+ height: number;
18
+ }
19
+
20
+ export class DsScrollAreaThumb extends DSElement {
21
+ private isDragging = false;
22
+ private startY = 0;
23
+ private startX = 0;
24
+ private startScrollTop = 0;
25
+ private startScrollLeft = 0;
26
+
27
+ /** Cached scrollbar dimensions to avoid layout thrashing during drag */
28
+ private cachedDimensions: CachedDimensions = { width: 0, height: 0 };
29
+
30
+ /** ResizeObserver for tracking scrollbar size changes */
31
+ private resizeObserver: ResizeObserver | null = null;
32
+
33
+ /** Scrollbar element reference for ResizeObserver */
34
+ private scrollbarElement: HTMLElement | null = null;
35
+
36
+ override connectedCallback(): void {
37
+ super.connectedCallback();
38
+
39
+ this.addEventListener("mousedown", this.handleMouseDown);
40
+ this.addEventListener("touchstart", this.handleTouchStart, { passive: false });
41
+
42
+ // Set up ResizeObserver for dimension caching
43
+ this.setupResizeObserver();
44
+ }
45
+
46
+ override disconnectedCallback(): void {
47
+ super.disconnectedCallback();
48
+ this.removeEventListener("mousedown", this.handleMouseDown);
49
+ this.removeEventListener("touchstart", this.handleTouchStart);
50
+ this.cleanupDrag();
51
+ this.cleanupResizeObserver();
52
+ }
53
+
54
+ /**
55
+ * Set up ResizeObserver to cache scrollbar dimensions.
56
+ * This avoids calling getBoundingClientRect on every drag event.
57
+ */
58
+ private setupResizeObserver(): void {
59
+ // Clean up any existing observer
60
+ this.cleanupResizeObserver();
61
+
62
+ const scrollbar = this.closest("ds-scroll-area-scrollbar") as HTMLElement | null;
63
+ if (!scrollbar) return;
64
+
65
+ this.scrollbarElement = scrollbar;
66
+
67
+ // Create ResizeObserver to update cached dimensions on resize
68
+ this.resizeObserver = new ResizeObserver((entries) => {
69
+ for (const entry of entries) {
70
+ if (entry.target === this.scrollbarElement) {
71
+ // Use contentRect for dimensions (avoids additional layout)
72
+ this.cachedDimensions = {
73
+ width: entry.contentRect.width,
74
+ height: entry.contentRect.height,
75
+ };
76
+ }
77
+ }
78
+ });
79
+
80
+ this.resizeObserver.observe(scrollbar);
81
+
82
+ // Initialize cached dimensions
83
+ const rect = scrollbar.getBoundingClientRect();
84
+ this.cachedDimensions = {
85
+ width: rect.width,
86
+ height: rect.height,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Clean up ResizeObserver on disconnect.
92
+ */
93
+ private cleanupResizeObserver(): void {
94
+ if (this.resizeObserver) {
95
+ this.resizeObserver.disconnect();
96
+ this.resizeObserver = null;
97
+ }
98
+ this.scrollbarElement = null;
99
+ }
100
+
101
+ private getViewport(): HTMLElement | null {
102
+ const scrollArea = this.closest("ds-scroll-area");
103
+ return scrollArea?.querySelector("ds-scroll-area-viewport") || null;
104
+ }
105
+
106
+ private getOrientation(): "vertical" | "horizontal" {
107
+ const scrollbar = this.closest("ds-scroll-area-scrollbar");
108
+ return (scrollbar?.getAttribute("orientation") as "vertical" | "horizontal") || "vertical";
109
+ }
110
+
111
+ private handleMouseDown = (event: MouseEvent): void => {
112
+ event.preventDefault();
113
+ this.startDrag(event.clientX, event.clientY);
114
+
115
+ document.addEventListener("mousemove", this.handleMouseMove);
116
+ document.addEventListener("mouseup", this.handleMouseUp);
117
+ };
118
+
119
+ private handleMouseMove = (event: MouseEvent): void => {
120
+ if (!this.isDragging) return;
121
+ this.drag(event.clientX, event.clientY);
122
+ };
123
+
124
+ private handleMouseUp = (): void => {
125
+ this.cleanupDrag();
126
+ };
127
+
128
+ private handleTouchStart = (event: TouchEvent): void => {
129
+ const touch = event.touches[0];
130
+ if (!touch) return;
131
+
132
+ event.preventDefault();
133
+ this.startDrag(touch.clientX, touch.clientY);
134
+
135
+ document.addEventListener("touchmove", this.handleTouchMove, { passive: false });
136
+ document.addEventListener("touchend", this.handleTouchEnd);
137
+ };
138
+
139
+ private handleTouchMove = (event: TouchEvent): void => {
140
+ if (!this.isDragging) return;
141
+
142
+ const touch = event.touches[0];
143
+ if (!touch) return;
144
+
145
+ event.preventDefault();
146
+ this.drag(touch.clientX, touch.clientY);
147
+ };
148
+
149
+ private handleTouchEnd = (): void => {
150
+ this.cleanupDrag();
151
+ };
152
+
153
+ private startDrag(clientX: number, clientY: number): void {
154
+ const viewport = this.getViewport();
155
+ if (!viewport) return;
156
+
157
+ this.isDragging = true;
158
+ this.startX = clientX;
159
+ this.startY = clientY;
160
+ this.startScrollTop = viewport.scrollTop;
161
+ this.startScrollLeft = viewport.scrollLeft;
162
+
163
+ this.setAttribute("data-dragging", "");
164
+ document.body.style.userSelect = "none";
165
+ }
166
+
167
+ /**
168
+ * Handle drag movement using cached dimensions.
169
+ * Uses ResizeObserver-cached values instead of getBoundingClientRect
170
+ * to avoid layout thrashing during drag operations.
171
+ */
172
+ private drag(clientX: number, clientY: number): void {
173
+ const viewport = this.getViewport();
174
+ if (!viewport) return;
175
+
176
+ const orientation = this.getOrientation();
177
+
178
+ // Use cached dimensions instead of calling getBoundingClientRect
179
+ // This prevents layout thrashing during high-frequency drag events
180
+ const { width: trackWidth, height: trackHeight } = this.cachedDimensions;
181
+
182
+ if (orientation === "vertical") {
183
+ const deltaY = clientY - this.startY;
184
+ const scrollableHeight = viewport.scrollHeight - viewport.clientHeight;
185
+
186
+ // Guard against division by zero
187
+ if (trackHeight > 0 && scrollableHeight > 0) {
188
+ const scrollDelta = (deltaY / trackHeight) * scrollableHeight;
189
+ viewport.scrollTop = this.startScrollTop + scrollDelta;
190
+ }
191
+ } else {
192
+ const deltaX = clientX - this.startX;
193
+ const scrollableWidth = viewport.scrollWidth - viewport.clientWidth;
194
+
195
+ // Guard against division by zero
196
+ if (trackWidth > 0 && scrollableWidth > 0) {
197
+ const scrollDelta = (deltaX / trackWidth) * scrollableWidth;
198
+ viewport.scrollLeft = this.startScrollLeft + scrollDelta;
199
+ }
200
+ }
201
+ }
202
+
203
+ private cleanupDrag(): void {
204
+ this.isDragging = false;
205
+ this.removeAttribute("data-dragging");
206
+ document.body.style.userSelect = "";
207
+
208
+ document.removeEventListener("mousemove", this.handleMouseMove);
209
+ document.removeEventListener("mouseup", this.handleMouseUp);
210
+ document.removeEventListener("touchmove", this.handleTouchMove);
211
+ document.removeEventListener("touchend", this.handleTouchEnd);
212
+ }
213
+
214
+ override render() {
215
+ return html``;
216
+ }
217
+ }
218
+
219
+ define("ds-scroll-area-thumb", DsScrollAreaThumb);
220
+
221
+ declare global {
222
+ interface HTMLElementTagNameMap {
223
+ "ds-scroll-area-thumb": DsScrollAreaThumb;
224
+ }
225
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * ScrollAreaViewport component - scrollable content container.
3
+ *
4
+ * @element ds-scroll-area-viewport
5
+ *
6
+ * @slot - Scrollable content
7
+ */
8
+
9
+ import { html } from "lit";
10
+ import { DSElement } from "../../base/ds-element.js";
11
+ import { emitEvent } from "../../events/emit.js";
12
+ import { define } from "../../registry/define.js";
13
+
14
+ export class DsScrollAreaViewport extends DSElement {
15
+ private resizeObserver: ResizeObserver | null = null;
16
+
17
+ override connectedCallback(): void {
18
+ super.connectedCallback();
19
+ this.setAttribute("tabindex", "0");
20
+
21
+ this.addEventListener("scroll", this.handleScroll);
22
+
23
+ // Observe for overflow changes
24
+ this.resizeObserver = new ResizeObserver(() => {
25
+ this.updateOverflowState();
26
+ });
27
+ this.resizeObserver.observe(this);
28
+
29
+ this.updateComplete.then(() => {
30
+ this.updateOverflowState();
31
+ });
32
+ }
33
+
34
+ override disconnectedCallback(): void {
35
+ super.disconnectedCallback();
36
+ this.removeEventListener("scroll", this.handleScroll);
37
+ this.resizeObserver?.disconnect();
38
+ this.resizeObserver = null;
39
+ }
40
+
41
+ private handleScroll = (): void => {
42
+ this.updateScrollbarPositions();
43
+
44
+ emitEvent(this, "ds:scroll", {
45
+ detail: {
46
+ scrollTop: this.scrollTop,
47
+ scrollLeft: this.scrollLeft,
48
+ scrollHeight: this.scrollHeight,
49
+ scrollWidth: this.scrollWidth,
50
+ },
51
+ bubbles: true,
52
+ });
53
+ };
54
+
55
+ private updateOverflowState(): void {
56
+ const hasVerticalOverflow = this.scrollHeight > this.clientHeight;
57
+ const hasHorizontalOverflow = this.scrollWidth > this.clientWidth;
58
+
59
+ this.toggleAttribute("data-overflow-y", hasVerticalOverflow);
60
+ this.toggleAttribute("data-overflow-x", hasHorizontalOverflow);
61
+
62
+ // Update scrollbar visibility
63
+ const scrollArea = this.closest("ds-scroll-area");
64
+ if (scrollArea) {
65
+ scrollArea.toggleAttribute("data-overflow-y", hasVerticalOverflow);
66
+ scrollArea.toggleAttribute("data-overflow-x", hasHorizontalOverflow);
67
+ }
68
+
69
+ this.updateScrollbarPositions();
70
+ }
71
+
72
+ private updateScrollbarPositions(): void {
73
+ const scrollArea = this.closest("ds-scroll-area");
74
+ if (!scrollArea) return;
75
+
76
+ const scrollbars = scrollArea.querySelectorAll("ds-scroll-area-scrollbar");
77
+
78
+ for (const scrollbar of scrollbars) {
79
+ const orientation = scrollbar.getAttribute("orientation") || "vertical";
80
+ const thumb = scrollbar.querySelector("ds-scroll-area-thumb");
81
+
82
+ if (!thumb) continue;
83
+
84
+ if (orientation === "vertical") {
85
+ const scrollRatio = this.scrollTop / (this.scrollHeight - this.clientHeight);
86
+ const thumbHeight = (this.clientHeight / this.scrollHeight) * 100;
87
+ const thumbTop = scrollRatio * (100 - thumbHeight);
88
+
89
+ (thumb as HTMLElement).style.height = `${Math.max(10, thumbHeight)}%`;
90
+ (thumb as HTMLElement).style.top = `${thumbTop}%`;
91
+ } else {
92
+ const scrollRatio = this.scrollLeft / (this.scrollWidth - this.clientWidth);
93
+ const thumbWidth = (this.clientWidth / this.scrollWidth) * 100;
94
+ const thumbLeft = scrollRatio * (100 - thumbWidth);
95
+
96
+ (thumb as HTMLElement).style.width = `${Math.max(10, thumbWidth)}%`;
97
+ (thumb as HTMLElement).style.left = `${thumbLeft}%`;
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Scrolls to a specific position.
104
+ */
105
+ public scrollToPosition(options: ScrollToOptions): void {
106
+ this.scrollTo(options);
107
+ }
108
+
109
+ override render() {
110
+ return html`<slot></slot>`;
111
+ }
112
+ }
113
+
114
+ define("ds-scroll-area-viewport", DsScrollAreaViewport);
115
+
116
+ declare global {
117
+ interface HTMLElementTagNameMap {
118
+ "ds-scroll-area-viewport": DsScrollAreaViewport;
119
+ }
120
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * ScrollArea component - custom scrollbar container.
3
+ *
4
+ * Provides styled scrollbars with consistent appearance across browsers.
5
+ *
6
+ * @element ds-scroll-area
7
+ *
8
+ * @slot - Scrollable content
9
+ *
10
+ * @example
11
+ * ```html
12
+ * <ds-scroll-area style="height: 200px">
13
+ * <ds-scroll-area-viewport>
14
+ * <!-- Long content here -->
15
+ * </ds-scroll-area-viewport>
16
+ * <ds-scroll-area-scrollbar orientation="vertical">
17
+ * <ds-scroll-area-thumb></ds-scroll-area-thumb>
18
+ * </ds-scroll-area-scrollbar>
19
+ * </ds-scroll-area>
20
+ * ```
21
+ */
22
+
23
+ import { html } from "lit";
24
+ import { property } from "lit/decorators.js";
25
+ import { DSElement } from "../../base/ds-element.js";
26
+ import { define } from "../../registry/define.js";
27
+
28
+ // Import child components
29
+ import "./scroll-area-viewport.js";
30
+ import "./scroll-area-scrollbar.js";
31
+ import "./scroll-area-thumb.js";
32
+
33
+ export type ScrollAreaType = "auto" | "always" | "scroll" | "hover";
34
+
35
+ export class DsScrollArea extends DSElement {
36
+ /** When scrollbars are visible */
37
+ @property({ type: String, reflect: true })
38
+ type: ScrollAreaType = "hover";
39
+
40
+ /** Scroll hide delay in ms (for hover type) */
41
+ @property({ type: Number, attribute: "scroll-hide-delay" })
42
+ scrollHideDelay = 600;
43
+
44
+ /** Orientation for which to show scrollbar */
45
+ @property({ type: String, reflect: true })
46
+ orientation: "vertical" | "horizontal" | "both" = "vertical";
47
+
48
+ override connectedCallback(): void {
49
+ super.connectedCallback();
50
+ }
51
+
52
+ override render() {
53
+ return html`<slot></slot>`;
54
+ }
55
+ }
56
+
57
+ define("ds-scroll-area", DsScrollArea);
58
+
59
+ declare global {
60
+ interface HTMLElementTagNameMap {
61
+ "ds-scroll-area": DsScrollArea;
62
+ }
63
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Select compound component exports.
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * import { Select } from "@/components/ui";
7
+ *
8
+ * <Select.Root onValueChange={(value) => console.log(value)}>
9
+ * <Select.Trigger>
10
+ * <Select.Value placeholder="Select a fruit" />
11
+ * </Select.Trigger>
12
+ * <Select.Content>
13
+ * <Select.Label>Fruits</Select.Label>
14
+ * <Select.Option value="apple">Apple</Select.Option>
15
+ * <Select.Option value="banana">Banana</Select.Option>
16
+ * <Select.Separator />
17
+ * <Select.Option value="cherry">Cherry</Select.Option>
18
+ * </Select.Content>
19
+ * </Select.Root>
20
+ * ```
21
+ */
22
+
23
+ import { SelectContent, type SelectContentProps } from "./select-content.js";
24
+ import { SelectGroup, type SelectGroupProps } from "./select-group.js";
25
+ import { SelectLabel, type SelectLabelProps } from "./select-label.js";
26
+ import { SelectOption, type SelectOptionProps } from "./select-option.js";
27
+ import { SelectRoot, type SelectRootProps } from "./select-root.js";
28
+ import { SelectSeparator, type SelectSeparatorProps } from "./select-separator.js";
29
+ import { SelectTrigger, type SelectTriggerProps } from "./select-trigger.js";
30
+ import { SelectValue, type SelectValueProps } from "./select-value.js";
31
+
32
+ // Compound component
33
+ export const Select = {
34
+ Root: SelectRoot,
35
+ Trigger: SelectTrigger,
36
+ Value: SelectValue,
37
+ Content: SelectContent,
38
+ Group: SelectGroup,
39
+ Option: SelectOption,
40
+ Separator: SelectSeparator,
41
+ Label: SelectLabel,
42
+ };
43
+
44
+ // Type exports
45
+ export type {
46
+ SelectContentProps,
47
+ SelectGroupProps,
48
+ SelectLabelProps,
49
+ SelectOptionProps,
50
+ SelectRootProps,
51
+ SelectSeparatorProps,
52
+ SelectTriggerProps,
53
+ SelectValueProps,
54
+ };
55
+
56
+ // Re-export Placement from primitives-dom
57
+ export type { Placement } from "@hypoth-ui/primitives-dom";
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Select Content component - container for select options.
3
+ */
4
+
5
+ import {
6
+ type AnchorPosition,
7
+ type DismissableLayer,
8
+ type RovingFocus,
9
+ type TypeAhead,
10
+ createAnchorPosition,
11
+ createDismissableLayer,
12
+ createRovingFocus,
13
+ createTypeAhead,
14
+ } from "@hypoth-ui/primitives-dom";
15
+ import {
16
+ type HTMLAttributes,
17
+ type ReactNode,
18
+ forwardRef,
19
+ useCallback,
20
+ useEffect,
21
+ useRef,
22
+ } from "react";
23
+ import { useSelectContext } from "./select-context.js";
24
+
25
+ export interface SelectContentProps extends HTMLAttributes<HTMLDivElement> {
26
+ /** Content container */
27
+ children?: ReactNode;
28
+ /** Optional label for accessibility */
29
+ "aria-label"?: string;
30
+ }
31
+
32
+ /**
33
+ * Container for select options.
34
+ * Handles positioning, dismiss behavior, and keyboard navigation.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * <Select.Content>
39
+ * <Select.Option value="apple">Apple</Select.Option>
40
+ * <Select.Option value="banana">Banana</Select.Option>
41
+ * </Select.Content>
42
+ * ```
43
+ */
44
+ export const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
45
+ ({ children, className, onKeyDown, ...restProps }, ref) => {
46
+ const { behavior, open, setOpen, highlightedValue, setHighlightedValue, value } =
47
+ useSelectContext("Select.Content");
48
+ const internalRef = useRef<HTMLDivElement>(null);
49
+
50
+ // Behavior instances
51
+ const anchorPositionRef = useRef<AnchorPosition | null>(null);
52
+ const dismissLayerRef = useRef<DismissableLayer | null>(null);
53
+ const rovingFocusRef = useRef<RovingFocus | null>(null);
54
+ const typeAheadRef = useRef<TypeAhead | null>(null);
55
+ const triggerRef = useRef<HTMLElement | null>(null);
56
+
57
+ // Merge refs
58
+ const mergedRef = useCallback(
59
+ (element: HTMLDivElement | null) => {
60
+ (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = element;
61
+ if (typeof ref === "function") {
62
+ ref(element);
63
+ } else if (ref) {
64
+ (ref as React.MutableRefObject<HTMLDivElement | null>).current = element;
65
+ }
66
+ },
67
+ [ref]
68
+ );
69
+
70
+ // Setup and cleanup behaviors when open changes
71
+ useEffect(() => {
72
+ const content = internalRef.current;
73
+ if (!content || !open) return;
74
+
75
+ // Find trigger element (previous sibling or parent context)
76
+ const trigger = content.previousElementSibling as HTMLElement;
77
+ triggerRef.current = trigger;
78
+
79
+ // Setup anchor positioning
80
+ if (trigger) {
81
+ anchorPositionRef.current = createAnchorPosition({
82
+ anchor: trigger,
83
+ floating: content,
84
+ placement: "bottom-start",
85
+ offset: 4,
86
+ flip: true,
87
+ onPositionChange: (pos) => {
88
+ content.setAttribute("data-placement", pos.placement);
89
+ },
90
+ });
91
+ }
92
+
93
+ // Setup dismiss layer
94
+ dismissLayerRef.current = createDismissableLayer({
95
+ container: content,
96
+ excludeElements: trigger ? [trigger] : [],
97
+ onDismiss: () => setOpen(false),
98
+ closeOnEscape: true,
99
+ closeOnOutsideClick: true,
100
+ });
101
+ dismissLayerRef.current.activate();
102
+
103
+ // Setup roving focus
104
+ rovingFocusRef.current = createRovingFocus({
105
+ container: content,
106
+ selector: '[role="option"]:not([aria-disabled="true"])',
107
+ direction: "vertical",
108
+ loop: true,
109
+ skipDisabled: true,
110
+ });
111
+
112
+ // Setup type-ahead
113
+ typeAheadRef.current = createTypeAhead({
114
+ items: () =>
115
+ Array.from(
116
+ content.querySelectorAll<HTMLElement>('[role="option"]:not([aria-disabled="true"])')
117
+ ),
118
+ getText: (item) => item.textContent?.trim() || "",
119
+ onMatch: (item) => {
120
+ const optionValue = item.getAttribute("data-value");
121
+ if (optionValue) {
122
+ behavior.highlight(optionValue);
123
+ setHighlightedValue(optionValue);
124
+ item.scrollIntoView({ block: "nearest" });
125
+ }
126
+ },
127
+ });
128
+
129
+ // Focus on current or first option
130
+ requestAnimationFrame(() => {
131
+ const options = content.querySelectorAll<HTMLElement>(
132
+ '[role="option"]:not([aria-disabled="true"])'
133
+ );
134
+ if (options.length === 0) return;
135
+
136
+ // Find current value or highlight first
137
+ let initialIndex = 0;
138
+ if (value) {
139
+ const selectedIndex = Array.from(options).findIndex(
140
+ (opt) => opt.getAttribute("data-value") === value
141
+ );
142
+ if (selectedIndex >= 0) {
143
+ initialIndex = selectedIndex;
144
+ }
145
+ }
146
+
147
+ const initialOption = options[initialIndex];
148
+ if (initialOption) {
149
+ const optionValue = initialOption.getAttribute("data-value");
150
+ if (optionValue) {
151
+ behavior.highlight(optionValue);
152
+ setHighlightedValue(optionValue);
153
+ }
154
+ rovingFocusRef.current?.setFocusedIndex(initialIndex);
155
+ initialOption.scrollIntoView({ block: "nearest" });
156
+ }
157
+ });
158
+
159
+ // Cleanup
160
+ return () => {
161
+ anchorPositionRef.current?.destroy();
162
+ anchorPositionRef.current = null;
163
+ dismissLayerRef.current?.deactivate();
164
+ dismissLayerRef.current = null;
165
+ rovingFocusRef.current?.destroy();
166
+ rovingFocusRef.current = null;
167
+ typeAheadRef.current?.reset();
168
+ typeAheadRef.current = null;
169
+ };
170
+ }, [open, behavior, setOpen, setHighlightedValue, value]);
171
+
172
+ // Handle keyboard navigation
173
+ const handleKeyDown = useCallback(
174
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
175
+ // Let type-ahead handle character keys
176
+ typeAheadRef.current?.handleKeyDown(event.nativeEvent);
177
+
178
+ switch (event.key) {
179
+ case "Enter":
180
+ case " ":
181
+ event.preventDefault();
182
+ if (highlightedValue) {
183
+ behavior.select(highlightedValue);
184
+ }
185
+ break;
186
+ case "Escape":
187
+ event.preventDefault();
188
+ setOpen(false);
189
+ triggerRef.current?.focus();
190
+ break;
191
+ case "ArrowDown":
192
+ event.preventDefault();
193
+ behavior.highlightNext();
194
+ setHighlightedValue(behavior.state.highlightedValue);
195
+ break;
196
+ case "ArrowUp":
197
+ event.preventDefault();
198
+ behavior.highlightPrev();
199
+ setHighlightedValue(behavior.state.highlightedValue);
200
+ break;
201
+ case "Home":
202
+ event.preventDefault();
203
+ behavior.highlightFirst();
204
+ setHighlightedValue(behavior.state.highlightedValue);
205
+ break;
206
+ case "End":
207
+ event.preventDefault();
208
+ behavior.highlightLast();
209
+ setHighlightedValue(behavior.state.highlightedValue);
210
+ break;
211
+ case "Tab":
212
+ // Close on tab without preventing default
213
+ setOpen(false);
214
+ break;
215
+ }
216
+
217
+ onKeyDown?.(event);
218
+ },
219
+ [behavior, highlightedValue, setOpen, setHighlightedValue, onKeyDown]
220
+ );
221
+
222
+ // Don't render if not open
223
+ if (!open) return null;
224
+
225
+ const contentProps = behavior.getContentProps();
226
+
227
+ return (
228
+ <div
229
+ ref={mergedRef}
230
+ role={contentProps.role}
231
+ id={contentProps.id}
232
+ className={className}
233
+ onKeyDown={handleKeyDown}
234
+ data-state="open"
235
+ {...restProps}
236
+ >
237
+ {children}
238
+ </div>
239
+ );
240
+ }
241
+ );
242
+
243
+ SelectContent.displayName = "Select.Content";