sheer-ui 0.0.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 (635) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/package.json +369 -0
  4. package/src/lib/VENDORED.md +109 -0
  5. package/src/lib/assets/ui.css +733 -0
  6. package/src/lib/blocks/Appearance.svelte +104 -0
  7. package/src/lib/blocks/BoundaryFailed.svelte +33 -0
  8. package/src/lib/blocks/ErrorPage.svelte +65 -0
  9. package/src/lib/blocks/Footer.svelte +71 -0
  10. package/src/lib/blocks/Header.svelte +67 -0
  11. package/src/lib/blocks/Heading.svelte +86 -0
  12. package/src/lib/blocks/MobileNav.svelte +169 -0
  13. package/src/lib/blocks/PhoneInput.svelte +68 -0
  14. package/src/lib/blocks/Section.svelte +34 -0
  15. package/src/lib/blocks/alert-modal/AlertModal.svelte +83 -0
  16. package/src/lib/blocks/alert-modal/alert-modal.svelte.ts +81 -0
  17. package/src/lib/blocks/alert-modal/index.ts +2 -0
  18. package/src/lib/blocks/calendar.svelte +155 -0
  19. package/src/lib/blocks/copy-button/copy-button.svelte +43 -0
  20. package/src/lib/blocks/copy-button/copy.svelte.ts +52 -0
  21. package/src/lib/blocks/copy-button/index.ts +2 -0
  22. package/src/lib/blocks/date-picker/date-picker-with-presets.svelte +109 -0
  23. package/src/lib/blocks/date-picker/date-picker.svelte +91 -0
  24. package/src/lib/blocks/date-picker/index.ts +13 -0
  25. package/src/lib/blocks/date-picker/range-date-picker.svelte +128 -0
  26. package/src/lib/blocks/index.ts +12 -0
  27. package/src/lib/blocks/mockups/Browser.svelte +22 -0
  28. package/src/lib/blocks/mockups/Phone.svelte +44 -0
  29. package/src/lib/blocks/mockups/index.ts +2 -0
  30. package/src/lib/blocks/tab-nav/index.ts +1 -0
  31. package/src/lib/blocks/tab-nav/tab-nav.svelte +46 -0
  32. package/src/lib/blocks/time-picker/index.ts +16 -0
  33. package/src/lib/blocks/time-picker/time-input.svelte +118 -0
  34. package/src/lib/blocks/time-picker/time-picker.svelte +93 -0
  35. package/src/lib/blocks/time-picker/time-range-input.svelte +34 -0
  36. package/src/lib/blocks/time-picker/time-range-picker.svelte +180 -0
  37. package/src/lib/blocks/time-picker/time-slots.ts +61 -0
  38. package/src/lib/blocks/user-avatar/UserAvatar.svelte +36 -0
  39. package/src/lib/blocks/user-avatar/index.ts +1 -0
  40. package/src/lib/blocks/verification-code-field/VerificationCodeField.svelte +57 -0
  41. package/src/lib/blocks/verification-code-field/index.ts +1 -0
  42. package/src/lib/components/accordion/accordion.svelte.ts +43 -0
  43. package/src/lib/components/accordion/components/accordion-content.svelte +17 -0
  44. package/src/lib/components/accordion/components/accordion-item.svelte +58 -0
  45. package/src/lib/components/accordion/components/accordion-trigger.svelte +38 -0
  46. package/src/lib/components/accordion/components/accordion.svelte +24 -0
  47. package/src/lib/components/accordion/index.ts +11 -0
  48. package/src/lib/components/accordion/types.ts +19 -0
  49. package/src/lib/components/alert/alert-action.svelte +11 -0
  50. package/src/lib/components/alert/alert-description.svelte +18 -0
  51. package/src/lib/components/alert/alert-title.svelte +18 -0
  52. package/src/lib/components/alert/alert.svelte +37 -0
  53. package/src/lib/components/alert/index.ts +17 -0
  54. package/src/lib/components/avatar/avatar.svelte.ts +154 -0
  55. package/src/lib/components/avatar/components/avatar-fallback.svelte +38 -0
  56. package/src/lib/components/avatar/components/avatar-image.svelte +47 -0
  57. package/src/lib/components/avatar/components/avatar.svelte +57 -0
  58. package/src/lib/components/avatar/index.ts +9 -0
  59. package/src/lib/components/avatar/types.ts +46 -0
  60. package/src/lib/components/badge/badge.svelte +49 -0
  61. package/src/lib/components/badge/index.ts +1 -0
  62. package/src/lib/components/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  63. package/src/lib/components/breadcrumb/breadcrumb-item.svelte +11 -0
  64. package/src/lib/components/breadcrumb/breadcrumb-link.svelte +32 -0
  65. package/src/lib/components/breadcrumb/breadcrumb-list.svelte +15 -0
  66. package/src/lib/components/breadcrumb/breadcrumb-page.svelte +18 -0
  67. package/src/lib/components/breadcrumb/breadcrumb-separator.svelte +22 -0
  68. package/src/lib/components/breadcrumb/breadcrumb.svelte +10 -0
  69. package/src/lib/components/breadcrumb/index.ts +25 -0
  70. package/src/lib/components/button/button.svelte +55 -0
  71. package/src/lib/components/button/index.ts +12 -0
  72. package/src/lib/components/button/variants.ts +51 -0
  73. package/src/lib/components/calendar/calendar.svelte.ts +1081 -0
  74. package/src/lib/components/calendar/components/calendar-cell.svelte +34 -0
  75. package/src/lib/components/calendar/components/calendar-day.svelte +36 -0
  76. package/src/lib/components/calendar/components/calendar-grid-body.svelte +29 -0
  77. package/src/lib/components/calendar/components/calendar-grid-head.svelte +29 -0
  78. package/src/lib/components/calendar/components/calendar-grid-row.svelte +29 -0
  79. package/src/lib/components/calendar/components/calendar-grid.svelte +29 -0
  80. package/src/lib/components/calendar/components/calendar-head-cell.svelte +29 -0
  81. package/src/lib/components/calendar/components/calendar-header.svelte +29 -0
  82. package/src/lib/components/calendar/components/calendar-heading.svelte +33 -0
  83. package/src/lib/components/calendar/components/calendar-month-select.svelte +50 -0
  84. package/src/lib/components/calendar/components/calendar-next-button.svelte +37 -0
  85. package/src/lib/components/calendar/components/calendar-prev-button.svelte +37 -0
  86. package/src/lib/components/calendar/components/calendar-year-select.svelte +50 -0
  87. package/src/lib/components/calendar/components/calendar.svelte +123 -0
  88. package/src/lib/components/calendar/exports.ts +31 -0
  89. package/src/lib/components/calendar/index.ts +2 -0
  90. package/src/lib/components/calendar/types.ts +417 -0
  91. package/src/lib/components/card/card-action.svelte +15 -0
  92. package/src/lib/components/card/card-content.svelte +11 -0
  93. package/src/lib/components/card/card-description.svelte +11 -0
  94. package/src/lib/components/card/card-footer.svelte +11 -0
  95. package/src/lib/components/card/card-header.svelte +18 -0
  96. package/src/lib/components/card/card-title.svelte +11 -0
  97. package/src/lib/components/card/card.svelte +15 -0
  98. package/src/lib/components/card/index.ts +25 -0
  99. package/src/lib/components/carousel/carousel-arrow.svelte +42 -0
  100. package/src/lib/components/carousel/carousel-content.svelte +25 -0
  101. package/src/lib/components/carousel/carousel-dots.svelte +21 -0
  102. package/src/lib/components/carousel/carousel-item.svelte +21 -0
  103. package/src/lib/components/carousel/carousel-next.svelte +9 -0
  104. package/src/lib/components/carousel/carousel-previous.svelte +9 -0
  105. package/src/lib/components/carousel/carousel.svelte +245 -0
  106. package/src/lib/components/carousel/carouselState.svelte.ts +30 -0
  107. package/src/lib/components/carousel/index.ts +24 -0
  108. package/src/lib/components/chart/chart-container.svelte +52 -0
  109. package/src/lib/components/chart/chart-style.svelte +29 -0
  110. package/src/lib/components/chart/chart-tooltip.svelte +163 -0
  111. package/src/lib/components/chart/chart-utils.ts +50 -0
  112. package/src/lib/components/chart/index.ts +6 -0
  113. package/src/lib/components/checkbox/checkbox.svelte.ts +199 -0
  114. package/src/lib/components/checkbox/components/checkbox-group-label.svelte +29 -0
  115. package/src/lib/components/checkbox/components/checkbox-group.svelte +48 -0
  116. package/src/lib/components/checkbox/components/checkbox.svelte +85 -0
  117. package/src/lib/components/checkbox/index.ts +9 -0
  118. package/src/lib/components/checkbox/types.ts +131 -0
  119. package/src/lib/components/checkbox-native/checkbox.svelte +96 -0
  120. package/src/lib/components/checkbox-native/index.ts +8 -0
  121. package/src/lib/components/collapsible/components/collapsible-content.svelte +16 -0
  122. package/src/lib/components/collapsible/components/collapsible-trigger.svelte +14 -0
  123. package/src/lib/components/collapsible/components/collapsible.svelte +68 -0
  124. package/src/lib/components/collapsible/index.ts +9 -0
  125. package/src/lib/components/collapsible/types.ts +25 -0
  126. package/src/lib/components/combobox/components/combobox-arrow.svelte +8 -0
  127. package/src/lib/components/combobox/components/combobox-input.svelte +46 -0
  128. package/src/lib/components/combobox/components/combobox-trigger.svelte +44 -0
  129. package/src/lib/components/combobox/components/combobox.svelte +83 -0
  130. package/src/lib/components/combobox/index.ts +30 -0
  131. package/src/lib/components/combobox/select/components/select-content-static.svelte +56 -0
  132. package/src/lib/components/combobox/select/components/select-content.svelte +95 -0
  133. package/src/lib/components/combobox/select/components/select-group-heading.svelte +51 -0
  134. package/src/lib/components/combobox/select/components/select-group.svelte +51 -0
  135. package/src/lib/components/combobox/select/components/select-hidden-input.svelte +16 -0
  136. package/src/lib/components/combobox/select/components/select-item.svelte +86 -0
  137. package/src/lib/components/combobox/select/components/select-scroll-down-button.svelte +41 -0
  138. package/src/lib/components/combobox/select/components/select-scroll-up-button.svelte +34 -0
  139. package/src/lib/components/combobox/select/components/select-trigger.svelte +32 -0
  140. package/src/lib/components/combobox/select/components/select-value.svelte +40 -0
  141. package/src/lib/components/combobox/select/components/select-viewport.svelte +29 -0
  142. package/src/lib/components/combobox/select/components/select.svelte +92 -0
  143. package/src/lib/components/combobox/select/select.svelte.ts +1596 -0
  144. package/src/lib/components/combobox/select/types.ts +291 -0
  145. package/src/lib/components/combobox/types.ts +77 -0
  146. package/src/lib/components/command/command-dialog.svelte +42 -0
  147. package/src/lib/components/command/command.svelte.ts +1506 -0
  148. package/src/lib/components/command/components/_command-label.svelte +31 -0
  149. package/src/lib/components/command/components/command-empty.svelte +34 -0
  150. package/src/lib/components/command/components/command-group-heading.svelte +29 -0
  151. package/src/lib/components/command/components/command-group-items.svelte +31 -0
  152. package/src/lib/components/command/components/command-group.svelte +76 -0
  153. package/src/lib/components/command/components/command-input.svelte +55 -0
  154. package/src/lib/components/command/components/command-item.svelte +61 -0
  155. package/src/lib/components/command/components/command-link-item.svelte +61 -0
  156. package/src/lib/components/command/components/command-list.svelte +41 -0
  157. package/src/lib/components/command/components/command-loading.svelte +30 -0
  158. package/src/lib/components/command/components/command-separator.svelte +34 -0
  159. package/src/lib/components/command/components/command-viewport.svelte +29 -0
  160. package/src/lib/components/command/components/command.svelte +143 -0
  161. package/src/lib/components/command/compute-command-score.ts +207 -0
  162. package/src/lib/components/command/index.ts +29 -0
  163. package/src/lib/components/command/types.ts +222 -0
  164. package/src/lib/components/command/utils.ts +30 -0
  165. package/src/lib/components/context-menu/components/context-menu-content-static.svelte +22 -0
  166. package/src/lib/components/context-menu/components/context-menu-content.svelte +22 -0
  167. package/src/lib/components/context-menu/components/context-menu-trigger.svelte +46 -0
  168. package/src/lib/components/context-menu/components/context-menu.svelte +8 -0
  169. package/src/lib/components/context-menu/index.ts +39 -0
  170. package/src/lib/components/context-menu/types.ts +56 -0
  171. package/src/lib/components/data-table/column-helpers.ts +97 -0
  172. package/src/lib/components/data-table/data-table-checkbox.svelte +25 -0
  173. package/src/lib/components/data-table/data-table-sort-button.svelte +16 -0
  174. package/src/lib/components/data-table/flex-render.svelte +42 -0
  175. package/src/lib/components/data-table/index.ts +29 -0
  176. package/src/lib/components/data-table/render-helpers.ts +105 -0
  177. package/src/lib/components/data-table/table-cell.svelte +9 -0
  178. package/src/lib/components/data-table/table-column-header.svelte +51 -0
  179. package/src/lib/components/data-table/table-faceted-filter.svelte +109 -0
  180. package/src/lib/components/data-table/table-pagination.svelte +54 -0
  181. package/src/lib/components/data-table/table-priority-cell.svelte +22 -0
  182. package/src/lib/components/data-table/table-row-actions.svelte +29 -0
  183. package/src/lib/components/data-table/table-status-cell.svelte +34 -0
  184. package/src/lib/components/data-table/table-title-cell.svelte +24 -0
  185. package/src/lib/components/data-table/table-toolbar.svelte +55 -0
  186. package/src/lib/components/data-table/table-view-options.svelte +25 -0
  187. package/src/lib/components/date-field/components/date-field-hidden-input.svelte +9 -0
  188. package/src/lib/components/date-field/components/date-field-input.svelte +33 -0
  189. package/src/lib/components/date-field/components/date-field-label.svelte +29 -0
  190. package/src/lib/components/date-field/components/date-field-segment.svelte +29 -0
  191. package/src/lib/components/date-field/components/date-field.svelte +106 -0
  192. package/src/lib/components/date-field/date-field.svelte.ts +1474 -0
  193. package/src/lib/components/date-field/index.ts +11 -0
  194. package/src/lib/components/date-field/types.ts +165 -0
  195. package/src/lib/components/date-range-field/components/date-range-field-input.svelte +36 -0
  196. package/src/lib/components/date-range-field/components/date-range-field-label.svelte +29 -0
  197. package/src/lib/components/date-range-field/components/date-range-field.svelte +106 -0
  198. package/src/lib/components/date-range-field/date-range-field.svelte.ts +212 -0
  199. package/src/lib/components/date-range-field/index.ts +11 -0
  200. package/src/lib/components/date-range-field/types.ts +171 -0
  201. package/src/lib/components/dialog/components/dialog-close.svelte +31 -0
  202. package/src/lib/components/dialog/components/dialog-content-headless.svelte +104 -0
  203. package/src/lib/components/dialog/components/dialog-content.svelte +69 -0
  204. package/src/lib/components/dialog/components/dialog-description.svelte +29 -0
  205. package/src/lib/components/dialog/components/dialog-overlay.svelte +31 -0
  206. package/src/lib/components/dialog/components/dialog-title.svelte +30 -0
  207. package/src/lib/components/dialog/components/dialog-trigger.svelte +30 -0
  208. package/src/lib/components/dialog/components/dialog.svelte +21 -0
  209. package/src/lib/components/dialog/components/modal-surface.svelte +131 -0
  210. package/src/lib/components/dialog/dialog.svelte.ts +371 -0
  211. package/src/lib/components/dialog/index.ts +19 -0
  212. package/src/lib/components/dialog/types.ts +105 -0
  213. package/src/lib/components/drawer/drawer-close.svelte +7 -0
  214. package/src/lib/components/drawer/drawer-content.svelte +91 -0
  215. package/src/lib/components/drawer/drawer-description.svelte +8 -0
  216. package/src/lib/components/drawer/drawer-footer.svelte +11 -0
  217. package/src/lib/components/drawer/drawer-header.svelte +11 -0
  218. package/src/lib/components/drawer/drawer-nested.svelte +44 -0
  219. package/src/lib/components/drawer/drawer-overlay.svelte +60 -0
  220. package/src/lib/components/drawer/drawer-portal.svelte +10 -0
  221. package/src/lib/components/drawer/drawer-title.svelte +8 -0
  222. package/src/lib/components/drawer/drawer-trigger.svelte +7 -0
  223. package/src/lib/components/drawer/drawer.svelte +290 -0
  224. package/src/lib/components/drawer/index.ts +37 -0
  225. package/src/lib/components/dropdown-menu/components/dropdown-menu-content-static.svelte +22 -0
  226. package/src/lib/components/dropdown-menu/components/dropdown-menu-content.svelte +22 -0
  227. package/src/lib/components/dropdown-menu/index.ts +39 -0
  228. package/src/lib/components/dropdown-menu/types.ts +41 -0
  229. package/src/lib/components/empty/empty-content.svelte +15 -0
  230. package/src/lib/components/empty/empty-description.svelte +15 -0
  231. package/src/lib/components/empty/empty-header.svelte +15 -0
  232. package/src/lib/components/empty/empty-media.svelte +21 -0
  233. package/src/lib/components/empty/empty-title.svelte +11 -0
  234. package/src/lib/components/empty/empty.svelte +18 -0
  235. package/src/lib/components/empty/index.ts +22 -0
  236. package/src/lib/components/empty/variants.ts +16 -0
  237. package/src/lib/components/field/field-content.svelte +14 -0
  238. package/src/lib/components/field/field-description.svelte +19 -0
  239. package/src/lib/components/field/field-error.svelte +47 -0
  240. package/src/lib/components/field/field-group.svelte +17 -0
  241. package/src/lib/components/field/field-label.svelte +18 -0
  242. package/src/lib/components/field/field-legend.svelte +23 -0
  243. package/src/lib/components/field/field-separator.svelte +32 -0
  244. package/src/lib/components/field/field-set.svelte +14 -0
  245. package/src/lib/components/field/field-title.svelte +14 -0
  246. package/src/lib/components/field/field.svelte +36 -0
  247. package/src/lib/components/field/index.ts +32 -0
  248. package/src/lib/components/field/variants.ts +19 -0
  249. package/src/lib/components/input/file-input.svelte +26 -0
  250. package/src/lib/components/input/index.ts +12 -0
  251. package/src/lib/components/input/input.svelte +24 -0
  252. package/src/lib/components/input/variants.ts +28 -0
  253. package/src/lib/components/item/index.ts +34 -0
  254. package/src/lib/components/item/item-actions.svelte +11 -0
  255. package/src/lib/components/item/item-content.svelte +15 -0
  256. package/src/lib/components/item/item-description.svelte +18 -0
  257. package/src/lib/components/item/item-footer.svelte +11 -0
  258. package/src/lib/components/item/item-group.svelte +16 -0
  259. package/src/lib/components/item/item-header.svelte +11 -0
  260. package/src/lib/components/item/item-media.svelte +37 -0
  261. package/src/lib/components/item/item-separator.svelte +9 -0
  262. package/src/lib/components/item/item-title.svelte +15 -0
  263. package/src/lib/components/item/item.svelte +61 -0
  264. package/src/lib/components/kbd/index.ts +10 -0
  265. package/src/lib/components/kbd/kbd-group.svelte +11 -0
  266. package/src/lib/components/kbd/kbd.svelte +18 -0
  267. package/src/lib/components/label/exports.ts +2 -0
  268. package/src/lib/components/label/index.ts +2 -0
  269. package/src/lib/components/label/label.svelte +39 -0
  270. package/src/lib/components/label/label.svelte.ts +39 -0
  271. package/src/lib/components/label/types.ts +7 -0
  272. package/src/lib/components/link-preview/components/link-preview-content.svelte +60 -0
  273. package/src/lib/components/link-preview/components/link-preview-trigger.svelte +29 -0
  274. package/src/lib/components/link-preview/components/link-preview.svelte +34 -0
  275. package/src/lib/components/link-preview/exports.ts +11 -0
  276. package/src/lib/components/link-preview/index.ts +2 -0
  277. package/src/lib/components/link-preview/link-preview.svelte.ts +270 -0
  278. package/src/lib/components/link-preview/types.ts +102 -0
  279. package/src/lib/components/menu/components/menu-arrow.svelte +14 -0
  280. package/src/lib/components/menu/components/menu-checkbox-group.svelte +42 -0
  281. package/src/lib/components/menu/components/menu-checkbox-item.svelte +85 -0
  282. package/src/lib/components/menu/components/menu-content.svelte +98 -0
  283. package/src/lib/components/menu/components/menu-group-heading.svelte +47 -0
  284. package/src/lib/components/menu/components/menu-group.svelte +36 -0
  285. package/src/lib/components/menu/components/menu-item.svelte +58 -0
  286. package/src/lib/components/menu/components/menu-radio-group.svelte +44 -0
  287. package/src/lib/components/menu/components/menu-radio-item.svelte +65 -0
  288. package/src/lib/components/menu/components/menu-separator.svelte +38 -0
  289. package/src/lib/components/menu/components/menu-sub-content-static.svelte +8 -0
  290. package/src/lib/components/menu/components/menu-sub-content.svelte +133 -0
  291. package/src/lib/components/menu/components/menu-sub-trigger.svelte +60 -0
  292. package/src/lib/components/menu/components/menu-sub.svelte +23 -0
  293. package/src/lib/components/menu/components/menu-trigger.svelte +50 -0
  294. package/src/lib/components/menu/components/menu.svelte +58 -0
  295. package/src/lib/components/menu/exports.ts +38 -0
  296. package/src/lib/components/menu/menu.svelte.ts +1599 -0
  297. package/src/lib/components/menu/types.ts +285 -0
  298. package/src/lib/components/menu/utils.ts +68 -0
  299. package/src/lib/components/menubar/components/menubar-content-static.svelte +50 -0
  300. package/src/lib/components/menubar/components/menubar-content.svelte +54 -0
  301. package/src/lib/components/menubar/components/menubar-menu.svelte +34 -0
  302. package/src/lib/components/menubar/components/menubar-trigger.svelte +50 -0
  303. package/src/lib/components/menubar/components/menubar.svelte +57 -0
  304. package/src/lib/components/menubar/exports.ts +44 -0
  305. package/src/lib/components/menubar/index.ts +2 -0
  306. package/src/lib/components/menubar/menubar.svelte.ts +394 -0
  307. package/src/lib/components/menubar/types.ts +97 -0
  308. package/src/lib/components/meter/components/meter.svelte +41 -0
  309. package/src/lib/components/meter/exports.ts +2 -0
  310. package/src/lib/components/meter/index.ts +2 -0
  311. package/src/lib/components/meter/meter.svelte.ts +47 -0
  312. package/src/lib/components/meter/types.ts +28 -0
  313. package/src/lib/components/navigation-menu/components/navigation-menu-content-impl.svelte +85 -0
  314. package/src/lib/components/navigation-menu/components/navigation-menu-content.svelte +58 -0
  315. package/src/lib/components/navigation-menu/components/navigation-menu-indicator-impl.svelte +29 -0
  316. package/src/lib/components/navigation-menu/components/navigation-menu-indicator.svelte +56 -0
  317. package/src/lib/components/navigation-menu/components/navigation-menu-item.svelte +56 -0
  318. package/src/lib/components/navigation-menu/components/navigation-menu-link.svelte +64 -0
  319. package/src/lib/components/navigation-menu/components/navigation-menu-list.svelte +59 -0
  320. package/src/lib/components/navigation-menu/components/navigation-menu-sub.svelte +46 -0
  321. package/src/lib/components/navigation-menu/components/navigation-menu-trigger.svelte +80 -0
  322. package/src/lib/components/navigation-menu/components/navigation-menu-viewport.svelte +63 -0
  323. package/src/lib/components/navigation-menu/components/navigation-menu.svelte +80 -0
  324. package/src/lib/components/navigation-menu/index.ts +28 -0
  325. package/src/lib/components/navigation-menu/navigation-menu.svelte.ts +1092 -0
  326. package/src/lib/components/navigation-menu/types.ts +189 -0
  327. package/src/lib/components/pagination/components/pagination-next-button.svelte +60 -0
  328. package/src/lib/components/pagination/components/pagination-page.svelte +65 -0
  329. package/src/lib/components/pagination/components/pagination-prev-button.svelte +60 -0
  330. package/src/lib/components/pagination/components/pagination.svelte +65 -0
  331. package/src/lib/components/pagination/index.ts +11 -0
  332. package/src/lib/components/pagination/pagination.svelte.ts +275 -0
  333. package/src/lib/components/pagination/types.ts +100 -0
  334. package/src/lib/components/pin-input/components/pin-input-cell.svelte +49 -0
  335. package/src/lib/components/pin-input/components/pin-input.svelte +76 -0
  336. package/src/lib/components/pin-input/exports.ts +4 -0
  337. package/src/lib/components/pin-input/index.ts +2 -0
  338. package/src/lib/components/pin-input/pin-input.svelte.ts +541 -0
  339. package/src/lib/components/pin-input/types.ts +118 -0
  340. package/src/lib/components/pin-input/usePasswordManager.svelte.ts +122 -0
  341. package/src/lib/components/popover/components/popover-close.svelte +38 -0
  342. package/src/lib/components/popover/components/popover-content.svelte +70 -0
  343. package/src/lib/components/popover/components/popover-header.svelte +11 -0
  344. package/src/lib/components/popover/components/popover-title.svelte +11 -0
  345. package/src/lib/components/popover/components/popover-trigger.svelte +44 -0
  346. package/src/lib/components/popover/components/popover.svelte +26 -0
  347. package/src/lib/components/popover/exports.ts +13 -0
  348. package/src/lib/components/popover/index.ts +2 -0
  349. package/src/lib/components/popover/popover.svelte.ts +392 -0
  350. package/src/lib/components/popover/types.ts +92 -0
  351. package/src/lib/components/progress/components/progress.svelte +56 -0
  352. package/src/lib/components/progress/exports.ts +3 -0
  353. package/src/lib/components/progress/index.ts +2 -0
  354. package/src/lib/components/progress/progress.svelte.ts +58 -0
  355. package/src/lib/components/progress/types.ts +28 -0
  356. package/src/lib/components/radio-group/context.ts +13 -0
  357. package/src/lib/components/radio-group/index.ts +10 -0
  358. package/src/lib/components/radio-group/radio-group-item.svelte +67 -0
  359. package/src/lib/components/radio-group/radio-group.svelte +87 -0
  360. package/src/lib/components/range-calendar/components/range-calendar-cell.svelte +31 -0
  361. package/src/lib/components/range-calendar/components/range-calendar-day.svelte +33 -0
  362. package/src/lib/components/range-calendar/components/range-calendar.svelte +120 -0
  363. package/src/lib/components/range-calendar/exports.ts +31 -0
  364. package/src/lib/components/range-calendar/index.ts +2 -0
  365. package/src/lib/components/range-calendar/range-calendar.svelte.ts +739 -0
  366. package/src/lib/components/range-calendar/types.ts +258 -0
  367. package/src/lib/components/resizable/index.ts +15 -0
  368. package/src/lib/components/resizable/resizable-handle.svelte +30 -0
  369. package/src/lib/components/resizable/resizable-pane-group.svelte +19 -0
  370. package/src/lib/components/scroll-area/components/scroll-area-corner-impl.svelte +34 -0
  371. package/src/lib/components/scroll-area/components/scroll-area-corner.svelte +18 -0
  372. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-auto.svelte +15 -0
  373. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-hover.svelte +22 -0
  374. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-scroll.svelte +16 -0
  375. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-shared.svelte +19 -0
  376. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-visible.svelte +16 -0
  377. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-x.svelte +23 -0
  378. package/src/lib/components/scroll-area/components/scroll-area-scrollbar-y.svelte +24 -0
  379. package/src/lib/components/scroll-area/components/scroll-area-scrollbar.svelte +56 -0
  380. package/src/lib/components/scroll-area/components/scroll-area-thumb-impl.svelte +49 -0
  381. package/src/lib/components/scroll-area/components/scroll-area-thumb.svelte +21 -0
  382. package/src/lib/components/scroll-area/components/scroll-area-viewport.svelte +39 -0
  383. package/src/lib/components/scroll-area/components/scroll-area.svelte +82 -0
  384. package/src/lib/components/scroll-area/exports.ts +13 -0
  385. package/src/lib/components/scroll-area/index.ts +2 -0
  386. package/src/lib/components/scroll-area/scroll-area.svelte.ts +946 -0
  387. package/src/lib/components/scroll-area/types.ts +67 -0
  388. package/src/lib/components/select/index.ts +5 -0
  389. package/src/lib/components/select/select-opt-group.svelte +10 -0
  390. package/src/lib/components/select/select-option.svelte +15 -0
  391. package/src/lib/components/select/select.svelte +281 -0
  392. package/src/lib/components/separator/components/separator.svelte +49 -0
  393. package/src/lib/components/separator/index.ts +3 -0
  394. package/src/lib/components/separator/separator.svelte.ts +43 -0
  395. package/src/lib/components/separator/types.ts +22 -0
  396. package/src/lib/components/sheet/components/sheet-close.svelte +8 -0
  397. package/src/lib/components/sheet/components/sheet-content.svelte +108 -0
  398. package/src/lib/components/sheet/components/sheet-description.svelte +9 -0
  399. package/src/lib/components/sheet/components/sheet-footer.svelte +11 -0
  400. package/src/lib/components/sheet/components/sheet-header.svelte +11 -0
  401. package/src/lib/components/sheet/components/sheet-overlay.svelte +13 -0
  402. package/src/lib/components/sheet/components/sheet-title.svelte +9 -0
  403. package/src/lib/components/sheet/components/sheet-trigger.svelte +8 -0
  404. package/src/lib/components/sheet/index.ts +47 -0
  405. package/src/lib/components/sheet/variants.ts +20 -0
  406. package/src/lib/components/sidebar/constants.ts +6 -0
  407. package/src/lib/components/sidebar/context.svelte.ts +54 -0
  408. package/src/lib/components/sidebar/index.ts +77 -0
  409. package/src/lib/components/sidebar/sidebar-content.svelte +16 -0
  410. package/src/lib/components/sidebar/sidebar-desktop-surface.svelte +60 -0
  411. package/src/lib/components/sidebar/sidebar-footer.svelte +11 -0
  412. package/src/lib/components/sidebar/sidebar-group-action.svelte +34 -0
  413. package/src/lib/components/sidebar/sidebar-group-content.svelte +16 -0
  414. package/src/lib/components/sidebar/sidebar-group-label.svelte +34 -0
  415. package/src/lib/components/sidebar/sidebar-group.svelte +16 -0
  416. package/src/lib/components/sidebar/sidebar-header.svelte +11 -0
  417. package/src/lib/components/sidebar/sidebar-input.svelte +14 -0
  418. package/src/lib/components/sidebar/sidebar-inset.svelte +18 -0
  419. package/src/lib/components/sidebar/sidebar-menu-action.svelte +38 -0
  420. package/src/lib/components/sidebar/sidebar-menu-badge.svelte +19 -0
  421. package/src/lib/components/sidebar/sidebar-menu-button.svelte +99 -0
  422. package/src/lib/components/sidebar/sidebar-menu-item.svelte +21 -0
  423. package/src/lib/components/sidebar/sidebar-menu-skeleton.svelte +34 -0
  424. package/src/lib/components/sidebar/sidebar-menu-sub-button.svelte +40 -0
  425. package/src/lib/components/sidebar/sidebar-menu-sub-item.svelte +16 -0
  426. package/src/lib/components/sidebar/sidebar-menu-sub.svelte +19 -0
  427. package/src/lib/components/sidebar/sidebar-menu.svelte +21 -0
  428. package/src/lib/components/sidebar/sidebar-mobile-surface.svelte +48 -0
  429. package/src/lib/components/sidebar/sidebar-provider.svelte +60 -0
  430. package/src/lib/components/sidebar/sidebar-rail.svelte +36 -0
  431. package/src/lib/components/sidebar/sidebar-separator.svelte +9 -0
  432. package/src/lib/components/sidebar/sidebar-trigger.svelte +35 -0
  433. package/src/lib/components/sidebar/sidebar.svelte +30 -0
  434. package/src/lib/components/sidebar/types.ts +14 -0
  435. package/src/lib/components/skeleton/index.ts +7 -0
  436. package/src/lib/components/skeleton/skeleton.svelte +9 -0
  437. package/src/lib/components/slider/components/slider-range.svelte +37 -0
  438. package/src/lib/components/slider/components/slider-thumb-label.svelte +42 -0
  439. package/src/lib/components/slider/components/slider-thumb.svelte +46 -0
  440. package/src/lib/components/slider/components/slider-tick-label.svelte +42 -0
  441. package/src/lib/components/slider/components/slider-tick.svelte +28 -0
  442. package/src/lib/components/slider/components/slider.svelte +136 -0
  443. package/src/lib/components/slider/helpers.ts +249 -0
  444. package/src/lib/components/slider/index.ts +15 -0
  445. package/src/lib/components/slider/slider.svelte.ts +979 -0
  446. package/src/lib/components/slider/types.ts +294 -0
  447. package/src/lib/components/sonner/Toast.svelte +558 -0
  448. package/src/lib/components/sonner/Toaster.svelte +603 -0
  449. package/src/lib/components/sonner/index.ts +15 -0
  450. package/src/lib/components/sonner/toast-state.svelte.ts +217 -0
  451. package/src/lib/components/sonner/types.ts +384 -0
  452. package/src/lib/components/switch/index.ts +8 -0
  453. package/src/lib/components/switch/switch.svelte +78 -0
  454. package/src/lib/components/table/index.ts +28 -0
  455. package/src/lib/components/table/table-body.svelte +15 -0
  456. package/src/lib/components/table/table-caption.svelte +10 -0
  457. package/src/lib/components/table/table-cell.svelte +14 -0
  458. package/src/lib/components/table/table-footer.svelte +19 -0
  459. package/src/lib/components/table/table-head.svelte +17 -0
  460. package/src/lib/components/table/table-header.svelte +15 -0
  461. package/src/lib/components/table/table-row.svelte +14 -0
  462. package/src/lib/components/table/table.svelte +12 -0
  463. package/src/lib/components/tabs/components/tabs-content.svelte +30 -0
  464. package/src/lib/components/tabs/components/tabs-list.svelte +38 -0
  465. package/src/lib/components/tabs/components/tabs-trigger.svelte +51 -0
  466. package/src/lib/components/tabs/components/tabs.svelte +52 -0
  467. package/src/lib/components/tabs/index.ts +11 -0
  468. package/src/lib/components/tabs/tabs.svelte.ts +253 -0
  469. package/src/lib/components/tabs/types.ts +86 -0
  470. package/src/lib/components/textarea/index.ts +7 -0
  471. package/src/lib/components/textarea/textarea.svelte +23 -0
  472. package/src/lib/components/theme-toggle/index.ts +5 -0
  473. package/src/lib/components/theme-toggle/theme-toggle.svelte +49 -0
  474. package/src/lib/components/theme-toggle/theme.svelte.ts +81 -0
  475. package/src/lib/components/time-field/components/time-field-hidden-input.svelte +9 -0
  476. package/src/lib/components/time-field/components/time-field-input.svelte +33 -0
  477. package/src/lib/components/time-field/components/time-field-label.svelte +29 -0
  478. package/src/lib/components/time-field/components/time-field-segment.svelte +29 -0
  479. package/src/lib/components/time-field/components/time-field.svelte +99 -0
  480. package/src/lib/components/time-field/index.ts +11 -0
  481. package/src/lib/components/time-field/time-field.svelte.ts +1198 -0
  482. package/src/lib/components/time-field/types.ts +174 -0
  483. package/src/lib/components/time-range-field/components/time-range-field-input.svelte +36 -0
  484. package/src/lib/components/time-range-field/components/time-range-field-label.svelte +29 -0
  485. package/src/lib/components/time-range-field/components/time-range-field.svelte +104 -0
  486. package/src/lib/components/time-range-field/index.ts +11 -0
  487. package/src/lib/components/time-range-field/time-range-field.svelte.ts +235 -0
  488. package/src/lib/components/time-range-field/types.ts +181 -0
  489. package/src/lib/components/toggle/components/toggle.svelte +55 -0
  490. package/src/lib/components/toggle/index.ts +3 -0
  491. package/src/lib/components/toggle/toggle.svelte.ts +57 -0
  492. package/src/lib/components/toggle/types.ts +33 -0
  493. package/src/lib/components/toggle/variants.ts +25 -0
  494. package/src/lib/components/toggle-group/components/toggle-group-item.svelte +67 -0
  495. package/src/lib/components/toggle-group/components/toggle-group.svelte +109 -0
  496. package/src/lib/components/toggle-group/index.ts +7 -0
  497. package/src/lib/components/toggle-group/toggle-group.svelte.ts +235 -0
  498. package/src/lib/components/toggle-group/types.ts +89 -0
  499. package/src/lib/components/toolbar/components/toolbar-button.svelte +46 -0
  500. package/src/lib/components/toolbar/components/toolbar-group-item.svelte +56 -0
  501. package/src/lib/components/toolbar/components/toolbar-group.svelte +69 -0
  502. package/src/lib/components/toolbar/components/toolbar-link.svelte +39 -0
  503. package/src/lib/components/toolbar/components/toolbar.svelte +39 -0
  504. package/src/lib/components/toolbar/index.ts +13 -0
  505. package/src/lib/components/toolbar/toolbar.svelte.ts +373 -0
  506. package/src/lib/components/toolbar/types.ts +60 -0
  507. package/src/lib/components/tooltip/components/tooltip-content.svelte +81 -0
  508. package/src/lib/components/tooltip/components/tooltip-provider.svelte +26 -0
  509. package/src/lib/components/tooltip/components/tooltip-trigger.svelte +48 -0
  510. package/src/lib/components/tooltip/components/tooltip.svelte +55 -0
  511. package/src/lib/components/tooltip/index.ts +17 -0
  512. package/src/lib/components/tooltip/tooltip.svelte.ts +796 -0
  513. package/src/lib/components/tooltip/types.ts +175 -0
  514. package/src/lib/components/tooltip/utils.ts +1 -0
  515. package/src/lib/icons/google-icon.svelte +10 -0
  516. package/src/lib/icons/google-workspace.svelte +18 -0
  517. package/src/lib/icons/icon.d.ts +19 -0
  518. package/src/lib/icons/instagram.svelte +9 -0
  519. package/src/lib/icons/instantly.svelte +10 -0
  520. package/src/lib/icons/linkedin.svelte +9 -0
  521. package/src/lib/icons/mailshake.svelte +10 -0
  522. package/src/lib/icons/microsoft-365.svelte +7 -0
  523. package/src/lib/icons/passcode.svelte +12 -0
  524. package/src/lib/icons/replyio.svelte +10 -0
  525. package/src/lib/icons/smartlead.svelte +19 -0
  526. package/src/lib/icons/smtp.svelte +10 -0
  527. package/src/lib/icons/x-logo.svelte +9 -0
  528. package/src/lib/internal/animations-settled.svelte.ts +119 -0
  529. package/src/lib/internal/arrays.ts +253 -0
  530. package/src/lib/internal/attribute-types.ts +53 -0
  531. package/src/lib/internal/attrs.ts +64 -0
  532. package/src/lib/internal/body-scroll-lock.svelte.ts +330 -0
  533. package/src/lib/internal/create-id.ts +12 -0
  534. package/src/lib/internal/date-time/announcer.ts +74 -0
  535. package/src/lib/internal/date-time/calendar-helpers.svelte.ts +399 -0
  536. package/src/lib/internal/date-time/field/helpers.ts +431 -0
  537. package/src/lib/internal/date-time/field/parts.ts +15 -0
  538. package/src/lib/internal/date-time/field/range-field.svelte.ts +45 -0
  539. package/src/lib/internal/date-time/field/segments.ts +89 -0
  540. package/src/lib/internal/date-time/field/time-helpers.ts +311 -0
  541. package/src/lib/internal/date-time/formatter.ts +86 -0
  542. package/src/lib/internal/date-time/placeholders.ts +131 -0
  543. package/src/lib/internal/date-time/types.ts +121 -0
  544. package/src/lib/internal/date-time/utils.ts +243 -0
  545. package/src/lib/internal/dismissible-layer/index.ts +3 -0
  546. package/src/lib/internal/dismissible-layer/scheduler.ts +48 -0
  547. package/src/lib/internal/dismissible-layer/types.ts +57 -0
  548. package/src/lib/internal/dismissible-layer/use-dismissable-layer.svelte.ts +291 -0
  549. package/src/lib/internal/escape-layer/index.ts +3 -0
  550. package/src/lib/internal/escape-layer/types.ts +34 -0
  551. package/src/lib/internal/escape-layer/use-escape-layer.svelte.ts +91 -0
  552. package/src/lib/internal/floating-layer/components/floating-layer-arrow.svelte +35 -0
  553. package/src/lib/internal/floating-layer/components/floating-layer-content-static.svelte +19 -0
  554. package/src/lib/internal/floating-layer/components/floating-layer-content.svelte +52 -0
  555. package/src/lib/internal/floating-layer/components/floating-layer.svelte +12 -0
  556. package/src/lib/internal/floating-layer/components/index.ts +6 -0
  557. package/src/lib/internal/floating-layer/index.ts +2 -0
  558. package/src/lib/internal/floating-layer/types.ts +126 -0
  559. package/src/lib/internal/floating-layer/use-floating-layer.svelte.ts +471 -0
  560. package/src/lib/internal/focus-scope/focus-scope.svelte.ts +310 -0
  561. package/src/lib/internal/focus-scope/index.ts +4 -0
  562. package/src/lib/internal/focus-scope/types.ts +45 -0
  563. package/src/lib/internal/focus.ts +14 -0
  564. package/src/lib/internal/global-singleton.ts +10 -0
  565. package/src/lib/internal/group-value.svelte.ts +36 -0
  566. package/src/lib/internal/hover-intent-geometry.ts +96 -0
  567. package/src/lib/internal/index.ts +44 -0
  568. package/src/lib/internal/kbd.ts +60 -0
  569. package/src/lib/internal/layer-stack.ts +37 -0
  570. package/src/lib/internal/math.ts +11 -0
  571. package/src/lib/internal/merge-props.ts +7 -0
  572. package/src/lib/internal/native-dialog-controller.svelte.ts +102 -0
  573. package/src/lib/internal/native-popover.svelte.ts +173 -0
  574. package/src/lib/internal/open-cell.svelte.ts +41 -0
  575. package/src/lib/internal/paneforge/index.ts +5 -0
  576. package/src/lib/internal/paneforge/internal/constants.ts +2 -0
  577. package/src/lib/internal/paneforge/internal/helpers.ts +354 -0
  578. package/src/lib/internal/paneforge/internal/types.ts +33 -0
  579. package/src/lib/internal/paneforge/internal/utils/adjust-layout.ts +122 -0
  580. package/src/lib/internal/paneforge/internal/utils/aria.ts +36 -0
  581. package/src/lib/internal/paneforge/internal/utils/compare.ts +15 -0
  582. package/src/lib/internal/paneforge/internal/utils/is.ts +13 -0
  583. package/src/lib/internal/paneforge/internal/utils/resize.ts +31 -0
  584. package/src/lib/internal/paneforge/internal/utils/storage.ts +116 -0
  585. package/src/lib/internal/paneforge/internal/utils/style.ts +104 -0
  586. package/src/lib/internal/paneforge/pane-group.svelte +48 -0
  587. package/src/lib/internal/paneforge/pane-resizer.svelte +40 -0
  588. package/src/lib/internal/paneforge/pane.svelte +60 -0
  589. package/src/lib/internal/paneforge/paneforge.svelte.ts +821 -0
  590. package/src/lib/internal/paneforge/types.ts +179 -0
  591. package/src/lib/internal/popper-layer/index.ts +2 -0
  592. package/src/lib/internal/popper-layer/popper-content.svelte +19 -0
  593. package/src/lib/internal/popper-layer/popper-layer-inner.svelte +122 -0
  594. package/src/lib/internal/popper-layer/popper-layer.svelte +71 -0
  595. package/src/lib/internal/popper-layer/types.ts +65 -0
  596. package/src/lib/internal/portal/index.ts +2 -0
  597. package/src/lib/internal/portal/portal.svelte +61 -0
  598. package/src/lib/internal/prop-resolvers.ts +14 -0
  599. package/src/lib/internal/roving-focus-group.ts +147 -0
  600. package/src/lib/internal/roving-focus-item.svelte.ts +51 -0
  601. package/src/lib/internal/safe-polygon.svelte.ts +239 -0
  602. package/src/lib/internal/shared-state.svelte.ts +39 -0
  603. package/src/lib/internal/state-machine.ts +32 -0
  604. package/src/lib/internal/svelte-resize-observer.svelte.ts +37 -0
  605. package/src/lib/internal/tabbable.ts +398 -0
  606. package/src/lib/internal/table/core.ts +53 -0
  607. package/src/lib/internal/table/filter-fns.ts +10 -0
  608. package/src/lib/internal/table/index.ts +2 -0
  609. package/src/lib/internal/table/sorting-fns.ts +84 -0
  610. package/src/lib/internal/table/table.svelte.ts +330 -0
  611. package/src/lib/internal/table/types.ts +33 -0
  612. package/src/lib/internal/timeout-fn.svelte.ts +36 -0
  613. package/src/lib/internal/tools/box.svelte.ts +153 -0
  614. package/src/lib/internal/tools/index.ts +22 -0
  615. package/src/lib/internal/tools/types.ts +44 -0
  616. package/src/lib/internal/tools/utils/attach-ref.ts +74 -0
  617. package/src/lib/internal/tools/utils/compose-handlers.ts +36 -0
  618. package/src/lib/internal/tools/utils/dom-context.svelte.ts +63 -0
  619. package/src/lib/internal/tools/utils/dom.ts +62 -0
  620. package/src/lib/internal/tools/utils/events.ts +1 -0
  621. package/src/lib/internal/tools/utils/index.ts +5 -0
  622. package/src/lib/internal/tools/utils/strings.ts +28 -0
  623. package/src/lib/internal/typeahead.svelte.ts +48 -0
  624. package/src/lib/internal/types.ts +119 -0
  625. package/src/lib/internal/utils.ts +10 -0
  626. package/src/lib/internal/vaul/browser.ts +8 -0
  627. package/src/lib/internal/vaul/constants.ts +19 -0
  628. package/src/lib/internal/vaul/helpers.ts +39 -0
  629. package/src/lib/internal/vaul/types.ts +142 -0
  630. package/src/lib/internal/vaul/use-drawer-content.svelte.ts +219 -0
  631. package/src/lib/internal/vaul/use-drawer-root.svelte.ts +682 -0
  632. package/src/lib/internal/vaul/use-position-fixed.svelte.ts +142 -0
  633. package/src/lib/internal/vaul/use-prevent-scroll.svelte.ts +267 -0
  634. package/src/lib/internal/vaul/use-snap-points.svelte.ts +246 -0
  635. package/src/lib/time.ts +73 -0
@@ -0,0 +1,1596 @@
1
+ import { attachRef, DOMContext, type ReadableBoxedValues, type WritableBoxedValues, type Box, boxWith } from '../../../internal/tools/index.js';
2
+ import { on } from 'svelte/events';
3
+ import { createContext, onDestroy, tick, untrack } from 'svelte';
4
+ import { backward, forward, next, prev } from '../../../internal/arrays.js';
5
+ import {
6
+ boolToStr,
7
+ boolToStrTrueOrUndef,
8
+ boolToEmptyStrOrUndef,
9
+ getDataOpenClosed,
10
+ boolToTrueOrUndef,
11
+ } from '../../../internal/attrs.js';
12
+ import { kbd, FIRST_LAST_KEYS } from '../../../internal/kbd.js';
13
+ import type {
14
+ BitsEvent,
15
+ BitsFocusEvent,
16
+ BitsKeyboardEvent,
17
+ BitsMouseEvent,
18
+ BitsPointerEvent,
19
+ OnChangeFn,
20
+ WithRefOpts,
21
+ RefAttachment,
22
+ } from '../../../internal/types.js';
23
+ import { isIOS } from '../../../internal/tools/utils/dom.js';
24
+ import { createBitsAttrs } from '../../../internal/attrs.js';
25
+ import { getFloatingContentCSSVars } from '../../../internal/floating-layer/index.js';
26
+ import { Typeahead, textContentOf } from '../../../internal/typeahead.svelte.js';
27
+ import { useOpenChangeComplete } from '../../../internal/animations-settled.svelte.js';
28
+ import { DEV } from 'esm-env';
29
+ import type { SelectValueSnippetProps } from './types.js';
30
+
31
+ // prettier-ignore
32
+ export const INTERACTION_KEYS: string[] = [kbd.ARROW_LEFT, kbd.ESCAPE, kbd.ARROW_RIGHT, kbd.SHIFT, kbd.CAPS_LOCK, kbd.CONTROL, kbd.ALT, kbd.META, kbd.ENTER, kbd.F1, kbd.F2, kbd.F3, kbd.F4, kbd.F5, kbd.F6, kbd.F7, kbd.F8, kbd.F9, kbd.F10, kbd.F11, kbd.F12];
33
+
34
+ export const CONTENT_MARGIN = 10;
35
+
36
+ const selectAttrs = createBitsAttrs({
37
+ component: 'select',
38
+ parts: [
39
+ 'trigger',
40
+ 'content',
41
+ 'item',
42
+ 'viewport',
43
+ 'scroll-up-button',
44
+ 'scroll-down-button',
45
+ 'group',
46
+ 'group-label',
47
+ 'separator',
48
+ 'arrow',
49
+ 'input',
50
+ 'content-wrapper',
51
+ 'item-text',
52
+ 'value',
53
+ ],
54
+ });
55
+
56
+ const [getSelectRoot, setSelectRoot] = createContext<SelectRoot>();
57
+ const [getSelectGroup, setSelectGroup] = createContext<SelectGroupState>();
58
+ const [getSelectContent, setSelectContent] = createContext<SelectContentState>();
59
+
60
+ interface SelectBaseRootStateOpts
61
+ extends
62
+ ReadableBoxedValues<{
63
+ disabled: boolean;
64
+ required: boolean;
65
+ name: string;
66
+ loop: boolean;
67
+ scrollAlignment: 'nearest' | 'center';
68
+ items: { value: string; label: string; disabled?: boolean }[];
69
+ allowDeselect: boolean;
70
+ onOpenChangeComplete: OnChangeFn<boolean>;
71
+ }>,
72
+ WritableBoxedValues<{
73
+ open: boolean;
74
+ inputValue: string;
75
+ }> {
76
+ isCombobox: boolean;
77
+ }
78
+
79
+ abstract class SelectBaseRootState {
80
+ readonly opts: SelectBaseRootStateOpts;
81
+ touchedInput = $state(false);
82
+ inputNode = $state<HTMLElement | null>(null);
83
+ contentNode = $state<HTMLElement | null>(null);
84
+ readonly #completion: { readonly pending: boolean };
85
+ /** Rendered: open, or closing with the exit still settling — the window the popper layer's scroll lock covers. */
86
+ readonly present = $derived.by(() => this.opts.open.current || this.#completion.pending);
87
+ viewportNode = $state<HTMLElement | null>(null);
88
+ triggerNode = $state<HTMLElement | null>(null);
89
+ valueNode = $state<HTMLElement | null>(null);
90
+ valueId = $state('');
91
+ highlightedNode = $state<HTMLElement | null>(null);
92
+ readonly highlightedValue = $derived.by(() => {
93
+ if (!this.highlightedNode) return null;
94
+ return this.highlightedNode.getAttribute('data-value');
95
+ });
96
+ readonly highlightedId = $derived.by(() => {
97
+ if (!this.highlightedNode) return undefined;
98
+ return this.highlightedNode.id;
99
+ });
100
+ readonly highlightedLabel = $derived.by(() => {
101
+ if (!this.highlightedNode) return null;
102
+ return this.highlightedNode.getAttribute('data-label');
103
+ });
104
+ contentIsPositioned = $state(false);
105
+ isKeyboard = $state(false);
106
+ isCombobox = false;
107
+ domContext = new DOMContext(() => null);
108
+
109
+ constructor(opts: SelectBaseRootStateOpts) {
110
+ this.opts = opts;
111
+ this.isCombobox = opts.isCombobox;
112
+
113
+ // Content stays mounted through its exit transition (CSS owns the motion; the closed state
114
+ // is the content's `contentStyle`); completion is settle-based on the content node.
115
+ this.#completion = useOpenChangeComplete(
116
+ () => this.opts.open.current,
117
+ () => this.contentNode,
118
+ (open) => this.opts.onOpenChangeComplete.current(open),
119
+ );
120
+
121
+ $effect.pre(() => {
122
+ if (!this.opts.open.current) {
123
+ this.setHighlightedNode(null);
124
+ }
125
+ });
126
+ }
127
+
128
+ setHighlightedNode(node: HTMLElement | null, initial = false) {
129
+ this.highlightedNode = node;
130
+ if (node && (this.isKeyboard || initial)) {
131
+ this.scrollHighlightedNodeIntoView(node);
132
+ }
133
+ }
134
+
135
+ scrollHighlightedNodeIntoView(node: HTMLElement) {
136
+ if (!this.viewportNode || !this.contentIsPositioned) return;
137
+ node.scrollIntoView({ block: this.opts.scrollAlignment.current });
138
+ }
139
+
140
+ getCandidateNodes(): HTMLElement[] {
141
+ const node = this.contentNode;
142
+ if (!node) return [];
143
+ return Array.from(node.querySelectorAll<HTMLElement>(`[${this.getBitsAttr('item')}]:not([data-disabled])`));
144
+ }
145
+
146
+ setHighlightedToFirstCandidate(initial = false) {
147
+ this.setHighlightedNode(null);
148
+
149
+ let nodes = this.getCandidateNodes();
150
+ if (!nodes.length) return;
151
+
152
+ // don't consider nodes that aren't visible within the viewport
153
+ if (this.viewportNode) {
154
+ const viewportRect = this.viewportNode.getBoundingClientRect();
155
+
156
+ nodes = nodes.filter((node) => {
157
+ if (!this.viewportNode) return false;
158
+
159
+ const nodeRect = node.getBoundingClientRect();
160
+
161
+ const isNodeFullyVisible =
162
+ nodeRect.right <= viewportRect.right &&
163
+ nodeRect.left >= viewportRect.left &&
164
+ nodeRect.bottom <= viewportRect.bottom &&
165
+ nodeRect.top >= viewportRect.top;
166
+
167
+ return isNodeFullyVisible;
168
+ });
169
+ }
170
+
171
+ this.setHighlightedNode(nodes[0]!, initial);
172
+ }
173
+
174
+ getNodeByValue(value: string): HTMLElement | null {
175
+ const candidateNodes = this.getCandidateNodes();
176
+ return candidateNodes.find((node) => node.dataset.value === value) ?? null;
177
+ }
178
+
179
+ /**
180
+ * Resolves the display label for a value: `items` entry when present, otherwise the
181
+ * mounted item's `data-label` or its text content.
182
+ */
183
+ getLabelForValue(value: string): string {
184
+ if (value === '') return '';
185
+ const fromItems = this.opts.items.current.find((item) => item.value === value)?.label;
186
+ if (fromItems !== undefined) return fromItems;
187
+ const node = this.getNodeByValue(value);
188
+ if (node) {
189
+ const dataLabel = node.getAttribute('data-label');
190
+ if (dataLabel !== null && dataLabel !== '') return dataLabel;
191
+ return node.textContent?.trim() ?? value;
192
+ }
193
+ return value;
194
+ }
195
+
196
+ setOpen(open: boolean) {
197
+ this.opts.open.current = open;
198
+ }
199
+
200
+ toggleOpen() {
201
+ this.opts.open.current = !this.opts.open.current;
202
+ }
203
+
204
+ handleOpen() {
205
+ this.setOpen(true);
206
+ }
207
+
208
+ handleClose() {
209
+ this.setHighlightedNode(null);
210
+ this.setOpen(false);
211
+ }
212
+
213
+ toggleMenu() {
214
+ this.toggleOpen();
215
+ }
216
+
217
+ getBitsAttr: typeof selectAttrs.getAttr = (part) => {
218
+ return selectAttrs.getAttr(part, this.isCombobox ? 'combobox' : undefined);
219
+ };
220
+ }
221
+
222
+ interface SelectSingleRootStateOpts
223
+ extends
224
+ SelectBaseRootStateOpts,
225
+ WritableBoxedValues<{
226
+ value: string;
227
+ }> {}
228
+
229
+ export class SelectSingleRootState extends SelectBaseRootState {
230
+ override readonly opts: SelectSingleRootStateOpts;
231
+ readonly isMulti = false as const;
232
+ readonly hasValue = $derived.by(() => this.opts.value.current !== '');
233
+ readonly currentLabel = $derived.by(() => {
234
+ if (!this.opts.items.current.length) return '';
235
+ return this.opts.items.current.find((item) => item.value === this.opts.value.current)?.label ?? '';
236
+ });
237
+ readonly candidateLabels = $derived.by(() => {
238
+ if (!this.opts.items.current.length) return [];
239
+ const filteredItems = this.opts.items.current.filter((item) => !item.disabled);
240
+ return filteredItems.map((item) => item.label);
241
+ });
242
+ readonly dataTypeaheadEnabled = $derived.by(() => {
243
+ if (this.isMulti) return false;
244
+ if (this.opts.items.current.length === 0) return false;
245
+ return true;
246
+ });
247
+
248
+ constructor(opts: SelectSingleRootStateOpts) {
249
+ super(opts);
250
+
251
+ this.opts = opts;
252
+
253
+ $effect(() => {
254
+ if (!this.opts.open.current && this.highlightedNode) {
255
+ this.setHighlightedNode(null);
256
+ }
257
+ });
258
+
259
+ $effect(() => {
260
+ this.opts.open.current;
261
+ untrack(() => {
262
+ if (!this.opts.open.current) return;
263
+ this.setInitialHighlightedNode();
264
+ });
265
+ });
266
+ }
267
+
268
+ includesItem(itemValue: string) {
269
+ return this.opts.value.current === itemValue;
270
+ }
271
+
272
+ toggleItem(itemValue: string, itemLabel: string = itemValue) {
273
+ const newValue = this.includesItem(itemValue) ? '' : itemValue;
274
+ this.opts.value.current = newValue;
275
+ if (newValue !== '') {
276
+ this.opts.inputValue.current = itemLabel;
277
+ }
278
+ }
279
+
280
+ setInitialHighlightedNode() {
281
+ tick().then(() => {
282
+ if (this.highlightedNode && this.domContext.getDocument().contains(this.highlightedNode)) return;
283
+ if (this.opts.value.current !== '') {
284
+ const node = this.getNodeByValue(this.opts.value.current);
285
+ if (node) {
286
+ this.setHighlightedNode(node, true);
287
+ return;
288
+ }
289
+ }
290
+ // if no value is set, we want to highlight the first item
291
+ this.setHighlightedToFirstCandidate(true);
292
+ });
293
+ }
294
+ }
295
+
296
+ interface SelectMultipleRootStateOpts
297
+ extends
298
+ SelectBaseRootStateOpts,
299
+ WritableBoxedValues<{
300
+ value: string[];
301
+ }> {}
302
+
303
+ class SelectMultipleRootState extends SelectBaseRootState {
304
+ override readonly opts: SelectMultipleRootStateOpts;
305
+ readonly isMulti = true as const;
306
+ readonly hasValue = $derived.by(() => this.opts.value.current.length > 0);
307
+
308
+ constructor(opts: SelectMultipleRootStateOpts) {
309
+ super(opts);
310
+
311
+ this.opts = opts;
312
+
313
+ $effect(() => {
314
+ if (!this.opts.open.current && this.highlightedNode) {
315
+ this.setHighlightedNode(null);
316
+ }
317
+ });
318
+
319
+ $effect(() => {
320
+ this.opts.open.current;
321
+ untrack(() => {
322
+ if (!this.opts.open.current) return;
323
+ this.setInitialHighlightedNode();
324
+ });
325
+ });
326
+ }
327
+
328
+ includesItem(itemValue: string) {
329
+ return this.opts.value.current.includes(itemValue);
330
+ }
331
+
332
+ toggleItem(itemValue: string, itemLabel: string = itemValue) {
333
+ if (this.includesItem(itemValue)) {
334
+ this.opts.value.current = this.opts.value.current.filter((v) => v !== itemValue);
335
+ } else {
336
+ this.opts.value.current = [...this.opts.value.current, itemValue];
337
+ }
338
+ this.opts.inputValue.current = itemLabel;
339
+ }
340
+
341
+ setInitialHighlightedNode() {
342
+ tick().then(() => {
343
+ if (!this.domContext) return;
344
+ if (this.highlightedNode && this.domContext.getDocument().contains(this.highlightedNode)) return;
345
+ if (this.opts.value.current.length && this.opts.value.current[0] !== '') {
346
+ const node = this.getNodeByValue(this.opts.value.current[0]!);
347
+ if (node) {
348
+ this.setHighlightedNode(node, true);
349
+ return;
350
+ }
351
+ }
352
+ // if no value is set, we want to highlight the first item
353
+ this.setHighlightedToFirstCandidate(true);
354
+ });
355
+ }
356
+ }
357
+
358
+ interface SelectRootStateOpts
359
+ extends
360
+ ReadableBoxedValues<{
361
+ disabled: boolean;
362
+ required: boolean;
363
+ loop: boolean;
364
+ scrollAlignment: 'nearest' | 'center';
365
+ name: string;
366
+ items: { value: string; label: string; disabled?: boolean }[];
367
+ allowDeselect: boolean;
368
+ onOpenChangeComplete: OnChangeFn<boolean>;
369
+ }>,
370
+ WritableBoxedValues<{
371
+ open: boolean;
372
+ inputValue: string;
373
+ }> {
374
+ isCombobox: boolean;
375
+ type: 'single' | 'multiple';
376
+ value: Box<string> | Box<string[]>;
377
+ }
378
+
379
+ export class SelectRootState {
380
+ static create(props: SelectRootStateOpts): SelectRoot {
381
+ const { type, ...rest } = props;
382
+
383
+ const rootState =
384
+ type === 'single'
385
+ ? new SelectSingleRootState(rest as SelectSingleRootStateOpts)
386
+ : new SelectMultipleRootState(rest as SelectMultipleRootStateOpts);
387
+
388
+ return setSelectRoot(rootState);
389
+ }
390
+ }
391
+
392
+ type SelectRoot = SelectSingleRootState | SelectMultipleRootState;
393
+
394
+ type SelectValueStateProps = WithRefOpts<
395
+ ReadableBoxedValues<{
396
+ placeholder: string | null | undefined;
397
+ }>
398
+ >;
399
+
400
+ export class SelectValueState {
401
+ static create(opts: SelectValueStateProps) {
402
+ return new SelectValueState(opts, getSelectRoot());
403
+ }
404
+ readonly root: SelectRoot;
405
+ readonly opts: SelectValueStateProps;
406
+ readonly attachment: RefAttachment;
407
+
408
+ constructor(opts: SelectValueStateProps, root: SelectRoot) {
409
+ this.root = root;
410
+ this.opts = opts;
411
+ this.attachment = attachRef(opts.ref, (v) => (this.root.valueNode = v));
412
+ this.setValue = this.setValue.bind(this);
413
+ }
414
+
415
+ setValue(value: string | string[]) {
416
+ if (this.root.isMulti && !Array.isArray(value)) {
417
+ if (DEV) throw new Error(`Expected an array of strings passed to \`setValue\` got ${typeof value}.`);
418
+ return;
419
+ }
420
+ if (!this.root.isMulti && typeof value !== 'string') {
421
+ if (DEV) throw new Error(`Expected a string passed to \`setValue\` got ${typeof value}.`);
422
+ return;
423
+ }
424
+ this.root.opts.value.current = value;
425
+ }
426
+
427
+ // this way consumers get type narrowing for the value on `type`
428
+ readonly snippetProps: SelectValueSnippetProps = $derived.by(() => {
429
+ if (this.root.isMulti) {
430
+ return {
431
+ selection: {
432
+ type: 'multiple' as const,
433
+ selected:
434
+ this.root.opts.value.current.length > 0
435
+ ? this.root.opts.value.current.map((value) => ({
436
+ value,
437
+ label: this.root.getLabelForValue(value),
438
+ }))
439
+ : [],
440
+ setValue: this.setValue,
441
+ },
442
+ placeholder: this.opts.placeholder.current ?? null,
443
+ disabled: this.root.opts.disabled.current,
444
+ };
445
+ }
446
+ const value = this.root.opts.value.current;
447
+ return {
448
+ selection: {
449
+ type: 'single' as const,
450
+ selected: value !== '' ? { value, label: value === '' ? '' : this.root.getLabelForValue(value) } : undefined,
451
+ setValue: this.setValue,
452
+ },
453
+ placeholder: this.opts.placeholder.current ?? null,
454
+ disabled: this.root.opts.disabled.current,
455
+ };
456
+ });
457
+
458
+ readonly props = $derived.by(() => ({
459
+ id: this.opts.id.current,
460
+ 'data-placeholder': this.root.hasValue ? undefined : '',
461
+ 'data-select-value': '',
462
+ ...this.attachment,
463
+ }));
464
+ }
465
+
466
+ interface SelectInputStateOpts
467
+ extends
468
+ WithRefOpts,
469
+ ReadableBoxedValues<{
470
+ clearOnDeselect: boolean;
471
+ }> {}
472
+
473
+ export class SelectInputState {
474
+ static create(opts: SelectInputStateOpts) {
475
+ return new SelectInputState(opts, getSelectRoot());
476
+ }
477
+ readonly opts: SelectInputStateOpts;
478
+ readonly root: SelectRoot;
479
+ readonly attachment: RefAttachment;
480
+
481
+ constructor(opts: SelectInputStateOpts, root: SelectRoot) {
482
+ this.opts = opts;
483
+ this.root = root;
484
+ this.attachment = attachRef(opts.ref, (v) => (this.root.inputNode = v));
485
+ this.root.domContext = new DOMContext(opts.ref);
486
+
487
+ this.onkeydown = this.onkeydown.bind(this);
488
+ this.oninput = this.oninput.bind(this);
489
+
490
+ let prevValue: string | string[] | undefined = undefined;
491
+ $effect(() => {
492
+ const value = this.root.opts.value.current;
493
+ const clearOnDeselect = this.opts.clearOnDeselect.current;
494
+ untrack(() => {
495
+ if (!clearOnDeselect) return;
496
+ if (Array.isArray(value) && Array.isArray(prevValue)) {
497
+ if (value.length === 0 && prevValue.length !== 0) {
498
+ this.root.opts.inputValue.current = '';
499
+ }
500
+ } else if (value === '' && prevValue !== '') {
501
+ this.root.opts.inputValue.current = '';
502
+ }
503
+ });
504
+ prevValue = value;
505
+ });
506
+ }
507
+
508
+ onkeydown(e: BitsKeyboardEvent) {
509
+ this.root.isKeyboard = true;
510
+ if (e.key === kbd.ESCAPE) return;
511
+
512
+ // prevent arrow up/down from moving the position of the cursor in the input
513
+ if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) e.preventDefault();
514
+ if (!this.root.opts.open.current) {
515
+ if (INTERACTION_KEYS.includes(e.key)) return;
516
+ if (e.key === kbd.TAB) return;
517
+ if (e.key === kbd.BACKSPACE && this.root.opts.inputValue.current === '') return;
518
+ this.root.handleOpen();
519
+ // we need to wait for a tick after the menu opens to ensure the highlighted nodes are
520
+ // set correctly.
521
+ if (this.root.hasValue) return;
522
+ const candidateNodes = this.root.getCandidateNodes();
523
+ if (!candidateNodes.length) return;
524
+
525
+ if (e.key === kbd.ARROW_DOWN) {
526
+ const firstCandidate = candidateNodes[0]!;
527
+ this.root.setHighlightedNode(firstCandidate);
528
+ } else if (e.key === kbd.ARROW_UP) {
529
+ const lastCandidate = candidateNodes[candidateNodes.length - 1]!;
530
+ this.root.setHighlightedNode(lastCandidate);
531
+ }
532
+ return;
533
+ }
534
+
535
+ if (e.key === kbd.TAB) {
536
+ this.root.handleClose();
537
+ return;
538
+ }
539
+
540
+ if (e.key === kbd.ENTER && !e.isComposing) {
541
+ e.preventDefault();
542
+
543
+ const isCurrentSelectedValue = this.root.highlightedValue === this.root.opts.value.current;
544
+
545
+ if (!this.root.opts.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {
546
+ this.root.handleClose();
547
+ return;
548
+ }
549
+
550
+ if (this.root.highlightedValue && this.root.highlightedNode && this.root.highlightedNode.isConnected) {
551
+ this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);
552
+ }
553
+ if (!this.root.isMulti && !isCurrentSelectedValue) {
554
+ this.root.handleClose();
555
+ }
556
+ }
557
+
558
+ if (e.key === kbd.ARROW_UP && e.altKey) {
559
+ this.root.handleClose();
560
+ }
561
+
562
+ if (FIRST_LAST_KEYS.includes(e.key)) {
563
+ e.preventDefault();
564
+ const candidateNodes = this.root.getCandidateNodes();
565
+ const currHighlightedNode = this.root.highlightedNode;
566
+ const currIndex = currHighlightedNode ? candidateNodes.indexOf(currHighlightedNode) : -1;
567
+
568
+ const loop = this.root.opts.loop.current;
569
+ let nextItem: HTMLElement | undefined;
570
+
571
+ if (e.key === kbd.ARROW_DOWN) {
572
+ nextItem = next(candidateNodes, currIndex, loop);
573
+ } else if (e.key === kbd.ARROW_UP) {
574
+ nextItem = prev(candidateNodes, currIndex, loop);
575
+ } else if (e.key === kbd.PAGE_DOWN) {
576
+ nextItem = forward(candidateNodes, currIndex, 10, loop);
577
+ } else if (e.key === kbd.PAGE_UP) {
578
+ nextItem = backward(candidateNodes, currIndex, 10, loop);
579
+ } else if (e.key === kbd.HOME) {
580
+ nextItem = candidateNodes[0];
581
+ } else if (e.key === kbd.END) {
582
+ nextItem = candidateNodes[candidateNodes.length - 1];
583
+ }
584
+ if (!nextItem) return;
585
+ this.root.setHighlightedNode(nextItem);
586
+ return;
587
+ }
588
+
589
+ if (INTERACTION_KEYS.includes(e.key)) return;
590
+ if (!this.root.highlightedNode) {
591
+ this.root.setHighlightedToFirstCandidate();
592
+ }
593
+ }
594
+
595
+ oninput(e: BitsEvent<Event, HTMLInputElement>) {
596
+ this.root.opts.inputValue.current = e.currentTarget.value;
597
+ this.root.setHighlightedToFirstCandidate();
598
+ }
599
+
600
+ readonly props = $derived.by(
601
+ () =>
602
+ ({
603
+ id: this.opts.id.current,
604
+ role: 'combobox',
605
+ disabled: this.root.opts.disabled.current ? true : undefined,
606
+ 'aria-activedescendant': this.root.highlightedId,
607
+ 'aria-autocomplete': 'list',
608
+ 'aria-expanded': boolToStr(this.root.opts.open.current),
609
+ 'data-state': getDataOpenClosed(this.root.opts.open.current),
610
+ 'data-disabled': boolToEmptyStrOrUndef(this.root.opts.disabled.current),
611
+ onkeydown: this.onkeydown,
612
+ oninput: this.oninput,
613
+ [this.root.getBitsAttr('input')]: '',
614
+ ...this.attachment,
615
+ }) as const,
616
+ );
617
+ }
618
+
619
+ interface SelectComboTriggerStateOpts extends WithRefOpts {}
620
+
621
+ export class SelectComboTriggerState {
622
+ static create(opts: SelectComboTriggerStateOpts) {
623
+ return new SelectComboTriggerState(opts, getSelectRoot());
624
+ }
625
+ readonly opts: SelectComboTriggerStateOpts;
626
+ readonly root: SelectBaseRootState;
627
+ readonly attachment: RefAttachment;
628
+
629
+ constructor(opts: SelectComboTriggerStateOpts, root: SelectBaseRootState) {
630
+ this.opts = opts;
631
+ this.root = root;
632
+ this.attachment = attachRef(opts.ref);
633
+ this.onkeydown = this.onkeydown.bind(this);
634
+ this.onpointerdown = this.onpointerdown.bind(this);
635
+ }
636
+
637
+ onkeydown(e: BitsKeyboardEvent) {
638
+ if (!this.root.domContext) return;
639
+ if (e.key === kbd.ENTER || e.key === kbd.SPACE) {
640
+ e.preventDefault();
641
+ if (this.root.domContext.getActiveElement() !== this.root.inputNode) {
642
+ this.root.inputNode?.focus();
643
+ }
644
+ this.root.toggleMenu();
645
+ }
646
+ }
647
+
648
+ /**
649
+ * `pointerdown` fires before the `focus` event, so we can prevent the default
650
+ * behavior of focusing the button and keep focus on the input.
651
+ */
652
+ onpointerdown(e: BitsPointerEvent) {
653
+ if (this.root.opts.disabled.current || !this.root.domContext) return;
654
+ e.preventDefault();
655
+ if (this.root.domContext.getActiveElement() !== this.root.inputNode) {
656
+ this.root.inputNode?.focus();
657
+ }
658
+ this.root.toggleMenu();
659
+ }
660
+
661
+ readonly props = $derived.by(
662
+ () =>
663
+ ({
664
+ id: this.opts.id.current,
665
+ disabled: this.root.opts.disabled.current ? true : undefined,
666
+ 'aria-haspopup': 'listbox',
667
+ 'data-state': getDataOpenClosed(this.root.opts.open.current),
668
+ 'data-disabled': boolToEmptyStrOrUndef(this.root.opts.disabled.current),
669
+ [this.root.getBitsAttr('trigger')]: '',
670
+ onpointerdown: this.onpointerdown,
671
+ onkeydown: this.onkeydown,
672
+ ...this.attachment,
673
+ }) as const,
674
+ );
675
+ }
676
+
677
+ interface SelectTriggerStateOpts extends WithRefOpts {}
678
+
679
+ export class SelectTriggerState {
680
+ static create(opts: SelectTriggerStateOpts) {
681
+ return new SelectTriggerState(opts, getSelectRoot());
682
+ }
683
+ readonly opts: SelectTriggerStateOpts;
684
+ readonly root: SelectRoot;
685
+ readonly attachment: RefAttachment;
686
+ readonly #typeahead: Typeahead<HTMLElement>;
687
+ readonly #labelTypeahead: Typeahead<string>;
688
+
689
+ constructor(opts: SelectTriggerStateOpts, root: SelectRoot) {
690
+ this.opts = opts;
691
+ this.root = root;
692
+ this.attachment = attachRef(opts.ref, (v) => (this.root.triggerNode = v));
693
+ this.root.domContext = new DOMContext(opts.ref);
694
+
695
+ this.#typeahead = new Typeahead({
696
+ getSearchText: textContentOf,
697
+ getCurrentCandidate: () => this.root.highlightedNode,
698
+ onMatch: (node) => {
699
+ this.root.setHighlightedNode(node);
700
+ },
701
+ getWindow: () => this.root.domContext.getWindow(),
702
+ });
703
+
704
+ this.#labelTypeahead = new Typeahead({
705
+ getSearchText: (label) => label,
706
+ getCurrentCandidate: () => {
707
+ if (this.root.isMulti) return '';
708
+ return this.root.currentLabel;
709
+ },
710
+ onMatch: (label: string) => {
711
+ if (this.root.isMulti) return;
712
+ if (!this.root.opts.items.current) return;
713
+ const matchedItem = this.root.opts.items.current.find((item) => item.label === label);
714
+ if (!matchedItem) return;
715
+ this.root.opts.value.current = matchedItem.value;
716
+ },
717
+ getWindow: () => this.root.domContext.getWindow(),
718
+ });
719
+
720
+ this.onkeydown = this.onkeydown.bind(this);
721
+ this.onpointerdown = this.onpointerdown.bind(this);
722
+ this.onpointerup = this.onpointerup.bind(this);
723
+ this.onclick = this.onclick.bind(this);
724
+ }
725
+
726
+ #handleOpen() {
727
+ this.root.opts.open.current = true;
728
+ this.#labelTypeahead.reset();
729
+ this.#typeahead.reset();
730
+ }
731
+
732
+ #handlePointerOpen(_: PointerEvent) {
733
+ this.#handleOpen();
734
+ }
735
+
736
+ /**
737
+ * Logic used to handle keyboard selection/deselection.
738
+ *
739
+ * If it returns true, it means the item was selected and whatever is calling
740
+ * this function should return early
741
+ *
742
+ */
743
+ #handleKeyboardSelection() {
744
+ const isCurrentSelectedValue = this.root.highlightedValue === this.root.opts.value.current;
745
+
746
+ if (!this.root.opts.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {
747
+ this.root.handleClose();
748
+ return true;
749
+ }
750
+
751
+ // "" is a valid value for a select item so we need to check for that
752
+ if (this.root.highlightedValue !== null) {
753
+ this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);
754
+ }
755
+
756
+ if (!this.root.isMulti && !isCurrentSelectedValue) {
757
+ this.root.handleClose();
758
+ return true;
759
+ }
760
+
761
+ return false;
762
+ }
763
+
764
+ onkeydown(e: BitsKeyboardEvent) {
765
+ this.root.isKeyboard = true;
766
+ if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) e.preventDefault();
767
+
768
+ if (!this.root.opts.open.current) {
769
+ if (e.key === kbd.ENTER || e.key === kbd.SPACE || e.key === kbd.ARROW_DOWN || e.key === kbd.ARROW_UP) {
770
+ e.preventDefault();
771
+ this.root.handleOpen();
772
+ } else if (!this.root.isMulti && this.root.dataTypeaheadEnabled) {
773
+ this.#labelTypeahead.handleKey(e.key, this.root.candidateLabels);
774
+ return;
775
+ }
776
+
777
+ // we need to wait for a tick after the menu opens to ensure
778
+ // the highlighted nodes are set correctly
779
+ if (this.root.hasValue) return;
780
+ const candidateNodes = this.root.getCandidateNodes();
781
+ if (!candidateNodes.length) return;
782
+
783
+ if (e.key === kbd.ARROW_DOWN) {
784
+ const firstCandidate = candidateNodes[0]!;
785
+ this.root.setHighlightedNode(firstCandidate);
786
+ } else if (e.key === kbd.ARROW_UP) {
787
+ const lastCandidate = candidateNodes[candidateNodes.length - 1]!;
788
+ this.root.setHighlightedNode(lastCandidate);
789
+ }
790
+ return;
791
+ }
792
+
793
+ if (e.key === kbd.TAB) {
794
+ this.root.handleClose();
795
+ return;
796
+ }
797
+
798
+ if (
799
+ (e.key === kbd.ENTER ||
800
+ // if we're currently "typing ahead", we don't want to select the item
801
+ // just yet as the item the user is trying to get to may have a space in it,
802
+ // so we defer handling the close for this case until further down
803
+ (e.key === kbd.SPACE && this.#typeahead.search === '')) &&
804
+ !e.isComposing
805
+ ) {
806
+ e.preventDefault();
807
+ const shouldReturn = this.#handleKeyboardSelection();
808
+ if (shouldReturn) return;
809
+ }
810
+
811
+ if (e.key === kbd.ARROW_UP && e.altKey) {
812
+ this.root.handleClose();
813
+ }
814
+
815
+ if (FIRST_LAST_KEYS.includes(e.key)) {
816
+ e.preventDefault();
817
+ const candidateNodes = this.root.getCandidateNodes();
818
+ const currHighlightedNode = this.root.highlightedNode;
819
+ const currIndex = currHighlightedNode ? candidateNodes.indexOf(currHighlightedNode) : -1;
820
+
821
+ const loop = this.root.opts.loop.current;
822
+ let nextItem: HTMLElement | undefined;
823
+
824
+ if (e.key === kbd.ARROW_DOWN) {
825
+ nextItem = next(candidateNodes, currIndex, loop);
826
+ } else if (e.key === kbd.ARROW_UP) {
827
+ nextItem = prev(candidateNodes, currIndex, loop);
828
+ } else if (e.key === kbd.PAGE_DOWN) {
829
+ nextItem = forward(candidateNodes, currIndex, 10, loop);
830
+ } else if (e.key === kbd.PAGE_UP) {
831
+ nextItem = backward(candidateNodes, currIndex, 10, loop);
832
+ } else if (e.key === kbd.HOME) {
833
+ nextItem = candidateNodes[0];
834
+ } else if (e.key === kbd.END) {
835
+ nextItem = candidateNodes[candidateNodes.length - 1];
836
+ }
837
+ if (!nextItem) return;
838
+ this.root.setHighlightedNode(nextItem);
839
+ return;
840
+ }
841
+ const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;
842
+ const isCharacterKey = e.key.length === 1;
843
+ const isSpaceKey = e.key === kbd.SPACE;
844
+
845
+ const candidateNodes = this.root.getCandidateNodes();
846
+
847
+ if (e.key === kbd.TAB) return;
848
+
849
+ if (!isModifierKey && (isCharacterKey || isSpaceKey)) {
850
+ const matchedNode = this.#typeahead.handleKey(e.key, candidateNodes);
851
+ if (!matchedNode && isSpaceKey) {
852
+ e.preventDefault();
853
+ this.#handleKeyboardSelection();
854
+ }
855
+ return;
856
+ }
857
+
858
+ if (!this.root.highlightedNode) {
859
+ this.root.setHighlightedToFirstCandidate();
860
+ }
861
+ }
862
+
863
+ onclick(e: BitsMouseEvent) {
864
+ // While browsers generally have no issue focusing the trigger when clicking
865
+ // on a label, Safari seems to struggle with the fact that there's no `onClick`.
866
+ // We force `focus` in this case. Note: this doesn't create any other side-effect
867
+ // because we are preventing default in `onpointerdown` so effectively
868
+ // this only runs for a label 'click'
869
+ const currTarget = e.currentTarget as HTMLElement;
870
+ currTarget.focus();
871
+ }
872
+
873
+ onpointerdown(e: BitsPointerEvent) {
874
+ if (this.root.opts.disabled.current) return;
875
+ // prevent opening on touch down which can be triggered when scrolling on touch devices
876
+ if (e.pointerType === 'touch') return e.preventDefault();
877
+
878
+ // prevent implicit pointer capture
879
+ const target = e.target as HTMLElement;
880
+ if (target?.hasPointerCapture(e.pointerId)) {
881
+ target?.releasePointerCapture(e.pointerId);
882
+ }
883
+
884
+ // only call the handle if it's a left click, since pointerdown is triggered
885
+ // by right clicks as well, but not when ctrl is pressed
886
+ if (e.button === 0 && e.ctrlKey === false) {
887
+ if (this.root.opts.open.current === false) {
888
+ this.#handlePointerOpen(e);
889
+ } else {
890
+ this.root.handleClose();
891
+ }
892
+ }
893
+ }
894
+
895
+ onpointerup(e: BitsPointerEvent) {
896
+ if (this.root.opts.disabled.current) return;
897
+ e.preventDefault();
898
+ if (e.pointerType === 'touch') {
899
+ if (this.root.opts.open.current === false) {
900
+ this.#handlePointerOpen(e);
901
+ } else {
902
+ this.root.handleClose();
903
+ }
904
+ }
905
+ }
906
+
907
+ readonly props = $derived.by(
908
+ () =>
909
+ ({
910
+ id: this.opts.id.current,
911
+ disabled: this.root.opts.disabled.current ? true : undefined,
912
+ 'aria-haspopup': 'listbox',
913
+ 'aria-expanded': boolToStr(this.root.opts.open.current),
914
+ 'aria-activedescendant': this.root.highlightedId,
915
+ 'data-state': getDataOpenClosed(this.root.opts.open.current),
916
+ 'data-disabled': boolToEmptyStrOrUndef(this.root.opts.disabled.current),
917
+ 'data-placeholder': this.root.hasValue ? undefined : '',
918
+ [this.root.getBitsAttr('trigger')]: '',
919
+ onpointerdown: this.onpointerdown,
920
+ onkeydown: this.onkeydown,
921
+ onclick: this.onclick,
922
+ onpointerup: this.onpointerup,
923
+ ...this.attachment,
924
+ }) as const,
925
+ );
926
+ }
927
+
928
+ interface SelectContentStateOpts
929
+ extends
930
+ WithRefOpts,
931
+ ReadableBoxedValues<{
932
+ onInteractOutside: (e: PointerEvent) => void;
933
+ onEscapeKeydown: (e: KeyboardEvent) => void;
934
+ }> {
935
+ /** Rendered in flow (ContentStatic) rather than in a floating wrapper. */
936
+ isStatic?: boolean;
937
+ }
938
+
939
+ export class SelectContentState {
940
+ static create(opts: SelectContentStateOpts) {
941
+ return setSelectContent(new SelectContentState(opts, getSelectRoot()));
942
+ }
943
+ readonly opts: SelectContentStateOpts;
944
+ readonly root: SelectRoot;
945
+ readonly attachment: RefAttachment;
946
+ isPositioned = $state(false);
947
+ domContext: DOMContext;
948
+ #isStatic: boolean;
949
+
950
+ constructor(opts: SelectContentStateOpts, root: SelectRoot) {
951
+ this.opts = opts;
952
+ this.root = root;
953
+ this.#isStatic = opts.isStatic ?? false;
954
+ this.attachment = attachRef(opts.ref, (v) => (this.root.contentNode = v));
955
+ this.domContext = new DOMContext(this.opts.ref);
956
+
957
+ if (this.root.domContext === null) {
958
+ this.root.domContext = this.domContext;
959
+ }
960
+
961
+ onDestroy(() => {
962
+ this.root.contentNode = null;
963
+ this.root.contentIsPositioned = false;
964
+ this.isPositioned = false;
965
+ });
966
+
967
+ $effect(() => {
968
+ this.root.opts.open.current;
969
+ untrack(() => {
970
+ if (this.root.opts.open.current) return;
971
+ this.root.contentIsPositioned = false;
972
+ this.isPositioned = false;
973
+ });
974
+ });
975
+
976
+ $effect(() => {
977
+ this.isPositioned;
978
+ this.root.highlightedNode;
979
+ untrack(() => {
980
+ if (!this.isPositioned || !this.root.highlightedNode) return;
981
+ this.root.scrollHighlightedNodeIntoView(this.root.highlightedNode);
982
+ });
983
+ });
984
+
985
+ this.onpointermove = this.onpointermove.bind(this);
986
+ }
987
+
988
+ onpointermove(_: BitsPointerEvent) {
989
+ this.root.isKeyboard = false;
990
+ }
991
+
992
+ readonly #styles = $derived.by(() => {
993
+ return getFloatingContentCSSVars(this.root.isCombobox ? 'combobox' : 'select');
994
+ });
995
+
996
+ onInteractOutside = (e: PointerEvent) => {
997
+ if (e.target === this.root.triggerNode || e.target === this.root.inputNode) {
998
+ e.preventDefault();
999
+ return;
1000
+ }
1001
+ this.opts.onInteractOutside.current(e);
1002
+ if (e.defaultPrevented) return;
1003
+ this.root.handleClose();
1004
+ };
1005
+
1006
+ onEscapeKeydown = (e: KeyboardEvent) => {
1007
+ this.opts.onEscapeKeydown.current(e);
1008
+ if (e.defaultPrevented) return;
1009
+ this.root.handleClose();
1010
+ };
1011
+
1012
+ onOpenAutoFocus = (e: Event) => {
1013
+ e.preventDefault();
1014
+ };
1015
+
1016
+ onCloseAutoFocus = (e: Event) => {
1017
+ e.preventDefault();
1018
+ };
1019
+
1020
+ readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
1021
+
1022
+ readonly props = $derived.by(
1023
+ () =>
1024
+ ({
1025
+ id: this.opts.id.current,
1026
+ role: 'listbox',
1027
+ 'aria-multiselectable': this.root.isMulti ? 'true' : undefined,
1028
+ 'data-state': getDataOpenClosed(this.root.opts.open.current),
1029
+ [this.root.getBitsAttr('content')]: '',
1030
+ style: {
1031
+ display: 'flex',
1032
+ flexDirection: 'column',
1033
+ outline: 'none',
1034
+ boxSizing: 'border-box',
1035
+ pointerEvents: 'auto',
1036
+ ...this.#styles,
1037
+ },
1038
+ onpointermove: this.onpointermove,
1039
+ ...this.attachment,
1040
+ }) as const,
1041
+ );
1042
+
1043
+ /**
1044
+ * Inline style for the content element alone — `props.style` also reaches the floating wrapper
1045
+ * through PopperLayer, and hiding the wrapper would cancel the content's exit transition.
1046
+ * Always mounted: closed floating content is visibility:hidden, closed static content is
1047
+ * display:none (it must leave the flow); `popup-surface` in ui.css adds the motion.
1048
+ */
1049
+ readonly contentStyle = $derived.by(() => {
1050
+ if (this.root.opts.open.current) return {};
1051
+ return this.#isStatic ? { display: 'none' } : { visibility: 'hidden' };
1052
+ });
1053
+
1054
+ readonly popperProps = {
1055
+ onInteractOutside: this.onInteractOutside,
1056
+ onEscapeKeydown: this.onEscapeKeydown,
1057
+ onOpenAutoFocus: this.onOpenAutoFocus,
1058
+ onCloseAutoFocus: this.onCloseAutoFocus,
1059
+ trapFocus: false,
1060
+ loop: false,
1061
+ onPlaced: () => {
1062
+ // onPlaced is also called when the menu is closed, so we need to check if the menu
1063
+ // is actually open to avoid setting positioning to true when the menu is closed
1064
+ if (this.root.opts.open.current) {
1065
+ this.root.contentIsPositioned = true;
1066
+ this.isPositioned = true;
1067
+ }
1068
+ },
1069
+ };
1070
+ }
1071
+
1072
+ interface SelectItemStateOpts
1073
+ extends
1074
+ WithRefOpts,
1075
+ ReadableBoxedValues<{
1076
+ value: string;
1077
+ disabled: boolean;
1078
+ label: string;
1079
+ onHighlight: () => void;
1080
+ onUnhighlight: () => void;
1081
+ }> {}
1082
+
1083
+ export class SelectItemState {
1084
+ static create(opts: SelectItemStateOpts) {
1085
+ return new SelectItemState(opts, getSelectRoot());
1086
+ }
1087
+ readonly opts: SelectItemStateOpts;
1088
+ readonly root: SelectRoot;
1089
+ readonly attachment: RefAttachment;
1090
+ readonly isSelected = $derived.by(() => this.root.includesItem(this.opts.value.current));
1091
+ readonly isHighlighted = $derived.by(() => this.root.highlightedValue === this.opts.value.current);
1092
+ prevHighlighted = $state<boolean | undefined>(undefined);
1093
+ mounted = $state(false);
1094
+
1095
+ constructor(opts: SelectItemStateOpts, root: SelectRoot) {
1096
+ this.opts = opts;
1097
+ this.root = root;
1098
+ this.attachment = attachRef(opts.ref);
1099
+
1100
+ let lastHighlighted: boolean | undefined = undefined;
1101
+ $effect.pre(() => {
1102
+ const current = this.isHighlighted;
1103
+ this.prevHighlighted = lastHighlighted;
1104
+ lastHighlighted = current;
1105
+ });
1106
+
1107
+ $effect(() => {
1108
+ this.isHighlighted;
1109
+ this.prevHighlighted;
1110
+ untrack(() => {
1111
+ if (this.isHighlighted) {
1112
+ this.opts.onHighlight.current();
1113
+ } else if (this.prevHighlighted) {
1114
+ this.opts.onUnhighlight.current();
1115
+ }
1116
+ });
1117
+ });
1118
+
1119
+ $effect(() => {
1120
+ this.mounted;
1121
+ untrack(() => {
1122
+ if (!this.mounted) return;
1123
+ this.root.setInitialHighlightedNode();
1124
+ });
1125
+ });
1126
+
1127
+ this.onpointerdown = this.onpointerdown.bind(this);
1128
+ this.onpointerup = this.onpointerup.bind(this);
1129
+ this.onpointermove = this.onpointermove.bind(this);
1130
+ }
1131
+
1132
+ handleSelect() {
1133
+ if (this.opts.disabled.current) return;
1134
+ const isCurrentSelectedValue = this.opts.value.current === this.root.opts.value.current;
1135
+
1136
+ // if allowDeselect is false and the item is already selected and we're not in a
1137
+ // multi select, do nothing and close the menu
1138
+ if (!this.root.opts.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {
1139
+ this.root.handleClose();
1140
+ return;
1141
+ }
1142
+
1143
+ // otherwise, toggle the item and if we're not in a multi select, close the menu
1144
+ this.root.toggleItem(this.opts.value.current, this.opts.label.current);
1145
+
1146
+ if (!this.root.isMulti && !isCurrentSelectedValue) {
1147
+ this.root.handleClose();
1148
+ }
1149
+ }
1150
+
1151
+ snippetProps = $derived.by(() => ({
1152
+ selected: this.isSelected,
1153
+ highlighted: this.isHighlighted,
1154
+ }));
1155
+
1156
+ onpointerdown(e: BitsPointerEvent) {
1157
+ // prevent focus from leaving the input/select trigger
1158
+ e.preventDefault();
1159
+ }
1160
+
1161
+ /**
1162
+ * Using `pointerup` instead of `click` allows power users to pointerdown
1163
+ * the trigger, then release pointerup on an item to select it vs having to do
1164
+ * multiple clicks.
1165
+ */
1166
+ onpointerup(e: BitsPointerEvent) {
1167
+ if (e.defaultPrevented || !this.opts.ref.current) return;
1168
+ /**
1169
+ * For one reason or another, when it's a touch pointer and _not_ on IOS,
1170
+ * we need to listen for the immediate click event to handle the selection,
1171
+ * otherwise a click event will fire on the element _behind_ the item.
1172
+ */
1173
+ if (e.pointerType === 'touch' && !isIOS) {
1174
+ on(
1175
+ this.opts.ref.current,
1176
+ 'click',
1177
+ () => {
1178
+ this.handleSelect();
1179
+ // set highlighted node since we don't do it on `pointermove` events
1180
+ // for touch devices
1181
+ this.root.setHighlightedNode(this.opts.ref.current);
1182
+ },
1183
+ { once: true },
1184
+ );
1185
+ return;
1186
+ }
1187
+ e.preventDefault();
1188
+
1189
+ this.handleSelect();
1190
+ if (e.pointerType === 'touch') {
1191
+ // set highlighted node since we don't do it on `pointermove` events
1192
+ // for touch devices
1193
+ this.root.setHighlightedNode(this.opts.ref.current);
1194
+ }
1195
+ }
1196
+
1197
+ onpointermove(e: BitsPointerEvent) {
1198
+ /**
1199
+ * We don't want to highlight items on touch devices when scrolling,
1200
+ * as this is confusing behavior, so we return here and instead handle
1201
+ * the highlighting on the `pointerup` (or following `click`) event for
1202
+ * touch devices only.
1203
+ */
1204
+ if (e.pointerType === 'touch') return;
1205
+ if (this.root.highlightedNode !== this.opts.ref.current) {
1206
+ this.root.setHighlightedNode(this.opts.ref.current);
1207
+ }
1208
+ }
1209
+
1210
+ readonly props = $derived.by(
1211
+ () =>
1212
+ ({
1213
+ id: this.opts.id.current,
1214
+ role: 'option',
1215
+ 'aria-selected': this.root.includesItem(this.opts.value.current) ? 'true' : undefined,
1216
+ 'data-value': this.opts.value.current,
1217
+ 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled.current),
1218
+ 'data-highlighted': this.root.highlightedValue === this.opts.value.current && !this.opts.disabled.current ? '' : undefined,
1219
+ 'data-selected': this.root.includesItem(this.opts.value.current) ? '' : undefined,
1220
+ 'data-label': this.opts.label.current,
1221
+ [this.root.getBitsAttr('item')]: '',
1222
+ onpointermove: this.onpointermove,
1223
+ onpointerdown: this.onpointerdown,
1224
+ onpointerup: this.onpointerup,
1225
+ ...this.attachment,
1226
+ }) as const,
1227
+ );
1228
+ }
1229
+
1230
+ interface SelectGroupStateOpts extends WithRefOpts {}
1231
+
1232
+ export class SelectGroupState {
1233
+ static create(opts: SelectGroupStateOpts) {
1234
+ return setSelectGroup(new SelectGroupState(opts, getSelectRoot()));
1235
+ }
1236
+ readonly opts: SelectGroupStateOpts;
1237
+ readonly root: SelectBaseRootState;
1238
+ labelNode = $state<HTMLElement | null>(null);
1239
+ readonly attachment: RefAttachment;
1240
+
1241
+ constructor(opts: SelectGroupStateOpts, root: SelectBaseRootState) {
1242
+ this.opts = opts;
1243
+ this.root = root;
1244
+ this.attachment = attachRef(opts.ref);
1245
+ }
1246
+
1247
+ readonly props = $derived.by(
1248
+ () =>
1249
+ ({
1250
+ id: this.opts.id.current,
1251
+ role: 'group',
1252
+ [this.root.getBitsAttr('group')]: '',
1253
+ 'aria-labelledby': this.labelNode?.id ?? undefined,
1254
+ ...this.attachment,
1255
+ }) as const,
1256
+ );
1257
+ }
1258
+
1259
+ interface SelectGroupHeadingStateOpts extends WithRefOpts {}
1260
+
1261
+ export class SelectGroupHeadingState {
1262
+ static create(opts: SelectGroupHeadingStateOpts) {
1263
+ return new SelectGroupHeadingState(opts, getSelectGroup());
1264
+ }
1265
+ readonly opts: SelectGroupHeadingStateOpts;
1266
+ readonly group: SelectGroupState;
1267
+ readonly attachment: RefAttachment;
1268
+
1269
+ constructor(opts: SelectGroupHeadingStateOpts, group: SelectGroupState) {
1270
+ this.opts = opts;
1271
+ this.group = group;
1272
+ this.attachment = attachRef(opts.ref, (v) => (this.group.labelNode = v));
1273
+ }
1274
+
1275
+ readonly props = $derived.by(
1276
+ () =>
1277
+ ({
1278
+ id: this.opts.id.current,
1279
+ [this.group.root.getBitsAttr('group-label')]: '',
1280
+ ...this.attachment,
1281
+ }) as const,
1282
+ );
1283
+ }
1284
+
1285
+ interface SelectHiddenInputStateOpts extends ReadableBoxedValues<{
1286
+ value: string | undefined;
1287
+ }> {}
1288
+
1289
+ export class SelectHiddenInputState {
1290
+ static create(opts: SelectHiddenInputStateOpts) {
1291
+ return new SelectHiddenInputState(opts, getSelectRoot());
1292
+ }
1293
+ readonly opts: SelectHiddenInputStateOpts;
1294
+ readonly root: SelectBaseRootState;
1295
+ readonly shouldRender = $derived.by(() => this.root.opts.name.current !== '');
1296
+
1297
+ constructor(opts: SelectHiddenInputStateOpts, root: SelectBaseRootState) {
1298
+ this.opts = opts;
1299
+ this.root = root;
1300
+ this.onfocus = this.onfocus.bind(this);
1301
+ }
1302
+
1303
+ onfocus(e: BitsFocusEvent) {
1304
+ e.preventDefault();
1305
+
1306
+ if (!this.root.isCombobox) {
1307
+ this.root.triggerNode?.focus();
1308
+ } else {
1309
+ this.root.inputNode?.focus();
1310
+ }
1311
+ }
1312
+
1313
+ readonly props = $derived.by(
1314
+ () =>
1315
+ ({
1316
+ disabled: boolToTrueOrUndef(this.root.opts.disabled.current),
1317
+ required: boolToTrueOrUndef(this.root.opts.required.current),
1318
+ name: this.root.opts.name.current,
1319
+ value: this.opts.value.current,
1320
+ onfocus: this.onfocus,
1321
+ }) as const,
1322
+ );
1323
+ }
1324
+
1325
+ interface SelectViewportStateOpts extends WithRefOpts {}
1326
+
1327
+ export class SelectViewportState {
1328
+ static create(opts: SelectViewportStateOpts) {
1329
+ return new SelectViewportState(opts, getSelectContent());
1330
+ }
1331
+ readonly opts: SelectViewportStateOpts;
1332
+ readonly content: SelectContentState;
1333
+ readonly root: SelectBaseRootState;
1334
+ readonly attachment: RefAttachment;
1335
+ prevScrollTop = $state(0);
1336
+
1337
+ constructor(opts: SelectViewportStateOpts, content: SelectContentState) {
1338
+ this.opts = opts;
1339
+ this.content = content;
1340
+ this.root = content.root;
1341
+ this.attachment = attachRef(opts.ref, (v) => {
1342
+ this.root.viewportNode = v;
1343
+ });
1344
+ }
1345
+
1346
+ readonly props = $derived.by(
1347
+ () =>
1348
+ ({
1349
+ id: this.opts.id.current,
1350
+ role: 'presentation',
1351
+ [this.root.getBitsAttr('viewport')]: '',
1352
+ style: {
1353
+ // we use position: 'relative' here on the `viewport` so that when we call
1354
+ // `selectedItem.offsetTop` in calculations, the offset is relative to the viewport
1355
+ // (independent of the scrollUpButton).
1356
+ position: 'relative',
1357
+ flex: 1,
1358
+ overflow: 'auto',
1359
+ },
1360
+ ...this.attachment,
1361
+ }) as const,
1362
+ );
1363
+ }
1364
+
1365
+ interface SelectScrollButtonImplStateOpts
1366
+ extends
1367
+ WithRefOpts,
1368
+ ReadableBoxedValues<{
1369
+ delay: (tick: number) => number;
1370
+ }> {}
1371
+
1372
+ export class SelectScrollButtonImplState {
1373
+ readonly opts: SelectScrollButtonImplStateOpts;
1374
+ readonly content: SelectContentState;
1375
+ readonly root: SelectBaseRootState;
1376
+ readonly attachment: RefAttachment;
1377
+ autoScrollTimer: number | null = null;
1378
+ userScrollTimer = -1;
1379
+ isUserScrolling = false;
1380
+ onAutoScroll: () => void = () => {};
1381
+ mounted = $state(false);
1382
+
1383
+ constructor(opts: SelectScrollButtonImplStateOpts, content: SelectContentState) {
1384
+ this.opts = opts;
1385
+ this.content = content;
1386
+ this.root = content.root;
1387
+ this.attachment = attachRef(opts.ref);
1388
+
1389
+ $effect(() => {
1390
+ this.mounted;
1391
+ untrack(() => {
1392
+ if (!this.mounted) {
1393
+ this.isUserScrolling = false;
1394
+ return;
1395
+ }
1396
+ if (this.isUserScrolling) return;
1397
+ });
1398
+ });
1399
+
1400
+ $effect(() => {
1401
+ if (this.mounted) return;
1402
+ this.clearAutoScrollInterval();
1403
+ });
1404
+
1405
+ this.onpointerdown = this.onpointerdown.bind(this);
1406
+ this.onpointermove = this.onpointermove.bind(this);
1407
+ this.onpointerleave = this.onpointerleave.bind(this);
1408
+ }
1409
+
1410
+ handleUserScroll() {
1411
+ this.content.domContext.clearTimeout(this.userScrollTimer);
1412
+ this.isUserScrolling = true;
1413
+ this.userScrollTimer = this.content.domContext.setTimeout(() => {
1414
+ this.isUserScrolling = false;
1415
+ }, 200);
1416
+ }
1417
+
1418
+ clearAutoScrollInterval() {
1419
+ if (this.autoScrollTimer === null) return;
1420
+ this.content.domContext.clearTimeout(this.autoScrollTimer);
1421
+ this.autoScrollTimer = null;
1422
+ }
1423
+
1424
+ onpointerdown(_: BitsPointerEvent) {
1425
+ if (this.autoScrollTimer !== null) return;
1426
+ const autoScroll = (tick: number) => {
1427
+ this.onAutoScroll();
1428
+ this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(tick + 1), this.opts.delay.current(tick));
1429
+ };
1430
+ this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(1), this.opts.delay.current(0));
1431
+ }
1432
+
1433
+ onpointermove(e: BitsPointerEvent) {
1434
+ this.onpointerdown(e);
1435
+ }
1436
+
1437
+ onpointerleave(_: BitsPointerEvent) {
1438
+ this.clearAutoScrollInterval();
1439
+ }
1440
+
1441
+ readonly props = $derived.by(
1442
+ () =>
1443
+ ({
1444
+ id: this.opts.id.current,
1445
+ 'aria-hidden': boolToStrTrueOrUndef(true),
1446
+ style: {
1447
+ flexShrink: 0,
1448
+ },
1449
+ onpointerdown: this.onpointerdown,
1450
+ onpointermove: this.onpointermove,
1451
+ onpointerleave: this.onpointerleave,
1452
+ ...this.attachment,
1453
+ }) as const,
1454
+ );
1455
+ }
1456
+
1457
+ export class SelectScrollDownButtonState {
1458
+ static create(opts: SelectScrollButtonImplStateOpts) {
1459
+ return new SelectScrollDownButtonState(new SelectScrollButtonImplState(opts, getSelectContent()));
1460
+ }
1461
+ readonly scrollButtonState: SelectScrollButtonImplState;
1462
+ readonly content: SelectContentState;
1463
+ readonly root: SelectBaseRootState;
1464
+ canScrollDown = $state(false);
1465
+ scrollIntoViewTimer: ReturnType<typeof globalThis.setTimeout> | null = null;
1466
+
1467
+ constructor(scrollButtonState: SelectScrollButtonImplState) {
1468
+ this.scrollButtonState = scrollButtonState;
1469
+ this.content = scrollButtonState.content;
1470
+ this.root = scrollButtonState.root;
1471
+ this.scrollButtonState.onAutoScroll = this.handleAutoScroll;
1472
+
1473
+ $effect(() => {
1474
+ this.root.viewportNode;
1475
+ this.content.isPositioned;
1476
+ return untrack(() => {
1477
+ if (!this.root.viewportNode || !this.content.isPositioned) return;
1478
+ this.handleScroll(true);
1479
+
1480
+ return on(this.root.viewportNode, 'scroll', () => this.handleScroll());
1481
+ });
1482
+ });
1483
+
1484
+ /**
1485
+ * If the input value changes, this means that the filtered items may have changed,
1486
+ * so we need to re-evaluate the scroll-ability of the list.
1487
+ */
1488
+ $effect(() => {
1489
+ this.root.opts.inputValue.current;
1490
+ this.root.viewportNode;
1491
+ this.content.isPositioned;
1492
+ untrack(() => {
1493
+ if (!this.root.viewportNode || !this.content.isPositioned) return;
1494
+ this.handleScroll(true);
1495
+ });
1496
+ });
1497
+
1498
+ $effect(() => {
1499
+ this.scrollButtonState.mounted;
1500
+ untrack(() => {
1501
+ if (!this.scrollButtonState.mounted) return;
1502
+ if (this.scrollIntoViewTimer) {
1503
+ clearTimeout(this.scrollIntoViewTimer);
1504
+ }
1505
+ this.scrollIntoViewTimer = setTimeout(() => {
1506
+ const activeItem = this.root.highlightedNode;
1507
+ if (!activeItem) return;
1508
+ this.root.scrollHighlightedNodeIntoView(activeItem);
1509
+ }, 5);
1510
+ });
1511
+ });
1512
+ }
1513
+ /**
1514
+ * @param manual - if true, it means the function was invoked manually outside of an event
1515
+ * listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
1516
+ */
1517
+ handleScroll = (manual = false) => {
1518
+ if (!manual) {
1519
+ this.scrollButtonState.handleUserScroll();
1520
+ }
1521
+ if (!this.root.viewportNode) return;
1522
+ const maxScroll = this.root.viewportNode.scrollHeight - this.root.viewportNode.clientHeight;
1523
+ const paddingTop = Number.parseInt(getComputedStyle(this.root.viewportNode).paddingTop, 10);
1524
+
1525
+ this.canScrollDown = Math.ceil(this.root.viewportNode.scrollTop) < maxScroll - paddingTop;
1526
+ };
1527
+
1528
+ handleAutoScroll = () => {
1529
+ const viewport = this.root.viewportNode;
1530
+ const selectedItem = this.root.highlightedNode;
1531
+ if (!viewport || !selectedItem) return;
1532
+ viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
1533
+ };
1534
+
1535
+ readonly props = $derived.by(
1536
+ () =>
1537
+ ({
1538
+ ...this.scrollButtonState.props,
1539
+ [this.root.getBitsAttr('scroll-down-button')]: '',
1540
+ }) as const,
1541
+ );
1542
+ }
1543
+
1544
+ export class SelectScrollUpButtonState {
1545
+ static create(opts: SelectScrollButtonImplStateOpts) {
1546
+ return new SelectScrollUpButtonState(new SelectScrollButtonImplState(opts, getSelectContent()));
1547
+ }
1548
+ readonly scrollButtonState: SelectScrollButtonImplState;
1549
+ readonly content: SelectContentState;
1550
+ readonly root: SelectBaseRootState;
1551
+ canScrollUp = $state(false);
1552
+
1553
+ constructor(scrollButtonState: SelectScrollButtonImplState) {
1554
+ this.scrollButtonState = scrollButtonState;
1555
+ this.content = scrollButtonState.content;
1556
+ this.root = scrollButtonState.root;
1557
+ this.scrollButtonState.onAutoScroll = this.handleAutoScroll;
1558
+
1559
+ $effect(() => {
1560
+ this.root.viewportNode;
1561
+ this.content.isPositioned;
1562
+ return untrack(() => {
1563
+ if (!this.root.viewportNode || !this.content.isPositioned) return;
1564
+
1565
+ this.handleScroll(true);
1566
+ return on(this.root.viewportNode, 'scroll', () => this.handleScroll());
1567
+ });
1568
+ });
1569
+ }
1570
+
1571
+ /**
1572
+ * @param manual - if true, it means the function was invoked manually outside of an event
1573
+ * listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
1574
+ */
1575
+ handleScroll = (manual = false) => {
1576
+ if (!manual) {
1577
+ this.scrollButtonState.handleUserScroll();
1578
+ }
1579
+ if (!this.root.viewportNode) return;
1580
+ const paddingTop = Number.parseInt(getComputedStyle(this.root.viewportNode).paddingTop, 10);
1581
+ this.canScrollUp = this.root.viewportNode.scrollTop - paddingTop > 0.1;
1582
+ };
1583
+
1584
+ handleAutoScroll = () => {
1585
+ if (!this.root.viewportNode || !this.root.highlightedNode) return;
1586
+ this.root.viewportNode.scrollTop = this.root.viewportNode.scrollTop - this.root.highlightedNode.offsetHeight;
1587
+ };
1588
+
1589
+ readonly props = $derived.by(
1590
+ () =>
1591
+ ({
1592
+ ...this.scrollButtonState.props,
1593
+ [this.root.getBitsAttr('scroll-up-button')]: '',
1594
+ }) as const,
1595
+ );
1596
+ }