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,1599 @@
1
+ import {
2
+ attachRef,
3
+ DOMContext,
4
+ getWindow,
5
+ type ReadableBoxedValues,
6
+ type WritableBoxedValues,
7
+ simpleBox,
8
+ boxWith,
9
+ type ReadableBox,
10
+ } from '../../internal/tools/index.js';
11
+ import { mergeProps } from '../../internal/merge-props.js';
12
+ import { createContext, onDestroy, tick, untrack } from 'svelte';
13
+ import { SUB_OPEN_KEYS, getCheckedState, isMouseEvent } from './utils.js';
14
+ import { focusFirst } from '../../internal/focus.js';
15
+ import type {
16
+ AnyFn,
17
+ BitsFocusEvent,
18
+ BitsKeyboardEvent,
19
+ BitsMouseEvent,
20
+ BitsPointerEvent,
21
+ OnChangeFn,
22
+ RefAttachment,
23
+ WithRefOpts,
24
+ } from '../../internal/types.js';
25
+ import { isElement, isElementOrSVGElement, isHTMLElement } from '../../internal/tools/utils/dom.js';
26
+ import { kbd, FIRST_LAST_KEYS, LAST_KEYS, SELECTION_KEYS } from '../../internal/kbd.js';
27
+ import {
28
+ createBitsAttrs,
29
+ getAriaChecked,
30
+ boolToStr,
31
+ getDataOpenClosed,
32
+ boolToEmptyStrOrUndef,
33
+ } from '../../internal/attrs.js';
34
+ import type { Direction } from '../../internal/index.js';
35
+ import { getTabbableFrom, isTabbable } from '../../internal/tabbable.js';
36
+ import type { KeyboardEventHandler, PointerEventHandler, MouseEventHandler } from 'svelte/elements';
37
+ import { Typeahead, textContentOf } from '../../internal/typeahead.svelte.js';
38
+ import { RovingFocusGroup } from '../../internal/roving-focus-group.js';
39
+ import { useOpenChangeComplete } from '../../internal/animations-settled.svelte.js';
40
+ import { joinGroup } from '../../internal/group-value.svelte.js';
41
+ import { createEffectTimeout } from '../../internal/timeout-fn.svelte.js';
42
+ import { on } from 'svelte/events';
43
+ import {
44
+ AXIS_VERTICAL,
45
+ DIR_POS,
46
+ type Point,
47
+ type SideBits,
48
+ TARGET_CONTENT,
49
+ TARGET_NONE,
50
+ TARGET_TRIGGER,
51
+ type TargetBits,
52
+ flipSide,
53
+ getCorridorPolygon,
54
+ getSide,
55
+ isInsideRect,
56
+ isPointInPolygon,
57
+ } from '../../internal/hover-intent-geometry.js';
58
+
59
+ export const CONTEXT_MENU_TRIGGER_ATTR = 'data-context-menu-trigger';
60
+ export const CONTEXT_MENU_CONTENT_ATTR = 'data-context-menu-content';
61
+
62
+ const [getMenuRoot, setMenuRoot] = createContext<MenuRootState>();
63
+ const [getMenuMenu, setMenuMenu] = createContext<MenuMenuState>();
64
+ const [getMenuContent, setMenuContent] = createContext<MenuContentState>();
65
+ const [getMenuGroup, setMenuGroup] = createContext<MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState>();
66
+ const [getMenuRadioGroup, setMenuRadioGroup] = createContext<MenuRadioGroupState>();
67
+ const [getMenuCheckboxGroup, setMenuCheckboxGroup, hasMenuCheckboxGroup] = createContext<MenuCheckboxGroupState>();
68
+
69
+ type MenuVariant = 'context-menu' | 'dropdown-menu' | 'menubar';
70
+
71
+ export interface MenuRootStateOpts extends ReadableBoxedValues<{
72
+ dir: Direction;
73
+ variant: MenuVariant;
74
+ }> {
75
+ onClose: AnyFn;
76
+ /** When closing, if this returns true, exit animations are skipped (instant unmount). */
77
+ shouldSkipExitAnimation?: () => boolean;
78
+ }
79
+
80
+ export const menuAttrs = createBitsAttrs({
81
+ component: 'menu',
82
+ parts: [
83
+ 'trigger',
84
+ 'content',
85
+ 'sub-trigger',
86
+ 'item',
87
+ 'group',
88
+ 'group-heading',
89
+ 'checkbox-group',
90
+ 'checkbox-item',
91
+ 'radio-group',
92
+ 'radio-item',
93
+ 'separator',
94
+ 'sub-content',
95
+ 'arrow',
96
+ ],
97
+ });
98
+
99
+ type Polygon = Point[];
100
+
101
+ // const SVG_NS = "http://www.w3.org/2000/svg";
102
+
103
+ export interface MenuSubmenuIntentOptions {
104
+ enabled: () => boolean;
105
+ triggerNode: () => HTMLElement | null;
106
+ contentNode: () => HTMLElement | null;
107
+ parentContentNode: () => HTMLElement | null;
108
+ subContentSelector: () => string;
109
+ onIntentExit: (pointerPoint: Point | null) => void;
110
+ setIsPointerInTransit: (value: boolean) => void;
111
+ }
112
+
113
+ export class MenuSubmenuIntent {
114
+ readonly #opts: MenuSubmenuIntentOptions;
115
+ #cleanupDocMove: AnyFn | null = null;
116
+ // Every `#active = false` write stops this timer first, so the callback only ever fires while active.
117
+ #fallbackTimer = createEffectTimeout(() => this.#intentExit(), () => 500);
118
+ #disengageTimer = createEffectTimeout(() => this.#opts.setIsPointerInTransit(false), () => 100);
119
+ #active = false;
120
+ #target: TargetBits = TARGET_NONE;
121
+ #apex: Point | null = null;
122
+ #pointerPoint: Point | null = null;
123
+ #launchPoint: Point | null = null;
124
+
125
+ constructor(opts: MenuSubmenuIntentOptions) {
126
+ this.#opts = opts;
127
+
128
+ $effect(() => {
129
+ const triggerNode = opts.triggerNode();
130
+ const contentNode = opts.contentNode();
131
+ const enabled = opts.enabled();
132
+ return untrack(() => {
133
+ this.#reset();
134
+ if (!triggerNode || !contentNode || !enabled) return;
135
+
136
+ const onTriggerMove = (e: PointerEvent) => {
137
+ if (!isMouseEvent(e)) return;
138
+ this.#launchPoint = [e.clientX, e.clientY];
139
+ if (!this.#active) this.#preview(e, TARGET_CONTENT);
140
+ };
141
+
142
+ const onTriggerLeave = (e: PointerEvent) => {
143
+ if (!isMouseEvent(e)) return;
144
+ this.#engage(e, TARGET_CONTENT);
145
+ };
146
+
147
+ const onContentMove = (e: PointerEvent) => {
148
+ if (!isMouseEvent(e)) return;
149
+ if (!this.#active) this.#preview(e, TARGET_TRIGGER);
150
+ };
151
+
152
+ const onContentLeave = (e: PointerEvent) => {
153
+ if (!isMouseEvent(e)) return;
154
+ if (isElement(e.relatedTarget)) {
155
+ const selector = this.#opts.subContentSelector();
156
+ const matchedSubContent = e.relatedTarget.closest(selector);
157
+ if (matchedSubContent && matchedSubContent !== contentNode && matchedSubContent.id) {
158
+ const isChild = !!contentNode.querySelector(`[aria-controls="${matchedSubContent.id}"]`);
159
+ if (isChild) {
160
+ return;
161
+ }
162
+ }
163
+ }
164
+ this.#engage(e, TARGET_TRIGGER);
165
+ };
166
+
167
+ const onTriggerEnter = (e: PointerEvent) => {
168
+ if (!isMouseEvent(e)) return;
169
+ this.#disengage();
170
+ };
171
+
172
+ const onContentEnter = (e: PointerEvent) => {
173
+ if (!isMouseEvent(e)) return;
174
+ this.#disengage();
175
+ };
176
+
177
+ const cleanup = [
178
+ on(triggerNode, 'pointermove', onTriggerMove),
179
+ on(triggerNode, 'pointerleave', onTriggerLeave),
180
+ on(triggerNode, 'pointerenter', onTriggerEnter),
181
+ on(contentNode, 'pointermove', onContentMove),
182
+ on(contentNode, 'pointerleave', onContentLeave),
183
+ on(contentNode, 'pointerenter', onContentEnter),
184
+ ];
185
+
186
+ return () => {
187
+ cleanup.forEach((remove) => remove());
188
+ this.#reset();
189
+ };
190
+ });
191
+ });
192
+
193
+ onDestroy(() => {
194
+ this.#reset();
195
+ });
196
+ }
197
+
198
+ #parentTargetRect(): DOMRect | null {
199
+ const parent = this.#opts.parentContentNode();
200
+ if (parent) return parent.getBoundingClientRect();
201
+ return this.#opts.triggerNode()?.getBoundingClientRect() ?? null;
202
+ }
203
+
204
+ #computePolygons(
205
+ pointerPt: Point,
206
+ target: TargetBits,
207
+ ): {
208
+ corridor: Polygon;
209
+ intent: Polygon;
210
+ targetRect: DOMRect;
211
+ side: SideBits;
212
+ } | null {
213
+ const triggerNode = this.#opts.triggerNode();
214
+ const contentNode = this.#opts.contentNode();
215
+ if (!triggerNode || !contentNode) return null;
216
+
217
+ const triggerRect = triggerNode.getBoundingClientRect();
218
+ const contentRect = contentNode.getBoundingClientRect();
219
+ const side = getSide(triggerRect, contentRect);
220
+
221
+ let apex: Point;
222
+ let targetRect: DOMRect;
223
+
224
+ let sourceRect: DOMRect | undefined;
225
+
226
+ if (target & TARGET_CONTENT) {
227
+ apex = this.#active ? (this.#apex ?? pointerPt) : pointerPt;
228
+ targetRect = contentRect;
229
+ } else {
230
+ apex = this.#launchPoint ?? pointerPt;
231
+ targetRect = this.#parentTargetRect() ?? triggerRect;
232
+ sourceRect = contentRect;
233
+ }
234
+
235
+ this.#apex = apex;
236
+
237
+ return {
238
+ // this file's corridor has always used a fixed buffer of 2 (distinct from
239
+ // SafePolygon's configurable buffer), preserved explicitly here
240
+ corridor: getCorridorPolygon(triggerRect, contentRect, side, 2),
241
+ intent: getIntentPolygon(apex, targetRect, side, target, sourceRect),
242
+ targetRect,
243
+ side,
244
+ };
245
+ }
246
+
247
+ #isInSafeZone(pt: Point, corridor: Polygon, intent: Polygon): boolean {
248
+ return isPointInPolygon(pt, corridor) || isPointInPolygon(pt, intent);
249
+ }
250
+
251
+ #preview(e: PointerEvent, target: TargetBits) {
252
+ const pt: Point = [e.clientX, e.clientY];
253
+ const geo = this.#computePolygons(pt, target);
254
+ if (!geo) return;
255
+
256
+ this.#target = target;
257
+ this.#pointerPoint = pt;
258
+ }
259
+
260
+ #engage(e: PointerEvent, target: TargetBits) {
261
+ if (!this.#opts.enabled()) return;
262
+
263
+ const triggerNode = this.#opts.triggerNode();
264
+ const contentNode = this.#opts.contentNode();
265
+ if (!triggerNode || !contentNode) return;
266
+
267
+ const related = e.relatedTarget;
268
+ if (isElement(related)) {
269
+ if (target & TARGET_CONTENT && contentNode.contains(related)) return;
270
+ if (target & TARGET_TRIGGER && triggerNode.contains(related)) return;
271
+ }
272
+
273
+ const pt: Point = [e.clientX, e.clientY];
274
+
275
+ const geo = this.#computePolygons(pt, target);
276
+ if (!geo) return;
277
+
278
+ if (!isInsideRect(pt, geo.targetRect) && !this.#isInSafeZone(pt, geo.corridor, geo.intent)) {
279
+ this.#clearVisuals();
280
+ return;
281
+ }
282
+
283
+ this.#active = true;
284
+ this.#target = target;
285
+ this.#pointerPoint = pt;
286
+
287
+ this.#opts.setIsPointerInTransit(true);
288
+ this.#attachDocMove();
289
+ this.#fallbackTimer.start();
290
+ }
291
+
292
+ #disengage() {
293
+ if (!this.#active) return;
294
+ const wasReturning = (this.#target & TARGET_TRIGGER) !== 0;
295
+ this.#detachDocMove();
296
+ this.#fallbackTimer.stop();
297
+ this.#active = false;
298
+ this.#clearVisuals();
299
+
300
+ if (wasReturning) {
301
+ this.#disengageTimer.start();
302
+ } else {
303
+ this.#opts.setIsPointerInTransit(false);
304
+ }
305
+ }
306
+
307
+ #intentExit() {
308
+ const pointerPoint = this.#pointerPoint;
309
+ this.#detachDocMove();
310
+ this.#fallbackTimer.stop();
311
+ this.#disengageTimer.stop();
312
+ this.#active = false;
313
+ this.#opts.setIsPointerInTransit(false);
314
+ this.#clearVisuals();
315
+ this.#opts.onIntentExit(pointerPoint);
316
+ }
317
+
318
+ #reset() {
319
+ this.#detachDocMove();
320
+ this.#fallbackTimer.stop();
321
+ this.#disengageTimer.stop();
322
+ if (this.#active) this.#opts.setIsPointerInTransit(false);
323
+ this.#active = false;
324
+ this.#target = TARGET_NONE;
325
+ this.#apex = null;
326
+ this.#pointerPoint = null;
327
+ this.#launchPoint = null;
328
+ }
329
+
330
+ #isPointerInDescendantSubContent(pt: Point): boolean {
331
+ const contentNode = this.#opts.contentNode();
332
+ if (!contentNode) return false;
333
+ const doc = contentNode.ownerDocument;
334
+ const el = doc.elementFromPoint(pt[0], pt[1]);
335
+ if (!el) return false;
336
+ const selector = this.#opts.subContentSelector();
337
+ const subContent = el.closest(selector);
338
+ if (!subContent || subContent === contentNode) return false;
339
+ if (subContent.id) return !!contentNode.querySelector(`[aria-controls="${subContent.id}"]`);
340
+ return false;
341
+ }
342
+
343
+ #onDocMove = (e: PointerEvent) => {
344
+ if (!this.#active || !this.#target) return;
345
+ if (!isMouseEvent(e)) return;
346
+
347
+ const triggerNode = this.#opts.triggerNode();
348
+ const contentNode = this.#opts.contentNode();
349
+ if (!triggerNode || !contentNode) {
350
+ this.#intentExit();
351
+ return;
352
+ }
353
+
354
+ this.#fallbackTimer.stop();
355
+ const pt: Point = [e.clientX, e.clientY];
356
+ this.#pointerPoint = pt;
357
+
358
+ const triggerRect = triggerNode.getBoundingClientRect();
359
+ const contentRect = contentNode.getBoundingClientRect();
360
+ if (this.#target & TARGET_CONTENT && isInsideRect(pt, contentRect)) {
361
+ this.#disengage();
362
+ return;
363
+ }
364
+ if (this.#target & TARGET_TRIGGER && isInsideInsetRect(pt, triggerRect, 4)) {
365
+ this.#disengage();
366
+ return;
367
+ }
368
+
369
+ if (this.#isPointerInDescendantSubContent(pt)) {
370
+ this.#fallbackTimer.start();
371
+ return;
372
+ }
373
+
374
+ const geo = this.#computePolygons(pt, this.#target);
375
+ if (!geo) {
376
+ this.#intentExit();
377
+ return;
378
+ }
379
+
380
+ if (this.#isInSafeZone(pt, geo.corridor, geo.intent)) {
381
+ this.#fallbackTimer.start();
382
+ return;
383
+ }
384
+
385
+ this.#intentExit();
386
+ };
387
+
388
+ #attachDocMove() {
389
+ if (this.#cleanupDocMove) return;
390
+ const doc = (this.#opts.triggerNode() ?? this.#opts.contentNode())?.ownerDocument ?? document;
391
+ const remove = on(doc, 'pointermove', this.#onDocMove, { capture: true });
392
+ this.#cleanupDocMove = () => {
393
+ remove();
394
+ this.#cleanupDocMove = null;
395
+ };
396
+ }
397
+
398
+ #detachDocMove() {
399
+ this.#cleanupDocMove?.();
400
+ }
401
+
402
+ #clearVisuals() {
403
+ this.#target = TARGET_NONE;
404
+ this.#apex = null;
405
+ this.#pointerPoint = null;
406
+ }
407
+
408
+ }
409
+
410
+ function isInsideInsetRect(point: Point, rect: DOMRect, inset: number): boolean {
411
+ return point[0] >= rect.left + inset && point[0] <= rect.right - inset && point[1] >= rect.top + inset && point[1] <= rect.bottom - inset;
412
+ }
413
+
414
+ /**
415
+ * The submenu-chain "intent" safe zone: a precise triangular cone from the exit point to the
416
+ * target's near edge (optionally widened to also cover `sourceRect`, e.g. the content rect when
417
+ * aiming back at a parent trigger). Deliberately narrower than SafePolygon's hexagonal safe zone —
418
+ * densely-packed sibling menu items need more precision than a single floating trigger/content pair.
419
+ */
420
+ function getIntentPolygon(exitPoint: Point, targetRect: DOMRect, side: SideBits, target: TargetBits, sourceRect?: DOMRect): Polygon {
421
+ const edgeBuffer = 8;
422
+ const effectiveSide = target & TARGET_TRIGGER ? flipSide(side) : side;
423
+
424
+ const top = sourceRect ? Math.min(targetRect.top, sourceRect.top) - edgeBuffer : targetRect.top - edgeBuffer;
425
+ const bottom = sourceRect ? Math.max(targetRect.bottom, sourceRect.bottom) + edgeBuffer : targetRect.bottom + edgeBuffer;
426
+ const left = sourceRect ? Math.min(targetRect.left, sourceRect.left) - edgeBuffer : targetRect.left - edgeBuffer;
427
+ const right = sourceRect ? Math.max(targetRect.right, sourceRect.right) + edgeBuffer : targetRect.right + edgeBuffer;
428
+
429
+ const dir = effectiveSide & DIR_POS;
430
+
431
+ if (effectiveSide & AXIS_VERTICAL) {
432
+ // dir 0 = top (target is below the apex; cone opens toward targetRect.bottom), 1 = bottom
433
+ const edgeY = dir === 0 ? targetRect.bottom : targetRect.top;
434
+ return [exitPoint, [left, edgeY], [right, edgeY]];
435
+ }
436
+ // dir 0 = left (target is to the right; cone opens toward targetRect.right), 1 = right
437
+ const edgeX = dir === 0 ? targetRect.right : targetRect.left;
438
+ return [exitPoint, [edgeX, top], [edgeX, bottom]];
439
+ }
440
+
441
+ export class MenuRootState {
442
+ static create(opts: MenuRootStateOpts) {
443
+ const root = new MenuRootState(opts);
444
+ return setMenuRoot(root);
445
+ }
446
+
447
+ readonly opts: MenuRootStateOpts;
448
+ isKeyboard = $state(false);
449
+ ignoreCloseAutoFocus = $state(false);
450
+ isPointerInTransit = $state(false);
451
+
452
+ constructor(opts: MenuRootStateOpts) {
453
+ this.opts = opts;
454
+ }
455
+
456
+ getBitsAttr: typeof menuAttrs.getAttr = (part) => {
457
+ return menuAttrs.getAttr(part, this.opts.variant.current);
458
+ };
459
+ }
460
+
461
+ interface MenuMenuStateOpts
462
+ extends
463
+ WritableBoxedValues<{
464
+ open: boolean;
465
+ }>,
466
+ ReadableBoxedValues<{
467
+ onOpenChangeComplete: OnChangeFn<boolean>;
468
+ }> {}
469
+
470
+ export class MenuMenuState {
471
+ static create(opts: MenuMenuStateOpts, root: MenuRootState) {
472
+ return setMenuMenu(new MenuMenuState(opts, root, null));
473
+ }
474
+
475
+ readonly opts: MenuMenuStateOpts;
476
+ readonly root: MenuRootState;
477
+ readonly parentMenu: MenuMenuState | null;
478
+ contentId = boxWith<string>(() => '');
479
+ contentNode = $state<HTMLElement | null>(null);
480
+ triggerNode = $state<HTMLElement | null>(null);
481
+ readonly #completion: { readonly pending: boolean };
482
+ /** Rendered: open, or closing with the exit still settling — the window the popper layer's scroll lock covers. */
483
+ readonly present = $derived.by(() => this.opts.open.current || this.#completion.pending);
484
+ /** Registered by the mounted content: keyboard-driven opens land focus on the first item. */
485
+ focusFirstItem: (() => void) | null = null;
486
+
487
+ constructor(opts: MenuMenuStateOpts, root: MenuRootState, parentMenu: MenuMenuState | null) {
488
+ this.opts = opts;
489
+ this.root = root;
490
+ this.parentMenu = parentMenu;
491
+
492
+ // Content stays mounted through its exit transition (CSS owns the motion; the closed state
493
+ // is the content's `contentStyle`); completion is settle-based on the content node.
494
+ this.#completion = useOpenChangeComplete(
495
+ () => this.opts.open.current,
496
+ () => this.contentNode,
497
+ (open) => this.opts.onOpenChangeComplete.current(open),
498
+ (open) => !open && this.shouldSkipExitAnimation(),
499
+ );
500
+
501
+ if (parentMenu) {
502
+ $effect(() => {
503
+ parentMenu.opts.open.current;
504
+ untrack(() => {
505
+ if (parentMenu.opts.open.current) return;
506
+ this.opts.open.current = false;
507
+ });
508
+ });
509
+ }
510
+ }
511
+
512
+ /** A menubar swap between top-level menus closes the outgoing one without its exit transition. */
513
+ shouldSkipExitAnimation() {
514
+ if (this.root.opts.variant.current !== 'menubar' || this.parentMenu !== null) return false;
515
+ return this.root.opts.shouldSkipExitAnimation?.() ?? false;
516
+ }
517
+
518
+ toggleOpen() {
519
+ this.opts.open.current = !this.opts.open.current;
520
+ }
521
+
522
+ onOpen() {
523
+ this.opts.open.current = true;
524
+ }
525
+
526
+ onClose() {
527
+ this.opts.open.current = false;
528
+ }
529
+ }
530
+
531
+ interface MenuContentStateOpts
532
+ extends
533
+ WithRefOpts,
534
+ ReadableBoxedValues<{
535
+ loop: boolean;
536
+ onCloseAutoFocus: (event: Event) => void;
537
+ }> {
538
+ isSub?: boolean;
539
+ /** Rendered in flow (ContentStatic) rather than in a floating wrapper. */
540
+ isStatic?: boolean;
541
+ }
542
+
543
+ export class MenuContentState {
544
+ static create(opts: MenuContentStateOpts) {
545
+ return setMenuContent(new MenuContentState(opts, getMenuMenu()));
546
+ }
547
+
548
+ readonly opts: MenuContentStateOpts;
549
+ readonly parentMenu: MenuMenuState;
550
+ readonly rovingFocusGroup: RovingFocusGroup;
551
+ readonly domContext: DOMContext;
552
+ readonly attachment: RefAttachment;
553
+ readonly #typeahead: Typeahead<HTMLElement>;
554
+ #isSub: boolean;
555
+ #isStatic: boolean;
556
+ /** Items of a closed (still mounted) sub-content are hidden, not candidates. */
557
+ readonly #candidateSelector: string;
558
+
559
+ constructor(opts: MenuContentStateOpts, parentMenu: MenuMenuState) {
560
+ this.opts = opts;
561
+ this.parentMenu = parentMenu;
562
+ this.domContext = new DOMContext(opts.ref);
563
+ this.attachment = attachRef(this.opts.ref, (v) => {
564
+ if (this.parentMenu.contentNode !== v) {
565
+ this.parentMenu.contentNode = v;
566
+ }
567
+ });
568
+
569
+ parentMenu.contentId = opts.id;
570
+
571
+ this.#isSub = opts.isSub ?? false;
572
+ this.#isStatic = opts.isStatic ?? false;
573
+ const root = parentMenu.root;
574
+ this.#candidateSelector = `[${root.getBitsAttr('item')}]:not([data-disabled]):not([${root.getBitsAttr('sub-content')}][data-state='closed'] *)`;
575
+ this.onkeydown = this.onkeydown.bind(this);
576
+ this.onblur = this.onblur.bind(this);
577
+ this.onfocus = this.onfocus.bind(this);
578
+ this.handleInteractOutside = this.handleInteractOutside.bind(this);
579
+
580
+ new MenuSubmenuIntent({
581
+ contentNode: () => this.parentMenu.contentNode,
582
+ triggerNode: () => this.parentMenu.triggerNode,
583
+ parentContentNode: () => this.parentMenu.parentMenu?.contentNode ?? null,
584
+ subContentSelector: () => `[${this.parentMenu.root.getBitsAttr('sub-content')}]`,
585
+ enabled: () =>
586
+ this.parentMenu.opts.open.current &&
587
+ Boolean(this.parentMenu.triggerNode?.hasAttribute(this.parentMenu.root.getBitsAttr('sub-trigger'))),
588
+ onIntentExit: (pointerPoint) => {
589
+ this.parentMenu.opts.open.current = false;
590
+ this.#dispatchPointerMoveToHoveredSubTrigger(pointerPoint);
591
+ },
592
+ setIsPointerInTransit: (value) => {
593
+ this.parentMenu.root.isPointerInTransit = value;
594
+ },
595
+ });
596
+
597
+ this.#typeahead = new Typeahead({
598
+ getSearchText: textContentOf,
599
+ getCurrentCandidate: () => this.domContext.getActiveElement(),
600
+ onMatch: (node) => node.focus(),
601
+ getWindow: () => this.domContext.getWindow(),
602
+ });
603
+ this.rovingFocusGroup = new RovingFocusGroup({
604
+ rootNode: boxWith(() => this.parentMenu.contentNode),
605
+ candidateSelector: this.#candidateSelector,
606
+ loop: this.opts.loop,
607
+ orientation: boxWith(() => 'vertical'),
608
+ });
609
+
610
+ $effect(() => {
611
+ this.parentMenu.focusFirstItem = () => {
612
+ tick().then(() => {
613
+ if (!this.parentMenu.root.isKeyboard) return;
614
+ this.rovingFocusGroup.focusFirstCandidate();
615
+ });
616
+ };
617
+ return () => {
618
+ this.parentMenu.focusFirstItem = null;
619
+ };
620
+ });
621
+
622
+ $effect(() => {
623
+ if (!this.parentMenu.opts.open.current) this.#typeahead.reset();
624
+ });
625
+ }
626
+
627
+ #getCandidateNodes() {
628
+ const node = this.parentMenu.contentNode;
629
+ if (!node) return [];
630
+ return Array.from(node.querySelectorAll<HTMLElement>(this.#candidateSelector));
631
+ }
632
+
633
+ #isPointerMovingToSubmenu() {
634
+ return this.parentMenu.root.isPointerInTransit;
635
+ }
636
+
637
+ #dispatchPointerMoveToHoveredSubTrigger(pointerPoint: Point | null) {
638
+ if (!pointerPoint) return;
639
+ const parentContentNode = this.parentMenu.parentMenu?.contentNode;
640
+ if (!parentContentNode) return;
641
+ const hoveredNode = this.domContext.getDocument().elementFromPoint(pointerPoint[0], pointerPoint[1]);
642
+ if (!isElement(hoveredNode)) return;
643
+ const hoveredSubTrigger = hoveredNode.closest<HTMLElement>(`[${this.parentMenu.root.getBitsAttr('sub-trigger')}]`);
644
+ if (!hoveredSubTrigger || !parentContentNode.contains(hoveredSubTrigger)) return;
645
+ if (hoveredSubTrigger === this.parentMenu.triggerNode) return;
646
+ hoveredSubTrigger.dispatchEvent(
647
+ new PointerEvent('pointermove', {
648
+ bubbles: true,
649
+ cancelable: true,
650
+ pointerType: 'mouse',
651
+ clientX: pointerPoint[0],
652
+ clientY: pointerPoint[1],
653
+ }),
654
+ );
655
+ }
656
+
657
+ onCloseAutoFocus = (e: Event) => {
658
+ this.opts.onCloseAutoFocus.current?.(e);
659
+ if (e.defaultPrevented || this.#isSub) return;
660
+ if (this.parentMenu.root.ignoreCloseAutoFocus) {
661
+ e.preventDefault();
662
+ return;
663
+ }
664
+
665
+ if (this.parentMenu.triggerNode && isTabbable(this.parentMenu.triggerNode)) {
666
+ e.preventDefault();
667
+ this.parentMenu.triggerNode.focus();
668
+ }
669
+ };
670
+
671
+ handleTabKeyDown(e: BitsKeyboardEvent) {
672
+ /**
673
+ * We locate the root `menu`'s trigger by going up the tree until
674
+ * we find a menu that has no parent. This will allow us to focus the next
675
+ * tabbable element before/after the root trigger.
676
+ */
677
+ let rootMenu = this.parentMenu;
678
+ while (rootMenu.parentMenu !== null) {
679
+ rootMenu = rootMenu.parentMenu;
680
+ }
681
+ if (!rootMenu.triggerNode) return;
682
+ e.preventDefault();
683
+ const nodeToFocus = getTabbableFrom(rootMenu.triggerNode, e.shiftKey ? 'prev' : 'next');
684
+ if (nodeToFocus) {
685
+ /**
686
+ * We set a flag to ignore the `onCloseAutoFocus` event handler
687
+ * as well as the fallbacks inside the focus scope to prevent
688
+ * race conditions causing focus to fall back to the body even
689
+ * though we're trying to focus the next tabbable element.
690
+ */
691
+ this.parentMenu.root.ignoreCloseAutoFocus = true;
692
+ rootMenu.onClose();
693
+ tick().then(() => {
694
+ nodeToFocus.focus();
695
+ tick().then(() => {
696
+ this.parentMenu.root.ignoreCloseAutoFocus = false;
697
+ });
698
+ });
699
+ } else {
700
+ this.domContext.getDocument().body.focus();
701
+ }
702
+ }
703
+
704
+ onkeydown(e: BitsKeyboardEvent) {
705
+ if (e.defaultPrevented) return;
706
+ if (e.key === kbd.TAB) {
707
+ this.handleTabKeyDown(e);
708
+ return;
709
+ }
710
+
711
+ const target = e.target;
712
+ const currentTarget = e.currentTarget;
713
+ if (!isHTMLElement(target) || !isHTMLElement(currentTarget)) return;
714
+
715
+ const isKeydownInside = target.closest(`[${this.parentMenu.root.getBitsAttr('content')}]`)?.id === this.parentMenu.contentId.current;
716
+
717
+ const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;
718
+ const isCharacterKey = e.key.length === 1;
719
+
720
+ const kbdFocusedEl = this.rovingFocusGroup.handleKeydown(target, e);
721
+ if (kbdFocusedEl) return;
722
+
723
+ // prevent space from being considered with typeahead
724
+ if (e.code === 'Space') return;
725
+
726
+ const candidateNodes = this.#getCandidateNodes();
727
+
728
+ if (isKeydownInside) {
729
+ if (!isModifierKey && isCharacterKey) {
730
+ this.#typeahead.handleKey(e.key, candidateNodes);
731
+ }
732
+ }
733
+
734
+ // focus first/last based on key pressed
735
+ if ((e.target as HTMLElement)?.id !== this.parentMenu.contentId.current) return;
736
+
737
+ if (!FIRST_LAST_KEYS.includes(e.key)) return;
738
+ e.preventDefault();
739
+
740
+ if (LAST_KEYS.includes(e.key)) {
741
+ candidateNodes.reverse();
742
+ }
743
+ focusFirst(candidateNodes, () => this.domContext.getActiveElement());
744
+ }
745
+
746
+ get isTypingAhead() {
747
+ return this.#typeahead.search !== '';
748
+ }
749
+
750
+ onblur(e: BitsFocusEvent) {
751
+ if (!isElement(e.currentTarget)) return;
752
+ if (!isElement(e.target)) return;
753
+ // clear search buffer when leaving the menu
754
+ if (!e.currentTarget.contains?.(e.target)) this.#typeahead.reset();
755
+ }
756
+
757
+ onfocus(_: BitsFocusEvent) {
758
+ if (!this.parentMenu.root.isKeyboard) return;
759
+ tick().then(() => this.rovingFocusGroup.focusFirstCandidate());
760
+ }
761
+
762
+ onItemEnter() {
763
+ return this.#isPointerMovingToSubmenu();
764
+ }
765
+
766
+ onItemLeave(e: BitsPointerEvent) {
767
+ if (e.currentTarget.hasAttribute(this.parentMenu.root.getBitsAttr('sub-trigger'))) return;
768
+ if (this.#isPointerMovingToSubmenu() || this.parentMenu.root.isKeyboard) return;
769
+ const contentNode = this.parentMenu.contentNode;
770
+ contentNode?.focus({ preventScroll: true });
771
+ this.rovingFocusGroup.setCurrentTabStopId('');
772
+ }
773
+
774
+ onTriggerLeave() {
775
+ return this.#isPointerMovingToSubmenu();
776
+ }
777
+
778
+ handleInteractOutside(e: PointerEvent) {
779
+ if (!isElementOrSVGElement(e.target)) return;
780
+ const triggerId = this.parentMenu.triggerNode?.id;
781
+ if (e.target.id === triggerId) {
782
+ e.preventDefault();
783
+ return;
784
+ }
785
+ if (e.target.closest(`#${triggerId}`)) {
786
+ e.preventDefault();
787
+ return;
788
+ }
789
+ /**
790
+ * when the menu closes due to an outside pointer interaction (for example,
791
+ * clicking another dropdown trigger), avoid focusing this menu's trigger
792
+ * to prevent stealing focus from the new interaction target.
793
+ */
794
+ this.parentMenu.root.ignoreCloseAutoFocus = true;
795
+ tick().then(() => {
796
+ this.parentMenu.root.ignoreCloseAutoFocus = false;
797
+ });
798
+ }
799
+
800
+ readonly snippetProps = $derived.by(() => ({ open: this.parentMenu.opts.open.current }));
801
+
802
+ readonly props = $derived.by(
803
+ () =>
804
+ ({
805
+ id: this.opts.id.current,
806
+ role: 'menu',
807
+ 'aria-orientation': 'vertical' as const,
808
+ [this.parentMenu.root.getBitsAttr('content')]: '',
809
+ 'data-state': getDataOpenClosed(this.parentMenu.opts.open.current),
810
+ onkeydown: this.onkeydown,
811
+ onblur: this.onblur,
812
+ onfocus: this.onfocus,
813
+ dir: this.parentMenu.root.opts.dir.current,
814
+ // no `contain: layout` (upstream had it): layout containment makes this element the
815
+ // containing block of its fixed sub-content, shrinking the sub's anchor position area
816
+ // to this box; floating-ui compensated for containing blocks, CSS anchor cannot.
817
+ style: {
818
+ pointerEvents: 'auto',
819
+ },
820
+ ...this.attachment,
821
+ }) as const,
822
+ );
823
+
824
+ /**
825
+ * Inline style for the content element alone — `props.style` also reaches the floating wrapper
826
+ * through PopperLayer, and hiding the wrapper would cancel the content's exit transition.
827
+ * Always mounted: closed floating content is visibility:hidden (out of the a11y tree, not
828
+ * focusable, and transitionable in every engine — see `popup-surface` in ui.css); closed static
829
+ * content is display:none since it must leave the flow. The `popup-surface` utilities add the
830
+ * motion; a menubar swap zeroes the exit.
831
+ */
832
+ readonly contentStyle = $derived.by(() => {
833
+ if (this.parentMenu.opts.open.current) return {};
834
+ return {
835
+ ...(this.#isStatic ? { display: 'none' } : { visibility: 'hidden' }),
836
+ ...(this.parentMenu.shouldSkipExitAnimation() ? { transitionDuration: '0s', transitionDelay: '0s' } : {}),
837
+ };
838
+ });
839
+
840
+ readonly popperProps = {
841
+ onCloseAutoFocus: (e: Event) => this.onCloseAutoFocus(e),
842
+ };
843
+ }
844
+
845
+ interface MenuItemSharedStateOpts
846
+ extends
847
+ WithRefOpts,
848
+ ReadableBoxedValues<{
849
+ disabled: boolean;
850
+ }> {}
851
+
852
+ class MenuItemSharedState {
853
+ readonly opts: MenuItemSharedStateOpts;
854
+ readonly content: MenuContentState;
855
+ readonly attachment: RefAttachment;
856
+ #isFocused = $state(false);
857
+
858
+ constructor(opts: MenuItemSharedStateOpts, content: MenuContentState) {
859
+ this.opts = opts;
860
+ this.content = content;
861
+ this.attachment = attachRef(this.opts.ref);
862
+ this.onpointermove = this.onpointermove.bind(this);
863
+ this.onpointerleave = this.onpointerleave.bind(this);
864
+ this.onfocus = this.onfocus.bind(this);
865
+ this.onblur = this.onblur.bind(this);
866
+ }
867
+
868
+ onpointermove(e: BitsPointerEvent) {
869
+ if (e.defaultPrevented) return;
870
+ if (!isMouseEvent(e)) return;
871
+
872
+ if (this.opts.disabled.current) {
873
+ this.content.onItemLeave(e);
874
+ } else {
875
+ const defaultPrevented = this.content.onItemEnter();
876
+ if (defaultPrevented) return;
877
+ const item = e.currentTarget;
878
+ if (!isHTMLElement(item)) return;
879
+ item.focus({ preventScroll: true });
880
+ }
881
+ }
882
+
883
+ onpointerleave(e: BitsPointerEvent) {
884
+ if (e.defaultPrevented) return;
885
+ if (!isMouseEvent(e)) return;
886
+ this.content.onItemLeave(e);
887
+ }
888
+
889
+ onfocus(e: BitsFocusEvent) {
890
+ tick().then(() => {
891
+ if (e.defaultPrevented || this.opts.disabled.current) return;
892
+ this.#isFocused = true;
893
+ });
894
+ }
895
+
896
+ onblur(e: BitsFocusEvent) {
897
+ tick().then(() => {
898
+ if (e.defaultPrevented) return;
899
+ this.#isFocused = false;
900
+ });
901
+ }
902
+
903
+ readonly props = $derived.by(
904
+ () =>
905
+ ({
906
+ id: this.opts.id.current,
907
+ tabindex: -1,
908
+ role: 'menuitem',
909
+ 'aria-disabled': boolToStr(this.opts.disabled.current),
910
+ 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled.current),
911
+ 'data-highlighted': this.#isFocused ? '' : undefined,
912
+ [this.content.parentMenu.root.getBitsAttr('item')]: '',
913
+ //
914
+ onpointermove: this.onpointermove,
915
+ onpointerleave: this.onpointerleave,
916
+ onfocus: this.onfocus,
917
+ onblur: this.onblur,
918
+ ...this.attachment,
919
+ }) as const,
920
+ );
921
+ }
922
+
923
+ type MenuItemCombinedProps = MenuItemSharedStateOpts & MenuItemStateOpts;
924
+
925
+ interface MenuItemStateOpts extends ReadableBoxedValues<{
926
+ onSelect: AnyFn;
927
+ closeOnSelect: boolean;
928
+ }> {}
929
+
930
+ export class MenuItemState {
931
+ static create(opts: MenuItemCombinedProps) {
932
+ const item = new MenuItemSharedState(opts, getMenuContent());
933
+ return new MenuItemState(opts, item);
934
+ }
935
+
936
+ readonly opts: MenuItemStateOpts;
937
+ readonly item: MenuItemSharedState;
938
+ readonly root: MenuRootState;
939
+ #isPointerDown = false;
940
+
941
+ constructor(opts: MenuItemStateOpts, item: MenuItemSharedState) {
942
+ this.opts = opts;
943
+ this.item = item;
944
+ this.root = item.content.parentMenu.root;
945
+
946
+ this.onkeydown = this.onkeydown.bind(this);
947
+ this.onclick = this.onclick.bind(this);
948
+ this.onpointerdown = this.onpointerdown.bind(this);
949
+ this.onpointerup = this.onpointerup.bind(this);
950
+ }
951
+
952
+ #handleSelect() {
953
+ if (this.item.opts.disabled.current) return;
954
+ const selectEvent = new CustomEvent('menuitemselect', { bubbles: true, cancelable: true });
955
+ this.opts.onSelect.current(selectEvent);
956
+ if (selectEvent.defaultPrevented) {
957
+ this.item.content.parentMenu.root.isKeyboard = false;
958
+ return;
959
+ }
960
+ if (this.opts.closeOnSelect.current) {
961
+ this.item.content.parentMenu.root.opts.onClose();
962
+ }
963
+ }
964
+
965
+ onkeydown(e: BitsKeyboardEvent) {
966
+ const isTypingAhead = this.item.content.isTypingAhead;
967
+ if (this.item.opts.disabled.current || (isTypingAhead && e.key === kbd.SPACE)) return;
968
+ if (SELECTION_KEYS.includes(e.key)) {
969
+ if (!isHTMLElement(e.currentTarget)) return;
970
+ e.currentTarget.click();
971
+ /**
972
+ * We prevent default browser behavior for selection keys as they should trigger
973
+ * a selection only:
974
+ * - prevents space from scrolling the page.
975
+ * - if keydown causes focus to move, prevents keydown from firing on the new target.
976
+ */
977
+ e.preventDefault();
978
+ }
979
+ }
980
+
981
+ onclick(_: BitsMouseEvent) {
982
+ if (this.item.opts.disabled.current) return;
983
+ this.#handleSelect();
984
+ }
985
+
986
+ onpointerup(e: BitsPointerEvent) {
987
+ if (e.defaultPrevented) return;
988
+ if (!this.#isPointerDown) {
989
+ if (!isHTMLElement(e.currentTarget)) return;
990
+ e.currentTarget?.click();
991
+ }
992
+ }
993
+
994
+ onpointerdown(_: BitsPointerEvent) {
995
+ this.#isPointerDown = true;
996
+ }
997
+
998
+ readonly props = $derived.by(() =>
999
+ mergeProps(this.item.props, {
1000
+ onclick: this.onclick,
1001
+ onpointerdown: this.onpointerdown,
1002
+ onpointerup: this.onpointerup,
1003
+ onkeydown: this.onkeydown,
1004
+ }),
1005
+ );
1006
+ }
1007
+
1008
+ interface MenuSubTriggerStateOpts extends MenuItemSharedStateOpts, Pick<MenuItemStateOpts, 'onSelect'> {
1009
+ openDelay: ReadableBox<number>;
1010
+ }
1011
+
1012
+ export class MenuSubTriggerState {
1013
+ static create(opts: MenuSubTriggerStateOpts) {
1014
+ const content = getMenuContent();
1015
+ const item = new MenuItemSharedState(opts, content);
1016
+ const submenu = getMenuMenu();
1017
+ return new MenuSubTriggerState(opts, item, content, submenu);
1018
+ }
1019
+ readonly opts: MenuSubTriggerStateOpts;
1020
+ readonly item: MenuItemSharedState;
1021
+ readonly content: MenuContentState;
1022
+ readonly submenu: MenuMenuState;
1023
+ readonly attachment: RefAttachment;
1024
+ #openTimer = createEffectTimeout(() => {
1025
+ if (this.submenu.root.isPointerInTransit) return;
1026
+ this.submenu.onOpen();
1027
+ }, () => this.opts.openDelay.current);
1028
+
1029
+ constructor(opts: MenuSubTriggerStateOpts, item: MenuItemSharedState, content: MenuContentState, submenu: MenuMenuState) {
1030
+ this.opts = opts;
1031
+ this.item = item;
1032
+ this.content = content;
1033
+ this.submenu = submenu;
1034
+ this.attachment = attachRef(this.opts.ref, (v) => (this.submenu.triggerNode = v));
1035
+ this.onpointerleave = this.onpointerleave.bind(this);
1036
+ this.onpointermove = this.onpointermove.bind(this);
1037
+ this.onkeydown = this.onkeydown.bind(this);
1038
+ this.onclick = this.onclick.bind(this);
1039
+ }
1040
+
1041
+ onpointermove(e: BitsPointerEvent) {
1042
+ if (!isMouseEvent(e)) return;
1043
+ if (this.submenu.root.isPointerInTransit) {
1044
+ this.#openTimer.stop();
1045
+ return;
1046
+ }
1047
+
1048
+ if (!this.item.opts.disabled.current && !this.submenu.opts.open.current && !this.#openTimer.pending) {
1049
+ if (this.opts.openDelay.current <= 0) {
1050
+ this.submenu.onOpen();
1051
+ return;
1052
+ }
1053
+
1054
+ this.#openTimer.start();
1055
+ }
1056
+ }
1057
+
1058
+ onpointerleave(e: BitsPointerEvent) {
1059
+ if (!isMouseEvent(e)) return;
1060
+ this.#openTimer.stop();
1061
+ }
1062
+
1063
+ onkeydown(e: BitsKeyboardEvent) {
1064
+ const isTypingAhead = this.content.isTypingAhead;
1065
+ if (this.item.opts.disabled.current || (isTypingAhead && e.key === kbd.SPACE)) return;
1066
+
1067
+ if (SUB_OPEN_KEYS[this.submenu.root.opts.dir.current].includes(e.key)) {
1068
+ e.currentTarget.click();
1069
+ e.preventDefault();
1070
+ }
1071
+ }
1072
+
1073
+ onclick(e: BitsMouseEvent) {
1074
+ if (this.item.opts.disabled.current) return;
1075
+ /**
1076
+ * We manually focus because iOS Safari doesn't always focus on click (e.g. buttons)
1077
+ * and we rely heavily on `onFocusOutside` for submenus to close when switching
1078
+ * between separate submenus.
1079
+ */
1080
+ if (!isHTMLElement(e.currentTarget)) return;
1081
+ e.currentTarget.focus();
1082
+ const selectEvent = new CustomEvent('menusubtriggerselect', {
1083
+ bubbles: true,
1084
+ cancelable: true,
1085
+ });
1086
+ this.opts.onSelect.current(selectEvent);
1087
+ if (!this.submenu.opts.open.current) {
1088
+ this.submenu.onOpen();
1089
+ tick().then(() => {
1090
+ if (!this.submenu.contentNode) return;
1091
+ this.submenu.focusFirstItem?.();
1092
+ });
1093
+ }
1094
+ }
1095
+
1096
+ readonly props = $derived.by(() =>
1097
+ mergeProps(
1098
+ {
1099
+ 'aria-haspopup': 'menu',
1100
+ 'aria-expanded': boolToStr(this.submenu.opts.open.current),
1101
+ 'data-state': getDataOpenClosed(this.submenu.opts.open.current),
1102
+ 'aria-controls': this.submenu.opts.open.current ? this.submenu.contentId.current : undefined,
1103
+ [this.submenu.root.getBitsAttr('sub-trigger')]: '',
1104
+ onclick: this.onclick,
1105
+ onpointermove: this.onpointermove,
1106
+ onpointerleave: this.onpointerleave,
1107
+ onkeydown: this.onkeydown,
1108
+ ...this.attachment,
1109
+ },
1110
+ this.item.props,
1111
+ ),
1112
+ );
1113
+ }
1114
+
1115
+ interface MenuCheckboxItemStateOpts
1116
+ extends
1117
+ WritableBoxedValues<{
1118
+ checked: boolean;
1119
+ indeterminate: boolean;
1120
+ }>,
1121
+ ReadableBoxedValues<{
1122
+ value: string;
1123
+ }> {}
1124
+
1125
+ export class MenuCheckboxItemState {
1126
+ static create(opts: MenuItemCombinedProps & MenuCheckboxItemStateOpts) {
1127
+ const item = new MenuItemState(opts, new MenuItemSharedState(opts, getMenuContent()));
1128
+ return new MenuCheckboxItemState(opts, item);
1129
+ }
1130
+
1131
+ readonly opts: MenuCheckboxItemStateOpts;
1132
+ readonly item: MenuItemState;
1133
+ readonly groupChecked: () => boolean | undefined;
1134
+
1135
+ constructor(opts: MenuCheckboxItemStateOpts, item: MenuItemState) {
1136
+ this.opts = opts;
1137
+ this.item = item;
1138
+ this.groupChecked = joinGroup(hasMenuCheckboxGroup() ? getMenuCheckboxGroup() : null, opts);
1139
+ }
1140
+
1141
+ toggleChecked() {
1142
+ if (this.opts.indeterminate.current) {
1143
+ this.opts.indeterminate.current = false;
1144
+ this.opts.checked.current = true;
1145
+ } else {
1146
+ this.opts.checked.current = !this.opts.checked.current;
1147
+ }
1148
+ }
1149
+
1150
+ readonly snippetProps = $derived.by(() => ({
1151
+ checked: this.opts.checked.current,
1152
+ indeterminate: this.opts.indeterminate.current,
1153
+ }));
1154
+
1155
+ readonly props = $derived.by(
1156
+ () =>
1157
+ ({
1158
+ ...this.item.props,
1159
+ role: 'menuitemcheckbox',
1160
+ 'aria-checked': getAriaChecked(this.opts.checked.current, this.opts.indeterminate.current),
1161
+ 'data-state': getCheckedState(this.opts.checked.current),
1162
+ [this.item.root.getBitsAttr('checkbox-item')]: '',
1163
+ }) as const,
1164
+ );
1165
+ }
1166
+
1167
+ interface MenuGroupStateOpts extends WithRefOpts {}
1168
+
1169
+ export class MenuGroupState {
1170
+ static create(opts: MenuGroupStateOpts) {
1171
+ return setMenuGroup(new MenuGroupState(opts, getMenuRoot()));
1172
+ }
1173
+
1174
+ readonly opts: MenuGroupStateOpts;
1175
+ readonly root: MenuRootState;
1176
+ readonly attachment: RefAttachment;
1177
+ groupHeadingId = $state<string | undefined>(undefined);
1178
+
1179
+ constructor(opts: MenuGroupStateOpts, root: MenuRootState) {
1180
+ this.opts = opts;
1181
+ this.root = root;
1182
+ this.attachment = attachRef(this.opts.ref);
1183
+ }
1184
+
1185
+ readonly props = $derived.by(
1186
+ () =>
1187
+ ({
1188
+ id: this.opts.id.current,
1189
+ role: 'group',
1190
+ 'aria-labelledby': this.groupHeadingId,
1191
+ [this.root.getBitsAttr('group')]: '',
1192
+ ...this.attachment,
1193
+ }) as const,
1194
+ );
1195
+ }
1196
+
1197
+ interface MenuGroupHeadingStateOpts extends WithRefOpts {}
1198
+
1199
+ export class MenuGroupHeadingState {
1200
+ static create(opts: MenuGroupHeadingStateOpts) {
1201
+ return new MenuGroupHeadingState(opts, getMenuGroup());
1202
+ }
1203
+ readonly opts: MenuGroupHeadingStateOpts;
1204
+ readonly group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState;
1205
+ readonly attachment: RefAttachment;
1206
+
1207
+ constructor(opts: MenuGroupHeadingStateOpts, group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState) {
1208
+ this.opts = opts;
1209
+ this.group = group;
1210
+ this.attachment = attachRef(this.opts.ref, (v) => (this.group.groupHeadingId = v?.id));
1211
+ }
1212
+
1213
+ readonly props = $derived.by(
1214
+ () =>
1215
+ ({
1216
+ id: this.opts.id.current,
1217
+ role: 'group',
1218
+ [this.group.root.getBitsAttr('group-heading')]: '',
1219
+ ...this.attachment,
1220
+ }) as const,
1221
+ );
1222
+ }
1223
+
1224
+ interface MenuSeparatorStateOpts extends WithRefOpts {}
1225
+
1226
+ export class MenuSeparatorState {
1227
+ static create(opts: MenuSeparatorStateOpts) {
1228
+ return new MenuSeparatorState(opts, getMenuRoot());
1229
+ }
1230
+
1231
+ readonly opts: MenuSeparatorStateOpts;
1232
+ readonly root: MenuRootState;
1233
+ readonly attachment: RefAttachment;
1234
+
1235
+ constructor(opts: MenuSeparatorStateOpts, root: MenuRootState) {
1236
+ this.opts = opts;
1237
+ this.root = root;
1238
+ this.attachment = attachRef(this.opts.ref);
1239
+ }
1240
+
1241
+ readonly props = $derived.by(
1242
+ () =>
1243
+ ({
1244
+ id: this.opts.id.current,
1245
+ role: 'group',
1246
+ [this.root.getBitsAttr('separator')]: '',
1247
+ ...this.attachment,
1248
+ }) as const,
1249
+ );
1250
+ }
1251
+
1252
+ export class MenuArrowState {
1253
+ static create() {
1254
+ return new MenuArrowState(getMenuRoot());
1255
+ }
1256
+
1257
+ readonly root: MenuRootState;
1258
+
1259
+ constructor(root: MenuRootState) {
1260
+ this.root = root;
1261
+ }
1262
+
1263
+ readonly props = $derived.by(
1264
+ () =>
1265
+ ({
1266
+ [this.root.getBitsAttr('arrow')]: '',
1267
+ }) as const,
1268
+ );
1269
+ }
1270
+
1271
+ interface MenuRadioGroupStateOpts
1272
+ extends
1273
+ WithRefOpts,
1274
+ WritableBoxedValues<{
1275
+ value: string;
1276
+ }> {}
1277
+
1278
+ export class MenuRadioGroupState {
1279
+ static create(opts: MenuRadioGroupStateOpts) {
1280
+ return setMenuGroup(setMenuRadioGroup(new MenuRadioGroupState(opts, getMenuContent())));
1281
+ }
1282
+ readonly opts: MenuRadioGroupStateOpts;
1283
+ readonly content: MenuContentState;
1284
+ readonly attachment: RefAttachment;
1285
+ groupHeadingId = $state<string | null>(null);
1286
+ root: MenuRootState;
1287
+
1288
+ constructor(opts: MenuRadioGroupStateOpts, content: MenuContentState) {
1289
+ this.opts = opts;
1290
+ this.content = content;
1291
+ this.root = content.parentMenu.root;
1292
+ this.attachment = attachRef(this.opts.ref);
1293
+ }
1294
+
1295
+ setValue(v: string) {
1296
+ this.opts.value.current = v;
1297
+ }
1298
+
1299
+ readonly props = $derived.by(
1300
+ () =>
1301
+ ({
1302
+ id: this.opts.id.current,
1303
+ [this.root.getBitsAttr('radio-group')]: '',
1304
+ role: 'group',
1305
+ 'aria-labelledby': this.groupHeadingId,
1306
+ ...this.attachment,
1307
+ }) as const,
1308
+ );
1309
+ }
1310
+
1311
+ interface MenuRadioItemStateOpts
1312
+ extends
1313
+ WithRefOpts,
1314
+ ReadableBoxedValues<{
1315
+ value: string;
1316
+ closeOnSelect: boolean;
1317
+ }> {}
1318
+
1319
+ export class MenuRadioItemState {
1320
+ static create(opts: MenuRadioItemStateOpts & MenuItemCombinedProps) {
1321
+ const radioGroup = getMenuRadioGroup();
1322
+ const sharedItem = new MenuItemSharedState(opts, radioGroup.content);
1323
+ const item = new MenuItemState(opts, sharedItem);
1324
+ return new MenuRadioItemState(opts, item, radioGroup);
1325
+ }
1326
+ readonly opts: MenuRadioItemStateOpts;
1327
+ readonly item: MenuItemState;
1328
+ readonly group: MenuRadioGroupState;
1329
+ readonly attachment: RefAttachment;
1330
+ readonly isChecked = $derived.by(() => this.group.opts.value.current === this.opts.value.current);
1331
+
1332
+ constructor(opts: MenuRadioItemStateOpts, item: MenuItemState, group: MenuRadioGroupState) {
1333
+ this.opts = opts;
1334
+ this.item = item;
1335
+ this.group = group;
1336
+ this.attachment = attachRef(this.opts.ref);
1337
+ }
1338
+
1339
+ selectValue() {
1340
+ this.group.setValue(this.opts.value.current);
1341
+ }
1342
+
1343
+ readonly props = $derived.by(
1344
+ () =>
1345
+ ({
1346
+ [this.group.root.getBitsAttr('radio-item')]: '',
1347
+ ...this.item.props,
1348
+ role: 'menuitemradio',
1349
+ 'aria-checked': getAriaChecked(this.isChecked, false),
1350
+ 'data-state': getCheckedState(this.isChecked),
1351
+ ...this.attachment,
1352
+ }) as const,
1353
+ );
1354
+ }
1355
+
1356
+ //
1357
+ // DROPDOWN MENU TRIGGER
1358
+ //
1359
+
1360
+ interface DropdownMenuTriggerStateOpts
1361
+ extends
1362
+ WithRefOpts,
1363
+ ReadableBoxedValues<{
1364
+ disabled: boolean;
1365
+ }> {}
1366
+
1367
+ export class DropdownMenuTriggerState {
1368
+ static create(opts: DropdownMenuTriggerStateOpts) {
1369
+ return new DropdownMenuTriggerState(opts, getMenuMenu());
1370
+ }
1371
+
1372
+ readonly opts: DropdownMenuTriggerStateOpts;
1373
+ readonly parentMenu: MenuMenuState;
1374
+ readonly attachment: RefAttachment;
1375
+
1376
+ constructor(opts: DropdownMenuTriggerStateOpts, parentMenu: MenuMenuState) {
1377
+ this.opts = opts;
1378
+ this.parentMenu = parentMenu;
1379
+ this.attachment = attachRef(this.opts.ref, (v) => (this.parentMenu.triggerNode = v));
1380
+ }
1381
+
1382
+ onclick: MouseEventHandler<HTMLElement> = (e) => {
1383
+ /**
1384
+ * MacOS VoiceOver sends a click in Safari/Firefox bypassing the keydown event
1385
+ * when V0+Space is pressed. Since we already handle the keydown event and the
1386
+ * pointerdown events separately, we ignore it if the detail is not 0.
1387
+ */
1388
+ if (this.opts.disabled.current || e.detail !== 0) return;
1389
+ this.parentMenu.toggleOpen();
1390
+ e.preventDefault();
1391
+ };
1392
+
1393
+ onpointerdown: PointerEventHandler<HTMLElement> = (e) => {
1394
+ if (this.opts.disabled.current) return;
1395
+ if (e.pointerType === 'touch') return e.preventDefault();
1396
+
1397
+ if (e.button === 0 && e.ctrlKey === false) {
1398
+ this.parentMenu.toggleOpen();
1399
+ // prevent trigger focusing when opening to allow
1400
+ // the content to be given focus without competition
1401
+ if (!this.parentMenu.opts.open.current) e.preventDefault();
1402
+ }
1403
+ };
1404
+
1405
+ onpointerup: PointerEventHandler<HTMLElement> = (e) => {
1406
+ if (this.opts.disabled.current) return;
1407
+ if (e.pointerType === 'touch') {
1408
+ e.preventDefault();
1409
+ this.parentMenu.toggleOpen();
1410
+ }
1411
+ };
1412
+
1413
+ onkeydown: KeyboardEventHandler<HTMLElement> = (e) => {
1414
+ if (this.opts.disabled.current) return;
1415
+ if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
1416
+ this.parentMenu.toggleOpen();
1417
+ e.preventDefault();
1418
+ return;
1419
+ }
1420
+ if (e.key === kbd.ARROW_DOWN) {
1421
+ this.parentMenu.onOpen();
1422
+ e.preventDefault();
1423
+ }
1424
+ };
1425
+
1426
+ readonly #ariaControls = $derived.by(() => {
1427
+ if (this.parentMenu.opts.open.current && this.parentMenu.contentId.current) return this.parentMenu.contentId.current;
1428
+ return undefined;
1429
+ });
1430
+
1431
+ readonly props = $derived.by(
1432
+ () =>
1433
+ ({
1434
+ id: this.opts.id.current,
1435
+ disabled: this.opts.disabled.current,
1436
+ 'aria-haspopup': 'menu',
1437
+ 'aria-expanded': boolToStr(this.parentMenu.opts.open.current),
1438
+ 'aria-controls': this.#ariaControls,
1439
+ 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled.current),
1440
+ 'data-state': getDataOpenClosed(this.parentMenu.opts.open.current),
1441
+ [this.parentMenu.root.getBitsAttr('trigger')]: '',
1442
+ //
1443
+ onclick: this.onclick,
1444
+ onpointerdown: this.onpointerdown,
1445
+ onpointerup: this.onpointerup,
1446
+ onkeydown: this.onkeydown,
1447
+ ...this.attachment,
1448
+ }) as const,
1449
+ );
1450
+ }
1451
+
1452
+ interface ContextMenuTriggerStateOpts
1453
+ extends
1454
+ WithRefOpts,
1455
+ ReadableBoxedValues<{
1456
+ disabled: boolean;
1457
+ }> {}
1458
+
1459
+ export class ContextMenuTriggerState {
1460
+ static create(opts: ContextMenuTriggerStateOpts) {
1461
+ return new ContextMenuTriggerState(opts, getMenuMenu());
1462
+ }
1463
+
1464
+ readonly opts: ContextMenuTriggerStateOpts;
1465
+ readonly parentMenu: MenuMenuState;
1466
+ readonly attachment: RefAttachment;
1467
+ #point = $state({ x: 0, y: 0 });
1468
+
1469
+ virtualElement = simpleBox({
1470
+ getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...this.#point }),
1471
+ });
1472
+ #longPressTimer = createEffectTimeout((e: BitsPointerEvent) => this.#handleOpen(e), () => 700);
1473
+
1474
+ constructor(opts: ContextMenuTriggerStateOpts, parentMenu: MenuMenuState) {
1475
+ this.opts = opts;
1476
+ this.parentMenu = parentMenu;
1477
+ this.attachment = attachRef(this.opts.ref, (v) => (this.parentMenu.triggerNode = v));
1478
+ this.oncontextmenu = this.oncontextmenu.bind(this);
1479
+ this.onpointerdown = this.onpointerdown.bind(this);
1480
+ this.onpointermove = this.onpointermove.bind(this);
1481
+ this.onpointercancel = this.onpointercancel.bind(this);
1482
+ this.onpointerup = this.onpointerup.bind(this);
1483
+
1484
+ $effect(() => {
1485
+ const point = this.#point;
1486
+ untrack(() => {
1487
+ this.virtualElement.current = {
1488
+ getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...point }),
1489
+ };
1490
+ });
1491
+ });
1492
+
1493
+ $effect(() => {
1494
+ const isDisabled = this.opts.disabled.current;
1495
+ untrack(() => {
1496
+ if (isDisabled) {
1497
+ this.#longPressTimer.stop();
1498
+ }
1499
+ });
1500
+ });
1501
+
1502
+ }
1503
+
1504
+ #handleOpen(e: BitsMouseEvent | BitsPointerEvent) {
1505
+ this.#point = { x: e.clientX, y: e.clientY };
1506
+ this.parentMenu.onOpen();
1507
+ }
1508
+
1509
+ oncontextmenu(e: BitsMouseEvent) {
1510
+ if (e.defaultPrevented || this.opts.disabled.current) return;
1511
+
1512
+ this.#longPressTimer.stop();
1513
+ this.#handleOpen(e);
1514
+ e.preventDefault();
1515
+ this.parentMenu.contentNode?.focus();
1516
+ }
1517
+
1518
+ onpointerdown(e: BitsPointerEvent) {
1519
+ if (this.opts.disabled.current || isMouseEvent(e)) return;
1520
+ this.#longPressTimer.start(e);
1521
+ }
1522
+
1523
+ onpointermove(e: BitsPointerEvent) {
1524
+ if (this.opts.disabled.current || isMouseEvent(e)) return;
1525
+ this.#longPressTimer.stop();
1526
+ }
1527
+
1528
+ onpointercancel(e: BitsPointerEvent) {
1529
+ if (this.opts.disabled.current || isMouseEvent(e)) return;
1530
+ this.#longPressTimer.stop();
1531
+ }
1532
+
1533
+ onpointerup(e: BitsPointerEvent) {
1534
+ if (this.opts.disabled.current || isMouseEvent(e)) return;
1535
+ this.#longPressTimer.stop();
1536
+ }
1537
+
1538
+ readonly props = $derived.by(
1539
+ () =>
1540
+ ({
1541
+ id: this.opts.id.current,
1542
+ disabled: this.opts.disabled.current,
1543
+ 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled.current),
1544
+ 'data-state': getDataOpenClosed(this.parentMenu.opts.open.current),
1545
+ [CONTEXT_MENU_TRIGGER_ATTR]: '',
1546
+ tabindex: -1,
1547
+ onpointerdown: this.onpointerdown,
1548
+ onpointermove: this.onpointermove,
1549
+ onpointercancel: this.onpointercancel,
1550
+ onpointerup: this.onpointerup,
1551
+ oncontextmenu: this.oncontextmenu,
1552
+ ...this.attachment,
1553
+ }) as const,
1554
+ );
1555
+ }
1556
+
1557
+ interface MenuCheckboxGroupStateOpts
1558
+ extends
1559
+ WithRefOpts,
1560
+ WritableBoxedValues<{
1561
+ value: string[];
1562
+ }> {}
1563
+
1564
+ export class MenuCheckboxGroupState {
1565
+ static create(opts: MenuCheckboxGroupStateOpts) {
1566
+ return setMenuGroup(setMenuCheckboxGroup(new MenuCheckboxGroupState(opts, getMenuContent())));
1567
+ }
1568
+
1569
+ readonly opts: MenuCheckboxGroupStateOpts;
1570
+ readonly content: MenuContentState;
1571
+ readonly root: MenuRootState;
1572
+ readonly attachment: RefAttachment;
1573
+ groupHeadingId = $state<string | null>(null);
1574
+
1575
+ constructor(opts: MenuCheckboxGroupStateOpts, content: MenuContentState) {
1576
+ this.opts = opts;
1577
+ this.content = content;
1578
+ this.root = content.parentMenu.root;
1579
+ this.attachment = attachRef(this.opts.ref);
1580
+ }
1581
+
1582
+ readonly props = $derived.by(
1583
+ () =>
1584
+ ({
1585
+ id: this.opts.id.current,
1586
+ [this.root.getBitsAttr('checkbox-group')]: '',
1587
+ role: 'group',
1588
+ 'aria-labelledby': this.groupHeadingId,
1589
+ ...this.attachment,
1590
+ }) as const,
1591
+ );
1592
+ }
1593
+
1594
+ export class MenuSubmenuState {
1595
+ static create(opts: MenuMenuStateOpts) {
1596
+ const menu = getMenuMenu();
1597
+ return setMenuMenu(new MenuMenuState(opts, menu.root, menu));
1598
+ }
1599
+ }