mtrl 0.3.8 → 0.4.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 (703) hide show
  1. package/dist/LICENSE +21 -0
  2. package/dist/README.md +324 -0
  3. package/dist/components/badge/api.d.ts +48 -0
  4. package/{src/components/badge/badge.ts → dist/components/badge/badge.d.ts} +14 -57
  5. package/dist/components/badge/config.d.ts +79 -0
  6. package/dist/components/badge/constants.d.ts +35 -0
  7. package/dist/components/badge/features.d.ts +36 -0
  8. package/dist/components/badge/index.d.ts +8 -0
  9. package/dist/components/badge/types.d.ts +256 -0
  10. package/dist/components/bottom-app-bar/bottom-app-bar.d.ts +12 -0
  11. package/dist/components/bottom-app-bar/config.d.ts +16 -0
  12. package/dist/components/bottom-app-bar/constants.d.ts +22 -0
  13. package/{src/components/bottom-app-bar/index.ts → dist/components/bottom-app-bar/index.d.ts} +0 -9
  14. package/dist/components/bottom-app-bar/types.d.ts +96 -0
  15. package/dist/components/button/api.d.ts +47 -0
  16. package/dist/components/button/button.d.ts +25 -0
  17. package/dist/components/button/config.d.ts +59 -0
  18. package/dist/components/button/constants.d.ts +43 -0
  19. package/dist/components/button/index.d.ts +6 -0
  20. package/dist/components/button/types.d.ts +265 -0
  21. package/dist/components/card/api.d.ts +11 -0
  22. package/{src/components/card/card.ts → dist/components/card/card.d.ts} +10 -58
  23. package/dist/components/card/config.d.ts +105 -0
  24. package/dist/components/card/constants.d.ts +85 -0
  25. package/dist/components/card/content.d.ts +92 -0
  26. package/dist/components/card/features.d.ts +131 -0
  27. package/{src/components/card/index.ts → dist/components/card/index.d.ts} +13 -74
  28. package/dist/components/card/types.d.ts +471 -0
  29. package/dist/components/carousel/api.d.ts +33 -0
  30. package/dist/components/carousel/carousel.d.ts +75 -0
  31. package/dist/components/carousel/config.d.ts +45 -0
  32. package/dist/components/carousel/constants.d.ts +165 -0
  33. package/dist/components/carousel/features/drag.d.ts +8 -0
  34. package/{src/components/carousel/features/index.ts → dist/components/carousel/features/index.d.ts} +1 -4
  35. package/dist/components/carousel/features/slides.d.ts +8 -0
  36. package/{src/components/carousel/index.ts → dist/components/carousel/index.d.ts} +10 -29
  37. package/dist/components/carousel/types.d.ts +276 -0
  38. package/dist/components/checkbox/api.d.ts +7 -0
  39. package/dist/components/checkbox/checkbox.d.ts +65 -0
  40. package/dist/components/checkbox/config.d.ts +44 -0
  41. package/dist/components/checkbox/constants.d.ts +63 -0
  42. package/{src/components/checkbox/index.ts → dist/components/checkbox/index.d.ts} +8 -25
  43. package/dist/components/checkbox/types.d.ts +242 -0
  44. package/dist/components/chips/api.d.ts +49 -0
  45. package/dist/components/chips/chip/api.d.ts +23 -0
  46. package/dist/components/chips/chip/chip.d.ts +8 -0
  47. package/dist/components/chips/chip/config.d.ts +57 -0
  48. package/dist/components/chips/chip/constants.d.ts +38 -0
  49. package/dist/components/chips/chip/index.d.ts +2 -0
  50. package/dist/components/chips/chip/types.d.ts +11 -0
  51. package/dist/components/chips/chips.d.ts +17 -0
  52. package/dist/components/chips/config.d.ts +67 -0
  53. package/dist/components/chips/constants.d.ts +46 -0
  54. package/dist/components/chips/features/chip-items.d.ts +8 -0
  55. package/dist/components/chips/features/container.d.ts +8 -0
  56. package/dist/components/chips/features/controller.d.ts +9 -0
  57. package/{src/components/chips/features/index.ts → dist/components/chips/features/index.d.ts} +1 -2
  58. package/dist/components/chips/features/label.d.ts +8 -0
  59. package/dist/components/chips/index.d.ts +4 -0
  60. package/dist/components/chips/schema.d.ts +34 -0
  61. package/dist/components/chips/types.d.ts +398 -0
  62. package/dist/components/datepicker/api.d.ts +9 -0
  63. package/dist/components/datepicker/config.d.ts +79 -0
  64. package/dist/components/datepicker/constants.d.ts +97 -0
  65. package/dist/components/datepicker/datepicker.d.ts +8 -0
  66. package/dist/components/datepicker/index.d.ts +3 -0
  67. package/dist/components/datepicker/render.d.ts +43 -0
  68. package/dist/components/datepicker/types.d.ts +321 -0
  69. package/dist/components/datepicker/utils.d.ts +84 -0
  70. package/dist/components/dialog/api.d.ts +59 -0
  71. package/dist/components/dialog/config.d.ts +91 -0
  72. package/dist/components/dialog/constants.d.ts +116 -0
  73. package/{src/components/dialog/dialog.ts → dist/components/dialog/dialog.d.ts} +16 -69
  74. package/dist/components/dialog/features.d.ts +37 -0
  75. package/dist/components/dialog/index.d.ts +14 -0
  76. package/dist/components/dialog/types.d.ts +483 -0
  77. package/dist/components/divider/config.d.ts +143 -0
  78. package/dist/components/divider/constants.d.ts +45 -0
  79. package/{src/components/divider/divider.ts → dist/components/divider/divider.d.ts} +9 -37
  80. package/dist/components/divider/features.d.ts +50 -0
  81. package/{src/components/divider/index.ts → dist/components/divider/index.d.ts} +6 -12
  82. package/dist/components/divider/types.d.ts +124 -0
  83. package/dist/components/extended-fab/api.d.ts +83 -0
  84. package/dist/components/extended-fab/config.d.ts +75 -0
  85. package/dist/components/extended-fab/constants.d.ts +82 -0
  86. package/dist/components/extended-fab/extended-fab.d.ts +61 -0
  87. package/{src/components/extended-fab/index.ts → dist/components/extended-fab/index.d.ts} +6 -15
  88. package/dist/components/extended-fab/types.d.ts +703 -0
  89. package/dist/components/fab/api.d.ts +64 -0
  90. package/dist/components/fab/config.d.ts +71 -0
  91. package/dist/components/fab/constants.d.ts +75 -0
  92. package/{src/components/fab/fab.ts → dist/components/fab/fab.d.ts} +10 -46
  93. package/{src/components/fab/index.ts → dist/components/fab/index.d.ts} +6 -15
  94. package/dist/components/fab/types.d.ts +577 -0
  95. package/dist/components/index.d.ts +76 -0
  96. package/dist/components/list/api.d.ts +106 -0
  97. package/dist/components/list/config.d.ts +61 -0
  98. package/dist/components/list/constants.d.ts +76 -0
  99. package/dist/components/list/features/index.d.ts +2 -0
  100. package/dist/components/list/features/listmanager.d.ts +9 -0
  101. package/dist/components/list/features/selection.d.ts +9 -0
  102. package/dist/components/list/index.d.ts +12 -0
  103. package/dist/components/list/list.d.ts +11 -0
  104. package/dist/components/list/types.d.ts +294 -0
  105. package/dist/components/menu/api.d.ts +58 -0
  106. package/dist/components/menu/config.d.ts +78 -0
  107. package/dist/components/menu/constants.d.ts +106 -0
  108. package/dist/components/menu/features/controller.d.ts +10 -0
  109. package/dist/components/menu/features/index.d.ts +6 -0
  110. package/dist/components/menu/features/keyboard.d.ts +26 -0
  111. package/dist/components/menu/features/opener.d.ts +10 -0
  112. package/dist/components/menu/features/position.d.ts +18 -0
  113. package/dist/components/menu/features/submenu.d.ts +10 -0
  114. package/{src/components/menu/index.ts → dist/components/menu/index.d.ts} +11 -27
  115. package/dist/components/menu/menu.d.ts +29 -0
  116. package/dist/components/menu/types.d.ts +338 -0
  117. package/dist/components/navigation/api.d.ts +8 -0
  118. package/dist/components/navigation/config.d.ts +34 -0
  119. package/dist/components/navigation/constants.d.ts +137 -0
  120. package/dist/components/navigation/features/controller.d.ts +30 -0
  121. package/dist/components/navigation/features/items.d.ts +32 -0
  122. package/dist/components/navigation/index.d.ts +3 -0
  123. package/dist/components/navigation/nav-item.d.ts +30 -0
  124. package/dist/components/navigation/navigation.d.ts +8 -0
  125. package/dist/components/navigation/system/core.d.ts +72 -0
  126. package/dist/components/navigation/system/events.d.ts +35 -0
  127. package/dist/components/navigation/system/index.d.ts +10 -0
  128. package/dist/components/navigation/system/mobile.d.ts +52 -0
  129. package/dist/components/navigation/system/state.d.ts +22 -0
  130. package/dist/components/navigation/system/types.d.ts +305 -0
  131. package/dist/components/navigation/types.d.ts +216 -0
  132. package/dist/components/progress/api.d.ts +46 -0
  133. package/dist/components/progress/config.d.ts +72 -0
  134. package/dist/components/progress/constants.d.ts +139 -0
  135. package/dist/components/progress/features/canvas.d.ts +32 -0
  136. package/dist/components/progress/features/circular.d.ts +9 -0
  137. package/dist/components/progress/features/index.d.ts +8 -0
  138. package/dist/components/progress/features/linear.d.ts +9 -0
  139. package/dist/components/progress/features/resize-observer.d.ts +5 -0
  140. package/dist/components/progress/features/state.d.ts +38 -0
  141. package/dist/components/progress/features.d.ts +40 -0
  142. package/dist/components/progress/index.d.ts +3 -0
  143. package/dist/components/progress/progress.d.ts +24 -0
  144. package/dist/components/progress/types.d.ts +272 -0
  145. package/dist/components/radios/api.d.ts +37 -0
  146. package/dist/components/radios/config.d.ts +42 -0
  147. package/dist/components/radios/constants.d.ts +114 -0
  148. package/dist/components/radios/index.d.ts +3 -0
  149. package/dist/components/radios/radio.d.ts +8 -0
  150. package/dist/components/radios/radios.d.ts +8 -0
  151. package/dist/components/radios/types.d.ts +189 -0
  152. package/dist/components/search/api.d.ts +55 -0
  153. package/dist/components/search/config.d.ts +73 -0
  154. package/dist/components/search/constants.d.ts +128 -0
  155. package/{src/components/search/features/index.ts → dist/components/search/features/index.d.ts} +1 -2
  156. package/dist/components/search/features/search.d.ts +7 -0
  157. package/dist/components/search/features/states.d.ts +8 -0
  158. package/dist/components/search/features/structure.d.ts +7 -0
  159. package/dist/components/search/index.d.ts +3 -0
  160. package/dist/components/search/search.d.ts +8 -0
  161. package/dist/components/search/types.d.ts +132 -0
  162. package/dist/components/segmented-button/config.d.ts +65 -0
  163. package/dist/components/segmented-button/constants.d.ts +85 -0
  164. package/dist/components/segmented-button/index.d.ts +4 -0
  165. package/dist/components/segmented-button/segment.d.ts +15 -0
  166. package/dist/components/segmented-button/segmented-button.d.ts +49 -0
  167. package/dist/components/segmented-button/types.d.ts +257 -0
  168. package/dist/components/select/api.d.ts +8 -0
  169. package/dist/components/select/config.d.ts +18 -0
  170. package/dist/components/select/constants.d.ts +113 -0
  171. package/dist/components/select/features.d.ts +13 -0
  172. package/{src/components/select/index.ts → dist/components/select/index.d.ts} +7 -20
  173. package/dist/components/select/select.d.ts +36 -0
  174. package/dist/components/select/types.d.ts +302 -0
  175. package/dist/components/sheet/api.d.ts +37 -0
  176. package/dist/components/sheet/config.d.ts +42 -0
  177. package/dist/components/sheet/constants.d.ts +136 -0
  178. package/dist/components/sheet/features/content.d.ts +6 -0
  179. package/dist/components/sheet/features/gestures.d.ts +6 -0
  180. package/{src/components/sheet/features/index.ts → dist/components/sheet/features/index.d.ts} +1 -2
  181. package/dist/components/sheet/features/position.d.ts +7 -0
  182. package/dist/components/sheet/features/state.d.ts +6 -0
  183. package/dist/components/sheet/features/title.d.ts +6 -0
  184. package/dist/components/sheet/index.d.ts +3 -0
  185. package/dist/components/sheet/sheet.d.ts +8 -0
  186. package/dist/components/sheet/types.d.ts +250 -0
  187. package/dist/components/slider/api.d.ts +57 -0
  188. package/dist/components/slider/config.d.ts +75 -0
  189. package/dist/components/slider/constants.d.ts +138 -0
  190. package/dist/components/slider/features/controller.d.ts +9 -0
  191. package/dist/components/slider/features/handlers.d.ts +25 -0
  192. package/dist/components/slider/features/index.d.ts +3 -0
  193. package/dist/components/slider/features/range.d.ts +8 -0
  194. package/dist/components/slider/features/states.d.ts +9 -0
  195. package/dist/components/slider/index.d.ts +3 -0
  196. package/dist/components/slider/schema.d.ts +108 -0
  197. package/dist/components/slider/slider.d.ts +18 -0
  198. package/dist/components/slider/types.d.ts +170 -0
  199. package/dist/components/snackbar/api.d.ts +7 -0
  200. package/dist/components/snackbar/config.d.ts +55 -0
  201. package/dist/components/snackbar/constants.d.ts +88 -0
  202. package/dist/components/snackbar/features.d.ts +13 -0
  203. package/dist/components/snackbar/index.d.ts +3 -0
  204. package/dist/components/snackbar/position.d.ts +15 -0
  205. package/dist/components/snackbar/queue.d.ts +7 -0
  206. package/dist/components/snackbar/snackbar.d.ts +8 -0
  207. package/dist/components/snackbar/types.d.ts +172 -0
  208. package/dist/components/switch/api.d.ts +7 -0
  209. package/dist/components/switch/config.d.ts +34 -0
  210. package/dist/components/switch/constants.d.ts +88 -0
  211. package/dist/components/switch/features.d.ts +59 -0
  212. package/dist/components/switch/index.d.ts +4 -0
  213. package/dist/components/switch/switch.d.ts +8 -0
  214. package/dist/components/switch/types.d.ts +131 -0
  215. package/dist/components/tabs/api.d.ts +52 -0
  216. package/dist/components/tabs/config.d.ts +39 -0
  217. package/dist/components/tabs/constants.d.ts +142 -0
  218. package/dist/components/tabs/features.d.ts +133 -0
  219. package/dist/components/tabs/index.d.ts +11 -0
  220. package/dist/components/tabs/indicator.d.ts +49 -0
  221. package/dist/components/tabs/responsive.d.ts +38 -0
  222. package/dist/components/tabs/scroll-indicators.d.ts +18 -0
  223. package/dist/components/tabs/state.d.ts +53 -0
  224. package/dist/components/tabs/tab-api.d.ts +43 -0
  225. package/dist/components/tabs/tab.d.ts +7 -0
  226. package/dist/components/tabs/tabs.d.ts +27 -0
  227. package/dist/components/tabs/types.d.ts +390 -0
  228. package/dist/components/tabs/utils.d.ts +17 -0
  229. package/dist/components/textfield/api.d.ts +8 -0
  230. package/dist/components/textfield/config.d.ts +34 -0
  231. package/dist/components/textfield/constants.d.ts +148 -0
  232. package/{src/components/textfield/features/index.ts → dist/components/textfield/features/index.d.ts} +1 -6
  233. package/dist/components/textfield/features/leading-icon.d.ts +55 -0
  234. package/dist/components/textfield/features/placement.d.ts +28 -0
  235. package/dist/components/textfield/features/prefix-text.d.ts +54 -0
  236. package/dist/components/textfield/features/suffix-text.d.ts +54 -0
  237. package/dist/components/textfield/features/supporting-text.d.ts +59 -0
  238. package/dist/components/textfield/features/trailing-icon.d.ts +55 -0
  239. package/dist/components/textfield/index.d.ts +3 -0
  240. package/dist/components/textfield/textfield.d.ts +36 -0
  241. package/dist/components/textfield/types.d.ts +217 -0
  242. package/dist/components/timepicker/api.d.ts +24 -0
  243. package/dist/components/timepicker/clockdial.d.ts +34 -0
  244. package/dist/components/timepicker/config.d.ts +75 -0
  245. package/dist/components/timepicker/constants.d.ts +266 -0
  246. package/dist/components/timepicker/index.d.ts +4 -0
  247. package/dist/components/timepicker/render.d.ts +9 -0
  248. package/dist/components/timepicker/timepicker.d.ts +8 -0
  249. package/dist/components/timepicker/types.d.ts +284 -0
  250. package/dist/components/timepicker/utils.d.ts +74 -0
  251. package/dist/components/tooltip/api.d.ts +18 -0
  252. package/dist/components/tooltip/config.d.ts +38 -0
  253. package/dist/components/tooltip/constants.d.ts +108 -0
  254. package/dist/components/tooltip/index.d.ts +3 -0
  255. package/dist/components/tooltip/tooltip.d.ts +8 -0
  256. package/dist/components/tooltip/types.d.ts +188 -0
  257. package/dist/components/top-app-bar/config.d.ts +16 -0
  258. package/dist/components/top-app-bar/constants.d.ts +74 -0
  259. package/{src/components/top-app-bar/index.ts → dist/components/top-app-bar/index.d.ts} +2 -4
  260. package/dist/components/top-app-bar/top-app-bar.d.ts +68 -0
  261. package/dist/components/top-app-bar/types.d.ts +118 -0
  262. package/dist/constants.d.ts +30 -0
  263. package/dist/core/canvas/index.d.ts +5 -0
  264. package/dist/core/canvas/resize.d.ts +14 -0
  265. package/dist/core/collection/adapters/base.d.ts +47 -0
  266. package/dist/core/collection/adapters/route.d.ts +149 -0
  267. package/dist/core/collection/collection.d.ts +131 -0
  268. package/dist/core/collection/index.d.ts +10 -0
  269. package/dist/core/collection/list-manager/config.d.ts +29 -0
  270. package/dist/core/collection/list-manager/dom-elements.d.ts +30 -0
  271. package/dist/core/collection/list-manager/index.d.ts +61 -0
  272. package/dist/core/collection/list-manager/item-measurement.d.ts +91 -0
  273. package/dist/core/collection/list-manager/renderer.d.ts +31 -0
  274. package/dist/core/collection/list-manager/scroll-tracker.d.ts +20 -0
  275. package/dist/core/collection/list-manager/state.d.ts +60 -0
  276. package/dist/core/collection/list-manager/types.d.ts +361 -0
  277. package/dist/core/collection/list-manager/utils/recycling.d.ts +34 -0
  278. package/dist/core/collection/list-manager/utils/visibility.d.ts +45 -0
  279. package/dist/core/compose/base.d.ts +31 -0
  280. package/dist/core/compose/component.d.ts +61 -0
  281. package/dist/core/compose/features/badge.d.ts +43 -0
  282. package/dist/core/compose/features/checkable.d.ts +59 -0
  283. package/dist/core/compose/features/constants.d.ts +45 -0
  284. package/dist/core/compose/features/debounce.d.ts +84 -0
  285. package/dist/core/compose/features/disabled.d.ts +47 -0
  286. package/dist/core/compose/features/events.d.ts +37 -0
  287. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  288. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  289. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  290. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  291. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  292. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  293. package/dist/core/compose/features/gestures.d.ts +86 -0
  294. package/dist/core/compose/features/icon.d.ts +71 -0
  295. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +7 -8
  296. package/dist/core/compose/features/input.d.ts +71 -0
  297. package/dist/core/compose/features/lifecycle.d.ts +61 -0
  298. package/dist/core/compose/features/position.d.ts +51 -0
  299. package/dist/core/compose/features/ripple.d.ts +61 -0
  300. package/dist/core/compose/features/size.d.ts +17 -0
  301. package/dist/core/compose/features/style.d.ts +16 -0
  302. package/dist/core/compose/features/text.d.ts +63 -0
  303. package/dist/core/compose/features/textinput.d.ts +93 -0
  304. package/dist/core/compose/features/textlabel.d.ts +57 -0
  305. package/dist/core/compose/features/throttle.d.ts +75 -0
  306. package/dist/core/compose/features/track.d.ts +42 -0
  307. package/dist/core/compose/features/variant.d.ts +17 -0
  308. package/dist/core/compose/features/withEvents.d.ts +45 -0
  309. package/dist/core/compose/index.d.ts +17 -0
  310. package/{src/core/compose/pipe.ts → dist/core/compose/pipe.d.ts} +4 -20
  311. package/dist/core/compose/utils/type-guards.d.ts +27 -0
  312. package/dist/core/composition/features/dom.d.ts +19 -0
  313. package/dist/core/composition/features/icon.d.ts +45 -0
  314. package/{src/core/composition/features/index.ts → dist/core/composition/features/index.d.ts} +1 -6
  315. package/dist/core/composition/features/label.d.ts +49 -0
  316. package/{src/core/composition/features/layout.ts → dist/core/composition/features/layout.d.ts} +8 -24
  317. package/{src/core/composition/index.ts → dist/core/composition/index.d.ts} +4 -14
  318. package/{src/core/config/component-config.ts → dist/core/config/component.d.ts} +35 -83
  319. package/dist/core/config.d.ts +130 -0
  320. package/dist/core/dom/attributes.d.ts +20 -0
  321. package/dist/core/dom/classes.d.ts +52 -0
  322. package/dist/core/dom/create.d.ts +154 -0
  323. package/dist/core/dom/events.d.ts +69 -0
  324. package/dist/core/dom/index.d.ts +6 -0
  325. package/dist/core/dom/utils.d.ts +42 -0
  326. package/dist/core/gestures/index.d.ts +12 -0
  327. package/dist/core/gestures/longpress.d.ts +23 -0
  328. package/dist/core/gestures/manager.d.ts +14 -0
  329. package/dist/core/gestures/pan.d.ts +12 -0
  330. package/dist/core/gestures/pinch.d.ts +14 -0
  331. package/dist/core/gestures/rotate.d.ts +14 -0
  332. package/dist/core/gestures/swipe.d.ts +20 -0
  333. package/dist/core/gestures/tap.d.ts +12 -0
  334. package/dist/core/gestures/types.d.ts +320 -0
  335. package/dist/core/gestures/utils.d.ts +57 -0
  336. package/dist/core/index.d.ts +29 -0
  337. package/dist/core/layout/array.d.ts +15 -0
  338. package/dist/core/layout/config.d.ts +32 -0
  339. package/dist/core/layout/create.d.ts +14 -0
  340. package/dist/core/layout/index.d.ts +13 -0
  341. package/dist/core/layout/jsx.d.ts +13 -0
  342. package/dist/core/layout/object.d.ts +14 -0
  343. package/dist/core/layout/processor.d.ts +28 -0
  344. package/dist/core/layout/result.d.ts +12 -0
  345. package/dist/core/layout/template.d.ts +12 -0
  346. package/dist/core/layout/types.d.ts +137 -0
  347. package/dist/core/layout/utils.d.ts +38 -0
  348. package/dist/core/state/disabled.d.ts +32 -0
  349. package/dist/core/state/emitter.d.ts +40 -0
  350. package/dist/core/state/events.d.ts +36 -0
  351. package/{src/core/state/index.ts → dist/core/state/index.d.ts} +1 -7
  352. package/dist/core/state/lifecycle.d.ts +57 -0
  353. package/dist/core/state/store.d.ts +82 -0
  354. package/dist/core/utils/background.d.ts +40 -0
  355. package/dist/core/utils/index.d.ts +18 -0
  356. package/dist/core/utils/mobile.d.ts +54 -0
  357. package/dist/core/utils/object.d.ts +13 -0
  358. package/dist/core/utils/performance.d.ts +79 -0
  359. package/dist/core/utils/theme.d.ts +38 -0
  360. package/dist/core/utils/validate.d.ts +73 -0
  361. package/dist/index.cjs +64 -0
  362. package/dist/index.cjs.map +291 -0
  363. package/dist/index.d.ts +15 -0
  364. package/dist/index.js +53 -14854
  365. package/dist/index.js.map +291 -0
  366. package/dist/package.json +39 -0
  367. package/dist/styles.css +7 -0
  368. package/package.json +23 -4
  369. package/.env +0 -15
  370. package/.typedocignore +0 -11
  371. package/CONTRIBUTING.md +0 -218
  372. package/DOCS.md +0 -153
  373. package/TESTING.md +0 -214
  374. package/git-user-stats.js +0 -545
  375. package/index.ts +0 -10
  376. package/src/components/badge/api.ts +0 -313
  377. package/src/components/badge/config.ts +0 -153
  378. package/src/components/badge/features.ts +0 -194
  379. package/src/components/badge/index.ts +0 -90
  380. package/src/components/badge/types.ts +0 -279
  381. package/src/components/bottom-app-bar/bottom-app-bar.ts +0 -154
  382. package/src/components/bottom-app-bar/config.ts +0 -29
  383. package/src/components/bottom-app-bar/types.ts +0 -114
  384. package/src/components/button/api.ts +0 -172
  385. package/src/components/button/button.ts +0 -112
  386. package/src/components/button/config.ts +0 -96
  387. package/src/components/button/index.ts +0 -37
  388. package/src/components/button/types.ts +0 -290
  389. package/src/components/card/api.ts +0 -222
  390. package/src/components/card/config.ts +0 -304
  391. package/src/components/card/content.ts +0 -343
  392. package/src/components/card/features.ts +0 -407
  393. package/src/components/card/types.ts +0 -497
  394. package/src/components/carousel/api.ts +0 -147
  395. package/src/components/carousel/carousel.ts +0 -242
  396. package/src/components/carousel/config.ts +0 -91
  397. package/src/components/carousel/constants.ts +0 -181
  398. package/src/components/carousel/features/drag.ts +0 -388
  399. package/src/components/carousel/features/slides.ts +0 -682
  400. package/src/components/carousel/types.ts +0 -327
  401. package/src/components/checkbox/api.ts +0 -82
  402. package/src/components/checkbox/checkbox.ts +0 -142
  403. package/src/components/checkbox/config.ts +0 -89
  404. package/src/components/checkbox/types.ts +0 -342
  405. package/src/components/chips/api.ts +0 -194
  406. package/src/components/chips/chip/api.ts +0 -233
  407. package/src/components/chips/chip/chip.ts +0 -131
  408. package/src/components/chips/chip/config.ts +0 -91
  409. package/src/components/chips/chip/index.ts +0 -3
  410. package/src/components/chips/chips.md +0 -481
  411. package/src/components/chips/chips.ts +0 -75
  412. package/src/components/chips/config.ts +0 -109
  413. package/src/components/chips/constants.ts +0 -61
  414. package/src/components/chips/features/chip-items.ts +0 -33
  415. package/src/components/chips/features/container.ts +0 -77
  416. package/src/components/chips/features/controller.ts +0 -448
  417. package/src/components/chips/features/label.ts +0 -108
  418. package/src/components/chips/index.ts +0 -11
  419. package/src/components/chips/schema.ts +0 -61
  420. package/src/components/chips/types.ts +0 -469
  421. package/src/components/datepicker/api.ts +0 -265
  422. package/src/components/datepicker/config.ts +0 -141
  423. package/src/components/datepicker/datepicker.ts +0 -341
  424. package/src/components/datepicker/index.ts +0 -12
  425. package/src/components/datepicker/render.ts +0 -450
  426. package/src/components/datepicker/types.ts +0 -397
  427. package/src/components/datepicker/utils.ts +0 -289
  428. package/src/components/dialog/api.ts +0 -317
  429. package/src/components/dialog/config.ts +0 -116
  430. package/src/components/dialog/features.ts +0 -907
  431. package/src/components/dialog/index.ts +0 -141
  432. package/src/components/dialog/types.ts +0 -553
  433. package/src/components/divider/config.ts +0 -165
  434. package/src/components/divider/features.ts +0 -233
  435. package/src/components/divider/types.ts +0 -132
  436. package/src/components/extended-fab/api.ts +0 -193
  437. package/src/components/extended-fab/config.ts +0 -140
  438. package/src/components/extended-fab/extended-fab.ts +0 -153
  439. package/src/components/extended-fab/types.ts +0 -749
  440. package/src/components/fab/api.ts +0 -137
  441. package/src/components/fab/config.ts +0 -121
  442. package/src/components/fab/types.ts +0 -615
  443. package/src/components/list/api.ts +0 -82
  444. package/src/components/list/config.ts +0 -63
  445. package/src/components/list/features.ts +0 -229
  446. package/src/components/list/index.ts +0 -67
  447. package/src/components/list/list-item.ts +0 -163
  448. package/src/components/list/list.ts +0 -108
  449. package/src/components/list/types.ts +0 -396
  450. package/src/components/list/utils.ts +0 -98
  451. package/src/components/menu/api.ts +0 -230
  452. package/src/components/menu/config.ts +0 -127
  453. package/src/components/menu/features/anchor.ts +0 -394
  454. package/src/components/menu/features/controller.ts +0 -1423
  455. package/src/components/menu/features/index.ts +0 -13
  456. package/src/components/menu/features/position.ts +0 -353
  457. package/src/components/menu/menu.ts +0 -121
  458. package/src/components/menu/types.ts +0 -392
  459. package/src/components/navigation/api.ts +0 -142
  460. package/src/components/navigation/config.ts +0 -73
  461. package/src/components/navigation/features/controller.ts +0 -273
  462. package/src/components/navigation/features/items.ts +0 -353
  463. package/src/components/navigation/index.ts +0 -11
  464. package/src/components/navigation/nav-item.ts +0 -196
  465. package/src/components/navigation/navigation.ts +0 -115
  466. package/src/components/navigation/system/core.ts +0 -302
  467. package/src/components/navigation/system/events.ts +0 -240
  468. package/src/components/navigation/system/index.ts +0 -184
  469. package/src/components/navigation/system/mobile.ts +0 -278
  470. package/src/components/navigation/system/state.ts +0 -77
  471. package/src/components/navigation/system/types.ts +0 -364
  472. package/src/components/navigation/types.ts +0 -292
  473. package/src/components/progress/api.ts +0 -178
  474. package/src/components/progress/config.ts +0 -122
  475. package/src/components/progress/index.ts +0 -4
  476. package/src/components/progress/progress.ts +0 -159
  477. package/src/components/progress/types.ts +0 -255
  478. package/src/components/radios/api.ts +0 -125
  479. package/src/components/radios/config.ts +0 -59
  480. package/src/components/radios/constants.ts +0 -19
  481. package/src/components/radios/index.ts +0 -3
  482. package/src/components/radios/radio.ts +0 -292
  483. package/src/components/radios/radios.ts +0 -43
  484. package/src/components/radios/types.ts +0 -219
  485. package/src/components/search/api.ts +0 -203
  486. package/src/components/search/config.ts +0 -86
  487. package/src/components/search/features/search.ts +0 -717
  488. package/src/components/search/features/states.ts +0 -169
  489. package/src/components/search/features/structure.ts +0 -197
  490. package/src/components/search/index.ts +0 -7
  491. package/src/components/search/search.ts +0 -52
  492. package/src/components/search/types.ts +0 -175
  493. package/src/components/segmented-button/config.ts +0 -119
  494. package/src/components/segmented-button/index.ts +0 -4
  495. package/src/components/segmented-button/segment.ts +0 -108
  496. package/src/components/segmented-button/segmented-button.ts +0 -361
  497. package/src/components/segmented-button/types.ts +0 -306
  498. package/src/components/select/api.ts +0 -78
  499. package/src/components/select/config.ts +0 -76
  500. package/src/components/select/features.ts +0 -331
  501. package/src/components/select/select.ts +0 -73
  502. package/src/components/select/types.ts +0 -355
  503. package/src/components/sheet/api.ts +0 -96
  504. package/src/components/sheet/config.ts +0 -65
  505. package/src/components/sheet/features/content.ts +0 -51
  506. package/src/components/sheet/features/gestures.ts +0 -177
  507. package/src/components/sheet/features/position.ts +0 -41
  508. package/src/components/sheet/features/state.ts +0 -116
  509. package/src/components/sheet/features/title.ts +0 -86
  510. package/src/components/sheet/index.ts +0 -12
  511. package/src/components/sheet/sheet.ts +0 -56
  512. package/src/components/sheet/types.ts +0 -294
  513. package/src/components/slider/accessibility.md +0 -59
  514. package/src/components/slider/api.ts +0 -192
  515. package/src/components/slider/config.ts +0 -118
  516. package/src/components/slider/features/controller.ts +0 -737
  517. package/src/components/slider/features/handlers.ts +0 -497
  518. package/src/components/slider/features/index.ts +0 -5
  519. package/src/components/slider/features/range.ts +0 -104
  520. package/src/components/slider/features/states.ts +0 -195
  521. package/src/components/slider/index.ts +0 -17
  522. package/src/components/slider/schema.ts +0 -141
  523. package/src/components/slider/slider.ts +0 -76
  524. package/src/components/slider/types.ts +0 -223
  525. package/src/components/snackbar/api.ts +0 -162
  526. package/src/components/snackbar/config.ts +0 -61
  527. package/src/components/snackbar/features.ts +0 -76
  528. package/src/components/snackbar/index.ts +0 -9
  529. package/src/components/snackbar/position.ts +0 -79
  530. package/src/components/snackbar/queue.ts +0 -76
  531. package/src/components/snackbar/snackbar.ts +0 -60
  532. package/src/components/snackbar/types.ts +0 -159
  533. package/src/components/switch/api.ts +0 -93
  534. package/src/components/switch/config.ts +0 -56
  535. package/src/components/switch/features.ts +0 -198
  536. package/src/components/switch/index.ts +0 -8
  537. package/src/components/switch/switch.ts +0 -52
  538. package/src/components/switch/types.ts +0 -168
  539. package/src/components/tabs/api.ts +0 -221
  540. package/src/components/tabs/config.ts +0 -73
  541. package/src/components/tabs/features.ts +0 -403
  542. package/src/components/tabs/index.ts +0 -46
  543. package/src/components/tabs/indicator.ts +0 -285
  544. package/src/components/tabs/responsive.ts +0 -144
  545. package/src/components/tabs/scroll-indicators.ts +0 -149
  546. package/src/components/tabs/state.ts +0 -186
  547. package/src/components/tabs/tab-api.ts +0 -266
  548. package/src/components/tabs/tab.ts +0 -267
  549. package/src/components/tabs/tabs.ts +0 -71
  550. package/src/components/tabs/types.ts +0 -461
  551. package/src/components/tabs/utils.ts +0 -107
  552. package/src/components/textfield/api.ts +0 -197
  553. package/src/components/textfield/config.ts +0 -52
  554. package/src/components/textfield/features/leading-icon.ts +0 -127
  555. package/src/components/textfield/features/placement.ts +0 -149
  556. package/src/components/textfield/features/prefix-text.ts +0 -107
  557. package/src/components/textfield/features/suffix-text.ts +0 -100
  558. package/src/components/textfield/features/supporting-text.ts +0 -113
  559. package/src/components/textfield/features/trailing-icon.ts +0 -108
  560. package/src/components/textfield/index.ts +0 -9
  561. package/src/components/textfield/textfield.ts +0 -92
  562. package/src/components/textfield/types.ts +0 -265
  563. package/src/components/timepicker/README.md +0 -277
  564. package/src/components/timepicker/api.ts +0 -632
  565. package/src/components/timepicker/clockdial.ts +0 -479
  566. package/src/components/timepicker/config.ts +0 -228
  567. package/src/components/timepicker/index.ts +0 -3
  568. package/src/components/timepicker/render.ts +0 -613
  569. package/src/components/timepicker/timepicker.ts +0 -117
  570. package/src/components/timepicker/types.ts +0 -336
  571. package/src/components/timepicker/utils.ts +0 -241
  572. package/src/components/tooltip/api.ts +0 -415
  573. package/src/components/tooltip/config.ts +0 -80
  574. package/src/components/tooltip/index.ts +0 -12
  575. package/src/components/tooltip/tooltip.ts +0 -60
  576. package/src/components/tooltip/types.ts +0 -223
  577. package/src/components/top-app-bar/config.ts +0 -83
  578. package/src/components/top-app-bar/top-app-bar.ts +0 -316
  579. package/src/components/top-app-bar/types.ts +0 -140
  580. package/src/core/build/constants.ts +0 -48
  581. package/src/core/build/icon.ts +0 -137
  582. package/src/core/build/ripple.ts +0 -193
  583. package/src/core/build/text.ts +0 -91
  584. package/src/core/collection/adapters/base.ts +0 -62
  585. package/src/core/collection/adapters/route.ts +0 -201
  586. package/src/core/collection/collection.ts +0 -300
  587. package/src/core/collection/index.ts +0 -57
  588. package/src/core/collection/list-manager.ts +0 -333
  589. package/src/core/compose/base.ts +0 -43
  590. package/src/core/compose/component.ts +0 -255
  591. package/src/core/compose/features/badge.ts +0 -79
  592. package/src/core/compose/features/checkable.ts +0 -155
  593. package/src/core/compose/features/disabled.ts +0 -116
  594. package/src/core/compose/features/events.ts +0 -65
  595. package/src/core/compose/features/icon.ts +0 -71
  596. package/src/core/compose/features/input.ts +0 -174
  597. package/src/core/compose/features/lifecycle.ts +0 -139
  598. package/src/core/compose/features/position.ts +0 -94
  599. package/src/core/compose/features/ripple.ts +0 -58
  600. package/src/core/compose/features/size.ts +0 -29
  601. package/src/core/compose/features/style.ts +0 -31
  602. package/src/core/compose/features/text.ts +0 -44
  603. package/src/core/compose/features/textinput.ts +0 -238
  604. package/src/core/compose/features/textlabel.ts +0 -113
  605. package/src/core/compose/features/track.ts +0 -84
  606. package/src/core/compose/features/variant.ts +0 -29
  607. package/src/core/compose/features/withEvents.ts +0 -137
  608. package/src/core/compose/index.ts +0 -54
  609. package/src/core/composition/features/dom.ts +0 -45
  610. package/src/core/composition/features/icon.ts +0 -131
  611. package/src/core/composition/features/label.ts +0 -155
  612. package/src/core/config.ts +0 -211
  613. package/src/core/dom/attributes.ts +0 -33
  614. package/src/core/dom/classes.ts +0 -132
  615. package/src/core/dom/create.ts +0 -273
  616. package/src/core/dom/events.ts +0 -209
  617. package/src/core/dom/index.ts +0 -10
  618. package/src/core/dom/utils.ts +0 -97
  619. package/src/core/index.ts +0 -111
  620. package/src/core/layout/README.md +0 -715
  621. package/src/core/layout/array.ts +0 -180
  622. package/src/core/layout/config.ts +0 -193
  623. package/src/core/layout/create.ts +0 -54
  624. package/src/core/layout/index.ts +0 -36
  625. package/src/core/layout/object.ts +0 -123
  626. package/src/core/layout/processor.ts +0 -106
  627. package/src/core/layout/result.ts +0 -84
  628. package/src/core/layout/types.ts +0 -180
  629. package/src/core/layout/utils.ts +0 -144
  630. package/src/core/state/disabled.ts +0 -81
  631. package/src/core/state/emitter.ts +0 -94
  632. package/src/core/state/events.ts +0 -88
  633. package/src/core/state/lifecycle.ts +0 -131
  634. package/src/core/state/store.ts +0 -197
  635. package/src/core/utils/index.ts +0 -45
  636. package/src/core/utils/mobile.ts +0 -98
  637. package/src/core/utils/object.ts +0 -41
  638. package/src/core/utils/validate.ts +0 -234
  639. package/src/index.ts +0 -90
  640. package/src/styles/abstract/_base.scss +0 -2
  641. package/src/styles/abstract/_config.scss +0 -28
  642. package/src/styles/abstract/_functions.scss +0 -124
  643. package/src/styles/abstract/_mixins.scss +0 -352
  644. package/src/styles/abstract/_theme.scss +0 -269
  645. package/src/styles/abstract/_variables.scss +0 -305
  646. package/src/styles/base/_reset.scss +0 -86
  647. package/src/styles/base/_typography.scss +0 -155
  648. package/src/styles/components/_badge.scss +0 -182
  649. package/src/styles/components/_bottom-app-bar.scss +0 -103
  650. package/src/styles/components/_button.scss +0 -224
  651. package/src/styles/components/_card.scss +0 -401
  652. package/src/styles/components/_carousel.scss +0 -645
  653. package/src/styles/components/_checkbox.scss +0 -231
  654. package/src/styles/components/_chips.scss +0 -638
  655. package/src/styles/components/_datepicker.scss +0 -358
  656. package/src/styles/components/_dialog.scss +0 -259
  657. package/src/styles/components/_divider.scss +0 -57
  658. package/src/styles/components/_extended-fab.scss +0 -267
  659. package/src/styles/components/_fab.scss +0 -225
  660. package/src/styles/components/_list.scss +0 -248
  661. package/src/styles/components/_menu.scss +0 -242
  662. package/src/styles/components/_navigation-mobile.scss +0 -244
  663. package/src/styles/components/_navigation-system.scss +0 -151
  664. package/src/styles/components/_navigation.scss +0 -407
  665. package/src/styles/components/_progress.scss +0 -151
  666. package/src/styles/components/_radios.scss +0 -187
  667. package/src/styles/components/_search.scss +0 -306
  668. package/src/styles/components/_segmented-button.scss +0 -227
  669. package/src/styles/components/_select.scss +0 -272
  670. package/src/styles/components/_sheet.scss +0 -236
  671. package/src/styles/components/_slider.scss +0 -489
  672. package/src/styles/components/_snackbar.scss +0 -211
  673. package/src/styles/components/_switch.scss +0 -298
  674. package/src/styles/components/_tabs.scss +0 -416
  675. package/src/styles/components/_textfield.scss +0 -773
  676. package/src/styles/components/_timepicker.scss +0 -451
  677. package/src/styles/components/_tooltip.scss +0 -241
  678. package/src/styles/components/_top-app-bar.scss +0 -225
  679. package/src/styles/main.scss +0 -175
  680. package/src/styles/themes/_autumn.scss +0 -105
  681. package/src/styles/themes/_base-theme.scss +0 -85
  682. package/src/styles/themes/_baseline.scss +0 -173
  683. package/src/styles/themes/_bluekhaki.scss +0 -125
  684. package/src/styles/themes/_brownbeige.scss +0 -125
  685. package/src/styles/themes/_browngreen.scss +0 -125
  686. package/src/styles/themes/_forest.scss +0 -77
  687. package/src/styles/themes/_greenbeige.scss +0 -125
  688. package/src/styles/themes/_index.scss +0 -6
  689. package/src/styles/themes/_material.scss +0 -125
  690. package/src/styles/themes/_ocean.scss +0 -77
  691. package/src/styles/themes/_sageivory.scss +0 -125
  692. package/src/styles/themes/_spring.scss +0 -77
  693. package/src/styles/themes/_summer.scss +0 -87
  694. package/src/styles/themes/_sunset.scss +0 -60
  695. package/src/styles/themes/_tealcaramel.scss +0 -125
  696. package/src/styles/themes/_winter.scss +0 -77
  697. package/src/styles/utilities/_colors.scss +0 -154
  698. package/src/styles/utilities/_flexbox.scss +0 -194
  699. package/src/styles/utilities/_layout.scss +0 -665
  700. package/src/styles/utilities/_ripple.scss +0 -79
  701. package/src/styles/utilities/_spacing.scss +0 -139
  702. package/src/styles/utilities/_typography.scss +0 -178
  703. package/src/styles/utilities/_visibility.scss +0 -142
@@ -1,1423 +0,0 @@
1
- // src/components/menu/features/controller.ts
2
-
3
- import { MenuConfig, MenuContent, MenuItem, MenuDivider, MenuEvent, MenuSelectEvent } from '../types';
4
- import { createPositioner } from './position';
5
-
6
- let ignoreNextDocumentClick = false;
7
-
8
- /**
9
- * Adds controller functionality to the menu component
10
- * Manages state, rendering, positioning, and event handling
11
- *
12
- * @param config - Menu configuration
13
- * @returns Component enhancer with menu controller functionality
14
- */
15
- const withController = (config: MenuConfig) => component => {
16
- if (!component.element) {
17
- console.warn('Cannot initialize menu controller: missing element');
18
- return component;
19
- }
20
-
21
- // Initialize state
22
- const state = {
23
- visible: config.visible || false,
24
- items: config.items || [],
25
- position: config.position,
26
- selectedItemId: null as string | null,
27
- activeSubmenu: null as HTMLElement,
28
- activeSubmenuItem: null as HTMLElement,
29
- activeItemIndex: -1,
30
- submenuLevel: 0, // Track nesting level of submenus
31
- activeSubmenus: [] as Array<{
32
- element: HTMLElement,
33
- menuItem: HTMLElement,
34
- level: number,
35
- isOpening: boolean // Track if submenu is in opening transition
36
- }>,
37
- submenuTimer: null,
38
- hoverIntent: {
39
- timer: null,
40
- activeItem: null
41
- },
42
- component,
43
- keyboardNavActive: false // Track if keyboard navigation is active
44
- };
45
-
46
- // Create positioner
47
- const positioner = createPositioner(component, config);
48
-
49
- // Create event helpers
50
- const eventHelpers = {
51
- triggerEvent(eventName: string, data: any = {}, originalEvent?: Event) {
52
- const eventData = {
53
- menu: state.component,
54
- ...data,
55
- originalEvent,
56
- preventDefault: () => { eventData.defaultPrevented = true; },
57
- defaultPrevented: false
58
- };
59
-
60
- component.emit(eventName, eventData);
61
- return eventData;
62
- }
63
- };
64
-
65
- /**
66
- * Gets the anchor element from config
67
- */
68
- const getAnchorElement = (): HTMLElement => {
69
- // First try to get the resolved anchor from the anchor feature
70
- if (component.anchor && typeof component.anchor.getAnchor === 'function') {
71
- return component.anchor.getAnchor();
72
- }
73
-
74
- // Fall back to config anchor for initial positioning
75
- const { anchor } = config;
76
-
77
- if (typeof anchor === 'string') {
78
- const element = document.querySelector(anchor);
79
- if (!element) {
80
- console.warn(`Menu anchor not found: ${anchor}`);
81
- return null;
82
- }
83
- return element as HTMLElement;
84
- }
85
-
86
- // Handle component with element property
87
- if (typeof anchor === 'object' && anchor !== null && 'element' in anchor) {
88
- return anchor.element;
89
- }
90
-
91
- // Handle direct HTML element
92
- return anchor as HTMLElement;
93
- };
94
-
95
- /**
96
- * Creates a DOM element for a menu item
97
- */
98
- const createMenuItem = (item: MenuItem, index: number): HTMLElement => {
99
- const itemElement = document.createElement('li');
100
- const itemClass = `${component.getClass('menu-item')}`;
101
-
102
- itemElement.className = itemClass;
103
- itemElement.setAttribute('role', 'menuitem');
104
- itemElement.setAttribute('tabindex', '-1'); // Set to -1 by default, will update when needed
105
- itemElement.setAttribute('data-id', item.id);
106
- itemElement.setAttribute('data-index', index.toString());
107
-
108
- if (item.disabled) {
109
- itemElement.classList.add(`${itemClass}--disabled`);
110
- itemElement.setAttribute('aria-disabled', 'true');
111
- } else {
112
- itemElement.setAttribute('aria-disabled', 'false');
113
- }
114
-
115
- if (state.selectedItemId && item.id === state.selectedItemId) {
116
- itemElement.classList.add(`${itemClass}--selected`);
117
- itemElement.setAttribute('aria-selected', 'true');
118
- } else {
119
- itemElement.setAttribute('aria-selected', 'false');
120
- }
121
-
122
-
123
- if (item.hasSubmenu) {
124
- itemElement.classList.add(`${itemClass}--submenu`);
125
- itemElement.setAttribute('aria-haspopup', 'true');
126
- itemElement.setAttribute('aria-expanded', 'false');
127
- }
128
-
129
- // Create content container for flexible layout
130
- const contentContainer = document.createElement('span');
131
- contentContainer.className = `${component.getClass('menu-item-content')}`;
132
-
133
- // Add icon if provided
134
- if (item.icon) {
135
- const iconElement = document.createElement('span');
136
- iconElement.className = `${component.getClass('menu-item-icon')}`;
137
- iconElement.innerHTML = item.icon;
138
- contentContainer.appendChild(iconElement);
139
- }
140
-
141
- // Add text
142
- const textElement = document.createElement('span');
143
- textElement.className = `${component.getClass('menu-item-text')}`;
144
- textElement.textContent = item.text;
145
- contentContainer.appendChild(textElement);
146
-
147
- // Add shortcut if provided
148
- if (item.shortcut) {
149
- const shortcutElement = document.createElement('span');
150
- shortcutElement.className = `${component.getClass('menu-item-shortcut')}`;
151
- shortcutElement.textContent = item.shortcut;
152
- contentContainer.appendChild(shortcutElement);
153
- }
154
-
155
- itemElement.appendChild(contentContainer);
156
-
157
- // Add event listeners
158
- if (!item.disabled) {
159
- // Mouse events
160
- itemElement.addEventListener('click', (e) => handleItemClick(e, item, index));
161
-
162
- // Focus and blur events for proper focus styling
163
- itemElement.addEventListener('focus', () => {
164
- state.activeItemIndex = index;
165
- state.keyboardNavActive = true;
166
- });
167
-
168
- // Additional keyboard event handler for accessibility
169
- itemElement.addEventListener('keydown', (e) => {
170
- if (e.key === 'Enter' || e.key === ' ') {
171
- e.preventDefault();
172
- handleItemClick(e, item, index);
173
- }
174
- });
175
-
176
- if (item.hasSubmenu && config.openSubmenuOnHover) {
177
- itemElement.addEventListener('mouseenter', () => handleSubmenuHover(item, index, itemElement));
178
- itemElement.addEventListener('mouseleave', handleSubmenuLeave);
179
- }
180
- }
181
-
182
- return itemElement;
183
- };
184
-
185
- /**
186
- * Creates a DOM element for a menu divider
187
- */
188
- const createDivider = (divider: MenuDivider, index: number): HTMLElement => {
189
- const dividerElement = document.createElement('li');
190
- dividerElement.className = `${component.getClass('menu-divider')}`;
191
- dividerElement.setAttribute('role', 'separator');
192
- dividerElement.setAttribute('data-index', index.toString());
193
-
194
- if (divider.id) {
195
- dividerElement.setAttribute('id', divider.id);
196
- }
197
-
198
- return dividerElement;
199
- };
200
-
201
- /**
202
- * Renders the menu items
203
- */
204
- const renderMenuItems = (): void => {
205
- const menuList = document.createElement('ul');
206
- menuList.className = `${component.getClass('menu-list')}`;
207
- menuList.setAttribute('role', 'menu');
208
-
209
- // Create items
210
- state.items.forEach((item, index) => {
211
- if ('type' in item && item.type === 'divider') {
212
- menuList.appendChild(createDivider(item, index));
213
- } else {
214
- menuList.appendChild(createMenuItem(item as MenuItem, index));
215
- }
216
- });
217
-
218
- // Clear and append
219
- component.element.innerHTML = '';
220
- component.element.appendChild(menuList);
221
- };
222
-
223
- /**
224
- * Clean up hover intent timer
225
- */
226
- const clearHoverIntent = () => {
227
- if (state.hoverIntent.timer) {
228
- clearTimeout(state.hoverIntent.timer);
229
- state.hoverIntent.timer = null;
230
- state.hoverIntent.activeItem = null;
231
- }
232
- };
233
-
234
- /**
235
- * Sets focus appropriately based on interaction type
236
- * For keyboard interactions, focuses the first item
237
- * For mouse interactions, makes the menu container focusable but doesn't auto-focus
238
- *
239
- * @param {'keyboard'|'mouse'} interactionType - Type of interaction that opened the menu
240
- */
241
- const handleFocus = (interactionType: 'keyboard' | 'mouse'): void => {
242
- // Reset active item index
243
- state.activeItemIndex = -1;
244
-
245
- if (interactionType === 'keyboard') {
246
- // Find all focusable items
247
- const items = Array.from(
248
- component.element.querySelectorAll(`.${component.getClass('menu-item')}:not(.${component.getClass('menu-item--disabled')})`)
249
- ) as HTMLElement[];
250
-
251
- if (items.length > 0) {
252
- // Set all items to tabindex -1 except the first one
253
- items.forEach((item, index) => {
254
- item.setAttribute('tabindex', index === 0 ? '0' : '-1');
255
- });
256
-
257
- // Focus the first item for keyboard navigation
258
- items[0].focus();
259
- state.activeItemIndex = 0;
260
- state.keyboardNavActive = true;
261
- } else {
262
- // If no items, focus the menu itself
263
- component.element.setAttribute('tabindex', '0');
264
- component.element.focus();
265
- }
266
- } else {
267
- // For mouse interaction, make the menu focusable but don't auto-focus
268
- component.element.setAttribute('tabindex', '-1');
269
-
270
- // Still set up the tabindex correctly for potential keyboard navigation
271
- const items = Array.from(
272
- component.element.querySelectorAll(`.${component.getClass('menu-item')}:not(.${component.getClass('menu-item--disabled')})`)
273
- ) as HTMLElement[];
274
-
275
- if (items.length > 0) {
276
- // Set all items to tabindex -1 except the first one
277
- items.forEach((item, index) => {
278
- item.setAttribute('tabindex', index === 0 ? '0' : '-1');
279
- });
280
- }
281
- }
282
- };
283
-
284
- /**
285
- * Handles click on a menu item
286
- */
287
- const handleItemClick = (e: MouseEvent, item: MenuItem, index: number): void => {
288
- e.preventDefault();
289
- e.stopPropagation();
290
-
291
- // Don't process if disabled
292
- if (item.disabled) return;
293
-
294
- if (item.hasSubmenu) {
295
- handleSubmenuClick(item, index, e.currentTarget as HTMLElement);
296
- return;
297
- }
298
-
299
- // Trigger select event
300
- const selectEvent = eventHelpers.triggerEvent('select', {
301
- item,
302
- itemId: item.id,
303
- itemData: item.data
304
- }, e) as MenuSelectEvent;
305
-
306
- // Close menu if needed
307
- if (config.closeOnSelect && !selectEvent.defaultPrevented) {
308
- closeMenu(e);
309
- }
310
- };
311
-
312
- /**
313
- * Handles click on a submenu item
314
- */
315
- const handleSubmenuClick = (item: MenuItem, index: number, itemElement: HTMLElement, viaKeyboard = false): void => {
316
- if (!item.submenu || !item.hasSubmenu) return;
317
-
318
- // Check if the submenu is already open
319
- const isOpen = itemElement.getAttribute('aria-expanded') === 'true';
320
-
321
- // Find if any submenu is currently in opening transition
322
- const anySubmenuTransitioning = state.activeSubmenus.some(s => s.isOpening);
323
-
324
- // Completely ignore clicks during any submenu transition
325
- if (anySubmenuTransitioning) {
326
- return;
327
- }
328
-
329
- if (isOpen) {
330
- // Close submenu - only if fully open
331
- // Find the closest submenu level
332
- const currentLevel = parseInt(
333
- itemElement.closest(`.${component.getClass('menu--submenu')}`)?.getAttribute('data-level') || '0',
334
- 10
335
- );
336
-
337
- // Close this level + 1 and deeper
338
- closeSubmenuAtLevel(currentLevel + 1);
339
-
340
- // Reset expanded state
341
- itemElement.setAttribute('aria-expanded', 'false');
342
- } else {
343
- // Open new submenu
344
- openSubmenu(item, index, itemElement, viaKeyboard);
345
- }
346
- };
347
-
348
- /**
349
- * Handles hover on a submenu item
350
- */
351
- const handleSubmenuHover = (item: MenuItem, index: number, itemElement: HTMLElement): void => {
352
- if (!config.openSubmenuOnHover || !item.hasSubmenu) return;
353
-
354
- // If keyboard navigation is active, don't open submenu on hover
355
- if (state.keyboardNavActive) return;
356
-
357
- // Clear any existing timers
358
- clearHoverIntent();
359
- clearSubmenuTimer();
360
-
361
- // Set hover intent
362
- state.hoverIntent.activeItem = itemElement;
363
- state.hoverIntent.timer = setTimeout(() => {
364
- const isCurrentlyHovered = itemElement.matches(':hover');
365
- if (isCurrentlyHovered) {
366
- // Only close and reopen if this is a different submenu item
367
- if (state.activeSubmenuItem !== itemElement) {
368
- openSubmenu(item, index, itemElement);
369
- }
370
- }
371
- state.hoverIntent.timer = null;
372
- }, 100);
373
- };
374
-
375
- /**
376
- * Handles mouse leave from submenu
377
- */
378
- const handleSubmenuLeave = (e: MouseEvent): void => {
379
- // If keyboard navigation is active, don't close submenu on mouse leave
380
- if (state.keyboardNavActive) return;
381
-
382
- // Clear hover intent
383
- clearHoverIntent();
384
-
385
- // Don't close immediately to allow moving to submenu
386
- clearSubmenuTimer();
387
-
388
- // Set a timer to close the submenu if not re-entered
389
- state.submenuTimer = setTimeout(() => {
390
- // Check if mouse is over the submenu or the parent menu item
391
- const submenuElement = state.activeSubmenu;
392
- const menuItemElement = state.activeSubmenuItem;
393
-
394
- if (submenuElement && menuItemElement) {
395
- const overSubmenu = submenuElement.matches(':hover');
396
- const overMenuItem = menuItemElement.matches(':hover');
397
-
398
- if (!overSubmenu && !overMenuItem) {
399
- closeSubmenuAtLevel(state.submenuLevel);
400
- }
401
- }
402
-
403
- state.submenuTimer = null;
404
- }, 300);
405
- };
406
-
407
- /**
408
- * Opens a submenu with proper animation and positioning
409
- */
410
- const openSubmenu = (item: MenuItem, index: number, itemElement: HTMLElement, viaKeyboard = false): void => {
411
- if (!item.submenu || !item.hasSubmenu) return;
412
-
413
- // If opened via keyboard, update the keyboard navigation state
414
- if (viaKeyboard) {
415
- state.keyboardNavActive = true;
416
- }
417
-
418
- // Get current level of the submenu we're opening
419
- const currentLevel = itemElement.closest(`.${component.getClass('menu--submenu')}`)
420
- ? parseInt(itemElement.closest(`.${component.getClass('menu--submenu')}`).getAttribute('data-level') || '0', 10) + 1
421
- : 1;
422
-
423
- // Close any deeper level submenus first, preserving the current level
424
- closeSubmenuAtLevel(currentLevel);
425
-
426
- // Check if this submenu is already in opening state - if so, do nothing
427
- const existingSubmenuIndex = state.activeSubmenus.findIndex(
428
- s => s.menuItem === itemElement && s.isOpening
429
- );
430
- if (existingSubmenuIndex >= 0) {
431
- return; // Already opening this submenu, don't restart the process
432
- }
433
-
434
- // Set expanded state
435
- itemElement.setAttribute('aria-expanded', 'true');
436
-
437
- // Create submenu element with proper classes and attributes
438
- const submenuElement = document.createElement('div');
439
- submenuElement.className = `${component.getClass('menu')} ${component.getClass('menu--submenu')}`;
440
- submenuElement.setAttribute('role', 'menu');
441
- submenuElement.setAttribute('tabindex', '-1');
442
- submenuElement.setAttribute('data-level', currentLevel.toString());
443
- submenuElement.setAttribute('data-parent-item', item.id);
444
-
445
- // Increase z-index for each level of submenu
446
- submenuElement.style.zIndex = `${1000 + (currentLevel * 10)}`;
447
-
448
- // Create submenu list
449
- const submenuList = document.createElement('ul');
450
- submenuList.className = `${component.getClass('menu-list')}`;
451
-
452
- // Create submenu items
453
- const submenuItems = [];
454
- item.submenu.forEach((subitem, subindex) => {
455
- if ('type' in subitem && subitem.type === 'divider') {
456
- submenuList.appendChild(createDivider(subitem, subindex));
457
- } else {
458
- const subitemElement = createMenuItem(subitem as MenuItem, subindex);
459
- submenuList.appendChild(subitemElement);
460
- if (!(subitem as MenuItem).disabled) {
461
- submenuItems.push(subitemElement);
462
- }
463
- }
464
- });
465
-
466
- submenuElement.appendChild(submenuList);
467
-
468
- // Add to DOM to enable measurement and transitions
469
- document.body.appendChild(submenuElement);
470
-
471
- // Position the submenu using our positioner with the current nesting level
472
- positioner.positionSubmenu(submenuElement, itemElement, currentLevel);
473
-
474
- // Setup keyboard navigation for submenu
475
- submenuElement.addEventListener('keydown', handleMenuKeydown);
476
-
477
- // Add mouseenter event to prevent closing
478
- submenuElement.addEventListener('mouseenter', () => {
479
- if (!state.keyboardNavActive) {
480
- clearSubmenuTimer();
481
- }
482
- });
483
-
484
- // Add mouseleave event to handle closing
485
- submenuElement.addEventListener('mouseleave', (e) => {
486
- if (!state.keyboardNavActive) {
487
- handleSubmenuLeave(e);
488
- }
489
- });
490
-
491
- // Setup submenu event handlers for nested submenus
492
- const setupNestedSubmenuHandlers = (parent: HTMLElement) => {
493
- const submenuItems = parent.querySelectorAll(`.${component.getClass('menu-item--submenu')}`) as NodeListOf<HTMLElement>;
494
-
495
- submenuItems.forEach((menuItem) => {
496
- const itemIndex = parseInt(menuItem.getAttribute('data-index'), 10);
497
- const menuItemData = item.submenu[itemIndex] as MenuItem;
498
-
499
- if (menuItemData && menuItemData.hasSubmenu) {
500
- // Add hover handler for nested submenus
501
- if (config.openSubmenuOnHover) {
502
- menuItem.addEventListener('mouseenter', () => {
503
- handleNestedSubmenuHover(menuItemData, itemIndex, menuItem);
504
- });
505
- menuItem.addEventListener('mouseleave', handleSubmenuLeave);
506
- }
507
-
508
- // Add click handler for nested submenus
509
- menuItem.addEventListener('click', (e) => {
510
- e.preventDefault();
511
- e.stopPropagation();
512
- handleNestedSubmenuClick(menuItemData, itemIndex, menuItem, false);
513
- });
514
- }
515
- });
516
- };
517
-
518
- // Setup handlers for any nested submenus
519
- setupNestedSubmenuHandlers(submenuElement);
520
-
521
- // Update state with active submenu
522
- state.activeSubmenu = submenuElement;
523
- state.activeSubmenuItem = itemElement;
524
-
525
- // Add to active submenus array to maintain hierarchy
526
- state.activeSubmenus.push({
527
- element: submenuElement,
528
- menuItem: itemElement,
529
- level: currentLevel,
530
- isOpening: true // Mark as in opening transition
531
- });
532
-
533
- // Update submenu level
534
- state.submenuLevel = currentLevel;
535
-
536
- // Add document events for this submenu
537
- document.addEventListener('click', handleDocumentClickForSubmenu);
538
- window.addEventListener('resize', handleWindowResizeForSubmenu, { passive: true });
539
- window.addEventListener('scroll', handleWindowScrollForSubmenu, { passive: true });
540
-
541
- // Make visible with animation
542
- requestAnimationFrame(() => {
543
- submenuElement.classList.add(`${component.getClass('menu--visible')}`);
544
-
545
- // Wait for transition to complete before marking as fully opened
546
- // This should match your CSS transition duration
547
- setTimeout(() => {
548
- // Find this submenu in the active submenus array and update its state
549
- const index = state.activeSubmenus.findIndex(s => s.element === submenuElement);
550
- if (index !== -1) {
551
- state.activeSubmenus[index].isOpening = false;
552
- }
553
- }, 300); // Adjust to match your transition duration
554
-
555
- // If opened via keyboard, focus the first item in the submenu
556
- if (viaKeyboard && submenuItems.length > 0) {
557
- submenuItems[0].setAttribute('tabindex', '0');
558
-
559
- // Set other items to -1
560
- for (let i = 1; i < submenuItems.length; i++) {
561
- submenuItems[i].setAttribute('tabindex', '-1');
562
- }
563
-
564
- // Focus with a short delay to allow animation to start
565
- setTimeout(() => {
566
- submenuItems[0].focus();
567
- }, 50);
568
- }
569
- });
570
- };
571
-
572
- /**
573
- * Handles hover on a nested submenu item
574
- */
575
- const handleNestedSubmenuHover = (item: MenuItem, index: number, itemElement: HTMLElement): void => {
576
- if (!config.openSubmenuOnHover || !item.hasSubmenu || state.keyboardNavActive) return;
577
-
578
- // Clear any existing timers
579
- clearHoverIntent();
580
- clearSubmenuTimer();
581
-
582
- // Set hover intent with a slightly longer delay for nested menus
583
- state.hoverIntent.activeItem = itemElement;
584
- state.hoverIntent.timer = setTimeout(() => {
585
- const isCurrentlyHovered = itemElement.matches(':hover');
586
- if (isCurrentlyHovered) {
587
- // Find the closest submenu level of this item
588
- const currentLevel = parseInt(
589
- itemElement.closest(`.${component.getClass('menu--submenu')}`)?.getAttribute('data-level') || '1',
590
- 10
591
- );
592
-
593
- // Open the nested submenu (will handle closing deeper levels properly)
594
- handleNestedSubmenuClick(item, index, itemElement, false);
595
- }
596
- state.hoverIntent.timer = null;
597
- }, 120); // Slightly longer delay for nested submenus
598
- };
599
-
600
- /**
601
- * Handles click on a nested submenu item
602
- */
603
- const handleNestedSubmenuClick = (item: MenuItem, index: number, itemElement: HTMLElement, viaKeyboard = false): void => {
604
- if (!item.submenu || !item.hasSubmenu) return;
605
-
606
- // Check if the submenu is already open
607
- const isOpen = itemElement.getAttribute('aria-expanded') === 'true';
608
-
609
- // Find if any submenu is currently in opening transition
610
- const anySubmenuTransitioning = state.activeSubmenus.some(s => s.isOpening);
611
-
612
- // Completely ignore clicks during any submenu transition
613
- if (anySubmenuTransitioning) {
614
- return;
615
- }
616
-
617
- if (isOpen) {
618
- // Find the closest submenu level
619
- const currentLevel = parseInt(
620
- itemElement.closest(`.${component.getClass('menu--submenu')}`)?.getAttribute('data-level') || '1',
621
- 10
622
- );
623
-
624
- // Close submenus at and deeper than the next level
625
- closeSubmenuAtLevel(currentLevel + 1);
626
- } else {
627
- // Open the nested submenu
628
- openSubmenu(item, index, itemElement, viaKeyboard);
629
- }
630
- };
631
-
632
- /**
633
- * Clear submenu close timer
634
- */
635
- const clearSubmenuTimer = () => {
636
- if (state.submenuTimer) {
637
- clearTimeout(state.submenuTimer);
638
- state.submenuTimer = null;
639
- }
640
- };
641
-
642
- /**
643
- * Closes submenus at or deeper than the specified level
644
- * @param level - The level to start closing from
645
- */
646
- const closeSubmenuAtLevel = (level: number): void => {
647
- // Clear any hover intent or submenu timers
648
- clearHoverIntent();
649
- clearSubmenuTimer();
650
-
651
- // Find submenus at or deeper than the specified level
652
- const submenusCopy = [...state.activeSubmenus];
653
- const submenuIndicesToRemove = [];
654
-
655
- // Identify which submenus to remove, working from deepest level first
656
- for (let i = submenusCopy.length - 1; i >= 0; i--) {
657
- if (submenusCopy[i].level >= level) {
658
- const submenuToClose = submenusCopy[i];
659
-
660
- // Set aria-expanded attribute to false on the parent menu item
661
- if (submenuToClose.menuItem) {
662
- submenuToClose.menuItem.setAttribute('aria-expanded', 'false');
663
- }
664
-
665
- // Hide with animation
666
- submenuToClose.element.classList.remove(`${component.getClass('menu--visible')}`);
667
-
668
- // Schedule for removal
669
- setTimeout(() => {
670
- if (submenuToClose.element.parentNode) {
671
- submenuToClose.element.parentNode.removeChild(submenuToClose.element);
672
- }
673
- }, 200);
674
-
675
- // Mark for removal from state
676
- submenuIndicesToRemove.push(i);
677
- }
678
- }
679
-
680
- // Remove the closed submenus from state
681
- submenuIndicesToRemove.forEach(index => {
682
- state.activeSubmenus.splice(index, 1);
683
- });
684
-
685
- // Update active submenu references based on what's left
686
- if (state.activeSubmenus.length > 0) {
687
- const deepestRemaining = state.activeSubmenus[state.activeSubmenus.length - 1];
688
- state.activeSubmenu = deepestRemaining.element;
689
- state.activeSubmenuItem = deepestRemaining.menuItem;
690
- state.submenuLevel = deepestRemaining.level;
691
- } else {
692
- state.activeSubmenu = null;
693
- state.activeSubmenuItem = null;
694
- state.submenuLevel = 0;
695
- }
696
- };
697
-
698
- /**
699
- * Closes all submenus
700
- */
701
- const closeSubmenu = (): void => {
702
- // Clear timers
703
- clearHoverIntent();
704
- clearSubmenuTimer();
705
-
706
- if (state.activeSubmenus.length === 0) return;
707
-
708
- // Close all active submenus
709
- [...state.activeSubmenus].forEach(submenu => {
710
- // Remove expanded state from parent item
711
- if (submenu.menuItem) {
712
- submenu.menuItem.setAttribute('aria-expanded', 'false');
713
- }
714
-
715
- // Remove submenu element with animation
716
- submenu.element.classList.remove(`${component.getClass('menu--visible')}`);
717
-
718
- // Remove after animation
719
- setTimeout(() => {
720
- if (submenu.element.parentNode) {
721
- submenu.element.parentNode.removeChild(submenu.element);
722
- }
723
- }, 200);
724
- });
725
-
726
- // Clear state
727
- state.activeSubmenu = null;
728
- state.activeSubmenuItem = null;
729
- state.activeSubmenus = [];
730
- state.submenuLevel = 0;
731
-
732
- // Remove document events
733
- document.removeEventListener('click', handleDocumentClickForSubmenu);
734
- window.removeEventListener('resize', handleWindowResizeForSubmenu);
735
- window.removeEventListener('scroll', handleWindowScrollForSubmenu);
736
- };
737
-
738
- /**
739
- * Handles document click for submenu
740
- */
741
- const handleDocumentClickForSubmenu = (e: MouseEvent): void => {
742
- if (!state.activeSubmenu) return;
743
-
744
- const submenuElement = state.activeSubmenu;
745
- const menuItemElement = state.activeSubmenuItem;
746
-
747
- // Check if click was inside submenu or parent menu item
748
- if (submenuElement.contains(e.target as Node) ||
749
- (menuItemElement && menuItemElement.contains(e.target as Node))) {
750
- return;
751
- }
752
-
753
- // Close submenu if clicked outside
754
- closeSubmenu();
755
- };
756
-
757
- /**
758
- * Handles window resize for submenu
759
- */
760
- const handleWindowResizeForSubmenu = (): void => {
761
- // Reposition open submenu on resize
762
- if (state.activeSubmenu && state.activeSubmenuItem) {
763
- positioner.positionSubmenu(state.activeSubmenu, state.activeSubmenuItem, state.submenuLevel);
764
- }
765
- };
766
-
767
- /**
768
- * Handles window scroll for submenu
769
- * Repositions the submenu to stay attached to its parent during scrolling
770
- */
771
- const handleWindowScrollForSubmenu = (): void => {
772
- // Use requestAnimationFrame to optimize scroll performance
773
- window.requestAnimationFrame(() => {
774
- // Only reposition if we have an active submenu
775
- if (state.activeSubmenu && state.activeSubmenuItem) {
776
- positioner.positionSubmenu(state.activeSubmenu, state.activeSubmenuItem, state.submenuLevel);
777
- }
778
- });
779
- };
780
-
781
- /**
782
- * Opens the menu
783
- * @param {Event} [event] - Optional event that triggered the open
784
- * @param {'mouse'|'keyboard'} [interactionType='mouse'] - Type of interaction that triggered the open
785
- */
786
- const openMenu = (event?: Event, interactionType: 'mouse' | 'keyboard' = 'mouse'): void => {
787
- if (state.visible) return;
788
-
789
- // Set keyboard navigation state based on interaction type
790
- state.keyboardNavActive = interactionType === 'keyboard';
791
-
792
- // Update state
793
- state.visible = true;
794
-
795
- // First, remove any existing document click listener
796
- document.removeEventListener('click', handleDocumentClick);
797
-
798
- // Step 1: Add the menu to the DOM if it's not already there with initial hidden state
799
- if (!component.element.parentNode) {
800
- // Apply explicit initial styling to ensure it doesn't flash
801
- component.element.classList.remove(`${component.getClass('menu--visible')}`);
802
- component.element.setAttribute('aria-hidden', 'true');
803
- component.element.style.transform = 'scaleY(0)';
804
- component.element.style.opacity = '0';
805
-
806
- // Add to DOM
807
- document.body.appendChild(component.element);
808
- }
809
-
810
- // Step 2: Position the menu (will be invisible)
811
- const anchorElement = getAnchorElement();
812
- if (anchorElement) {
813
- positioner.positionMenu(anchorElement);
814
- }
815
-
816
- // Step 3: Use a small delay to ensure DOM operations are complete
817
- setTimeout(() => {
818
- // Set attributes for accessibility
819
- component.element.setAttribute('aria-hidden', 'false');
820
-
821
- // Remove the inline styles we added
822
- component.element.style.transform = '';
823
- component.element.style.opacity = '';
824
-
825
- // Force a reflow before adding the visible class
826
- void component.element.getBoundingClientRect();
827
-
828
- // Add visible class to start the CSS transition
829
- component.element.classList.add(`${component.getClass('menu--visible')}`);
830
-
831
- // Step 4: Focus based on interaction type (after animation starts)
832
- setTimeout(() => {
833
- handleFocus(interactionType);
834
- }, 100);
835
-
836
- // Add the document click handler on the next event loop
837
- // after the current click is fully processed
838
- setTimeout(() => {
839
- if (config.closeOnClickOutside && state.visible) {
840
- document.addEventListener('click', handleDocumentClick);
841
- }
842
-
843
- // Add other document events normally
844
- if (config.closeOnEscape) {
845
- document.addEventListener('keydown', handleDocumentKeydown);
846
- }
847
- window.addEventListener('resize', handleWindowResize, { passive: true });
848
- window.addEventListener('scroll', handleWindowScroll, { passive: true });
849
- }, 0);
850
- }, 20); // Short delay for browser to process
851
-
852
- // Trigger event
853
- eventHelpers.triggerEvent('open', {}, event);
854
- };
855
-
856
- /**
857
- * Closes the menu
858
- * @param {Event} [event] - Optional event that triggered the close
859
- * @param {boolean} [restoreFocus=true] - Whether to restore focus to the anchor element
860
- * @param {boolean} [skipAnimation=false] - Whether to skip animation (for focus changes)
861
- */
862
- const closeMenu = (event?: Event, restoreFocus: boolean = true, skipAnimation: boolean = false): void => {
863
- if (!state.visible) return;
864
-
865
- // Check if we're in a tab navigation - if so, don't restore focus
866
- const isTabNavigation = document.body.hasAttribute('data-menu-tab-navigation');
867
- if (isTabNavigation) {
868
- restoreFocus = false;
869
- }
870
-
871
- // Reset keyboard navigation state on close
872
- state.keyboardNavActive = false;
873
-
874
- // Close any open submenu first
875
- closeSubmenu();
876
-
877
- // Update state
878
- state.visible = false;
879
-
880
- // Set attributes
881
- component.element.setAttribute('aria-hidden', 'true');
882
- component.element.classList.remove(`${component.getClass('menu--visible')}`);
883
-
884
- // Store anchor reference before potentially removing the menu
885
- const anchorElement = getAnchorElement();
886
-
887
- // Remove document events
888
- document.removeEventListener('click', handleDocumentClick);
889
- document.removeEventListener('keydown', handleDocumentKeydown);
890
- window.removeEventListener('resize', handleWindowResize);
891
- window.removeEventListener('scroll', handleWindowScroll);
892
-
893
- // Trigger event with added data
894
- eventHelpers.triggerEvent('close', {
895
- isFocusRelated: event instanceof FocusEvent,
896
- shouldRestoreFocus: restoreFocus,
897
- isTabNavigation: isTabNavigation || event?.key === 'Tab'
898
- }, event);
899
-
900
- // Determine animation duration - for tab navigation we want to close immediately
901
- const animationDuration = skipAnimation ? 0 : 300;
902
-
903
- // Remove from DOM after animation completes (or immediately if skipAnimation)
904
- setTimeout(() => {
905
- if (component.element.parentNode && !state.visible) {
906
- component.element.parentNode.removeChild(component.element);
907
-
908
- // Only restore focus if explicitly requested AND not in tab navigation
909
- if (restoreFocus && anchorElement && !isTabNavigation && event?.type !== 'click') {
910
- // Additional check to make sure we're not in an ongoing tab navigation
911
- if (!document.body.hasAttribute('data-menu-tab-navigation')) {
912
- requestAnimationFrame(() => {
913
- anchorElement.focus();
914
- });
915
- }
916
- }
917
- }
918
- }, animationDuration);
919
- };
920
-
921
- /**
922
- * Toggles the menu
923
- */
924
- const toggleMenu = (event?: Event, interactionType: 'mouse' | 'keyboard' = 'mouse'): void => {
925
- if (state.visible) {
926
- closeMenu(event);
927
- } else {
928
- // Determine interaction type from event
929
- if (event) {
930
- if (event instanceof KeyboardEvent) {
931
- interactionType = 'keyboard';
932
- } else if (event instanceof MouseEvent) {
933
- interactionType = 'mouse';
934
- }
935
- }
936
- openMenu(event, interactionType);
937
- }
938
- };
939
-
940
- /**
941
- * Updates the selected state of menu items
942
- * @param itemId - The ID of the item to mark as selected, or null to clear selection
943
- */
944
- const updateSelectedState = (itemId: string | null): void => {
945
- if (!component.element) return;
946
-
947
- // Get all menu items
948
- const menuItems = component.element.querySelectorAll(`.${component.getClass('menu-item')}`) as NodeListOf<HTMLElement>;
949
-
950
- // Update selected state for each item
951
- menuItems.forEach(item => {
952
- const currentItemId = item.getAttribute('data-id');
953
-
954
- if (currentItemId === itemId) {
955
- item.classList.add(`${component.getClass('menu-item--selected')}`);
956
- item.setAttribute('aria-selected', 'true');
957
- } else {
958
- item.classList.remove(`${component.getClass('menu-item--selected')}`);
959
- item.setAttribute('aria-selected', 'false');
960
- }
961
- });
962
-
963
- // Also update state
964
- state.selectedItemId = itemId;
965
- };
966
-
967
- /**
968
- * Handles document click
969
- */
970
- const handleDocumentClick = (e: MouseEvent): void => {
971
- // If we should ignore this click (happens right after opening), reset the flag and return
972
- if (ignoreNextDocumentClick) {
973
- ignoreNextDocumentClick = false;
974
- return;
975
- }
976
-
977
- // Don't close if clicked inside menu
978
- if (component.element.contains(e.target as Node)) {
979
- return;
980
- }
981
-
982
- // Check if clicked on anchor element
983
- const anchor = getAnchorElement();
984
- if (anchor && anchor.contains(e.target as Node)) {
985
- return;
986
- }
987
-
988
- // Close menu
989
- closeMenu(e);
990
- };
991
-
992
- /**
993
- * Handles document keydown
994
- */
995
- const handleDocumentKeydown = (e: KeyboardEvent): void => {
996
- if (e.key === 'Escape') {
997
- // When closing with Escape, always restore focus
998
- closeMenu(e, true);
999
- }
1000
- };
1001
-
1002
- /**
1003
- * Handles window resize
1004
- */
1005
- const handleWindowResize = (): void => {
1006
- if (state.visible) {
1007
- const anchorElement = getAnchorElement();
1008
- if (anchorElement) {
1009
- positioner.positionMenu(anchorElement);
1010
- }
1011
- }
1012
- };
1013
-
1014
- /**
1015
- * Handles window scroll
1016
- * Repositions the menu to stay attached to its anchor during scrolling
1017
- */
1018
- const handleWindowScroll = (): void => {
1019
- if (state.visible) {
1020
- // Use requestAnimationFrame to optimize scroll performance
1021
- window.requestAnimationFrame(() => {
1022
- // Reposition the main menu to stay attached to anchor when scrolling
1023
- const anchorElement = getAnchorElement();
1024
- if (anchorElement) {
1025
- positioner.positionMenu(anchorElement);
1026
- }
1027
-
1028
- // Also reposition any open submenu relative to its parent menu item
1029
- if (state.activeSubmenu && state.activeSubmenuItem) {
1030
- positioner.positionSubmenu(state.activeSubmenu, state.activeSubmenuItem, state.submenuLevel);
1031
- }
1032
- });
1033
- }
1034
- };
1035
-
1036
- /**
1037
- * Handles keydown events on the menu or submenu
1038
- */
1039
- const handleMenuKeydown = (e: KeyboardEvent): void => {
1040
- // Set keyboard navigation active flag
1041
- state.keyboardNavActive = true;
1042
-
1043
- // Determine if this event is from the main menu or a submenu
1044
- const isSubmenu = state.activeSubmenu && state.activeSubmenu.contains(e.target as Node);
1045
-
1046
- // Get the appropriate menu element
1047
- const menuElement = isSubmenu ? state.activeSubmenu : component.element;
1048
-
1049
- // Get all non-disabled menu items from the current menu
1050
- const items = Array.from(menuElement.querySelectorAll(
1051
- `.${component.getClass('menu-item')}:not(.${component.getClass('menu-item--disabled')})`
1052
- )) as HTMLElement[];
1053
-
1054
- if (items.length === 0) return;
1055
-
1056
- // Get the currently focused item index
1057
- let focusedItemIndex = -1;
1058
- const focusedElement = menuElement.querySelector(':focus') as HTMLElement;
1059
- if (focusedElement && focusedElement.classList.contains(component.getClass('menu-item'))) {
1060
- focusedItemIndex = items.indexOf(focusedElement);
1061
- }
1062
-
1063
- // Function to update tabindex and focus a specific item
1064
- const focusItem = (index: number) => {
1065
- // Set all items to tabindex -1
1066
- items.forEach(item => item.setAttribute('tabindex', '-1'));
1067
-
1068
- // Set the target item to tabindex 0 and focus it
1069
- items[index].setAttribute('tabindex', '0');
1070
- items[index].focus();
1071
- };
1072
-
1073
- switch (e.key) {
1074
- case 'ArrowDown':
1075
- case 'Down':
1076
- e.preventDefault();
1077
- // If no item is active, select the first one
1078
- if (focusedItemIndex < 0) {
1079
- focusItem(0);
1080
- } else if (focusedItemIndex < items.length - 1) {
1081
- focusItem(focusedItemIndex + 1);
1082
- } else {
1083
- // Wrap to first item
1084
- focusItem(0);
1085
- }
1086
- break;
1087
-
1088
- case 'ArrowUp':
1089
- case 'Up':
1090
- e.preventDefault();
1091
- // If no item is active, select the last one
1092
- if (focusedItemIndex < 0) {
1093
- focusItem(items.length - 1);
1094
- } else if (focusedItemIndex > 0) {
1095
- focusItem(focusedItemIndex - 1);
1096
- } else {
1097
- // Wrap to last item
1098
- focusItem(items.length - 1);
1099
- }
1100
- break;
1101
-
1102
- case 'Home':
1103
- e.preventDefault();
1104
- focusItem(0);
1105
- break;
1106
-
1107
- case 'End':
1108
- e.preventDefault();
1109
- focusItem(items.length - 1);
1110
- break;
1111
-
1112
- case 'Enter':
1113
- case ' ':
1114
- e.preventDefault();
1115
- // If an item is focused, click it
1116
- if (focusedItemIndex >= 0) {
1117
- items[focusedItemIndex].click();
1118
- }
1119
- break;
1120
-
1121
- case 'ArrowRight':
1122
- case 'Right':
1123
- e.preventDefault();
1124
- // Handle right arrow in different contexts
1125
- if (isSubmenu) {
1126
- // In a submenu, right arrow opens nested submenus
1127
- if (focusedItemIndex >= 0 && items[focusedItemIndex].classList.contains(`${component.getClass('menu-item--submenu')}`)) {
1128
- // Simulate click but specifying it's via keyboard
1129
- const itemElement = items[focusedItemIndex];
1130
- const itemIndex = parseInt(itemElement.getAttribute('data-index'), 10);
1131
-
1132
- // Get the parent submenu to find the correct data
1133
- const parentMenu = itemElement.closest(`.${component.getClass('menu--submenu')}`);
1134
- const parentItemId = parentMenu?.getAttribute('data-parent-item');
1135
-
1136
- // Find the parent item in the items array to get its submenu
1137
- const parentItem = findItemById(parentItemId);
1138
- if (parentItem && parentItem.submenu) {
1139
- const itemData = parentItem.submenu[itemIndex] as MenuItem;
1140
- handleNestedSubmenuClick(itemData, itemIndex, itemElement, true);
1141
- }
1142
- }
1143
- } else {
1144
- // In main menu, right arrow opens a submenu
1145
- if (focusedItemIndex >= 0 && items[focusedItemIndex].classList.contains(`${component.getClass('menu-item--submenu')}`)) {
1146
- // Get the correct menu item data
1147
- const itemElement = items[focusedItemIndex];
1148
- const itemIndex = parseInt(itemElement.getAttribute('data-index'), 10);
1149
- const itemData = state.items[itemIndex] as MenuItem;
1150
-
1151
- // Open submenu via keyboard
1152
- handleSubmenuClick(itemData, itemIndex, itemElement, true);
1153
- }
1154
- }
1155
- break;
1156
-
1157
- case 'ArrowLeft':
1158
- case 'Left':
1159
- e.preventDefault();
1160
- // Handle left arrow in different contexts
1161
- if (isSubmenu) {
1162
- // In a submenu, left arrow returns to the parent menu
1163
- if (state.activeSubmenuItem) {
1164
- // Store the reference to the parent item before closing the submenu
1165
- const parentItem = state.activeSubmenuItem;
1166
-
1167
- // Get the current level
1168
- const currentLevel = parseInt(
1169
- menuElement.getAttribute('data-level') || '1',
1170
- 10
1171
- );
1172
-
1173
- // Close this level of submenu
1174
- closeSubmenuAtLevel(currentLevel);
1175
-
1176
- // Focus the parent item after closing
1177
- if (parentItem) {
1178
- parentItem.setAttribute('tabindex', '0');
1179
- parentItem.focus();
1180
- }
1181
- } else {
1182
- closeSubmenu();
1183
- }
1184
- }
1185
- break;
1186
-
1187
- case 'Escape':
1188
- e.preventDefault();
1189
- if (isSubmenu) {
1190
- // In a submenu, Escape closes just the submenu
1191
- if (state.activeSubmenuItem) {
1192
- // Store the reference to the parent item before closing the submenu
1193
- const parentItem = state.activeSubmenuItem;
1194
-
1195
- // Get the current level
1196
- const currentLevel = parseInt(
1197
- menuElement.getAttribute('data-level') || '1',
1198
- 10
1199
- );
1200
-
1201
- // Close this level of submenu
1202
- closeSubmenuAtLevel(currentLevel);
1203
-
1204
- // Focus the parent item after closing
1205
- if (parentItem) {
1206
- parentItem.setAttribute('tabindex', '0');
1207
- parentItem.focus();
1208
- }
1209
- } else {
1210
- closeSubmenu();
1211
- }
1212
- } else {
1213
- // In main menu, Escape closes the entire menu and restores focus to anchor
1214
- closeMenu(e, true);
1215
- }
1216
- break;
1217
-
1218
- case 'Tab':
1219
- // Modified Tab handling - we want to close the menu and move focus to the next focusable element
1220
- e.preventDefault(); // Prevent default tab behavior
1221
-
1222
- // Find the focusable elements before closing the menu
1223
- const focusableElements = getFocusableElements();
1224
- const anchorElement = getAnchorElement();
1225
- const anchorIndex = anchorElement ? focusableElements.indexOf(anchorElement) : -1;
1226
-
1227
- // Calculate the next element to focus
1228
- let nextElementIndex = -1;
1229
- if (anchorIndex >= 0) {
1230
- nextElementIndex = e.shiftKey ?
1231
- // For Shift+Tab, go to previous element or last element if we're at the start
1232
- (anchorIndex > 0 ? anchorIndex - 1 : focusableElements.length - 1) :
1233
- // For Tab, go to next element or first element if we're at the end
1234
- (anchorIndex < focusableElements.length - 1 ? anchorIndex + 1 : 0);
1235
- }
1236
-
1237
- // Store the next element to focus before closing the menu
1238
- const nextElementToFocus = nextElementIndex >= 0 ? focusableElements[nextElementIndex] : null;
1239
-
1240
- // Create a flag that prevents focus restoration
1241
- const tabNavigationInProgress = true;
1242
-
1243
- // Close the menu with focus restoration explicitly disabled
1244
- closeMenu(e, false, true);
1245
-
1246
- // Focus the next element if found, with a slight delay to ensure menu is closed
1247
- if (nextElementToFocus) {
1248
- // Use setTimeout with a very small delay to ensure this happens after all other operations
1249
- setTimeout(() => {
1250
- // Set a flag to prevent any other focus management from interfering
1251
- document.body.setAttribute('data-menu-tab-navigation', 'true');
1252
-
1253
- // Focus the element
1254
- nextElementToFocus.focus();
1255
-
1256
- // Remove the flag after focus is set
1257
- setTimeout(() => {
1258
- document.body.removeAttribute('data-menu-tab-navigation');
1259
- }, 100);
1260
- }, 10);
1261
- }
1262
- break;
1263
- }
1264
- };
1265
-
1266
- /**
1267
- * Gets all focusable elements in the document
1268
- * Useful for Tab navigation management
1269
- */
1270
- const getFocusableElements = (): HTMLElement[] => {
1271
- // Query all potentially focusable elements
1272
- const focusableElementsString = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
1273
- const elements = document.querySelectorAll(focusableElementsString) as NodeListOf<HTMLElement>;
1274
-
1275
- // Convert to array and filter out hidden elements
1276
- return Array.from(elements).filter(element => {
1277
- return element.offsetParent !== null && !element.classList.contains('hidden');
1278
- });
1279
- };
1280
-
1281
- /**
1282
- * Find a menu item by its ID in the items array
1283
- */
1284
- const findItemById = (id: string): MenuItem | null => {
1285
- // Search in top-level items
1286
- for (const item of state.items) {
1287
- if ('id' in item && item.id === id) {
1288
- return item as MenuItem;
1289
- }
1290
-
1291
- // Search in submenu items
1292
- if ('submenu' in item && Array.isArray((item as MenuItem).submenu)) {
1293
- for (const subItem of (item as MenuItem).submenu) {
1294
- if ('id' in subItem && subItem.id === id) {
1295
- return subItem as MenuItem;
1296
- }
1297
- }
1298
- }
1299
- }
1300
-
1301
- return null;
1302
- };
1303
-
1304
- /**
1305
- * Sets up the menu
1306
- */
1307
- const initMenu = () => {
1308
- // Set up menu structure
1309
- renderMenuItems();
1310
-
1311
- // Set up keyboard navigation
1312
- component.element.addEventListener('keydown', handleMenuKeydown);
1313
-
1314
- // Position if visible
1315
- if (state.visible) {
1316
- const anchorElement = getAnchorElement();
1317
- if (anchorElement) {
1318
- positioner.positionMenu(anchorElement);
1319
- }
1320
-
1321
- // Show immediately
1322
- component.element.classList.add(`${component.getClass('menu--visible')}`);
1323
-
1324
- // Set up document events
1325
- if (config.closeOnClickOutside) {
1326
- document.addEventListener('click', handleDocumentClick);
1327
- }
1328
- if (config.closeOnEscape) {
1329
- document.addEventListener('keydown', handleDocumentKeydown);
1330
- }
1331
- window.addEventListener('resize', handleWindowResize);
1332
- window.addEventListener('scroll', handleWindowScroll);
1333
- }
1334
- };
1335
-
1336
- // Initialize after DOM is ready
1337
- setTimeout(initMenu, 0);
1338
-
1339
- // Register with lifecycle if available
1340
- if (component.lifecycle) {
1341
- const originalDestroy = component.lifecycle.destroy || (() => {});
1342
- component.lifecycle.destroy = () => {
1343
- // Clean up timers
1344
- clearHoverIntent();
1345
- clearSubmenuTimer();
1346
-
1347
- // Clean up document events
1348
- document.removeEventListener('click', handleDocumentClick);
1349
- document.removeEventListener('keydown', handleDocumentKeydown);
1350
- window.removeEventListener('resize', handleWindowResize);
1351
- window.removeEventListener('scroll', handleWindowScroll);
1352
-
1353
- // Clean up submenu events
1354
- document.removeEventListener('click', handleDocumentClickForSubmenu);
1355
- window.removeEventListener('resize', handleWindowResizeForSubmenu);
1356
- window.removeEventListener('scroll', handleWindowScrollForSubmenu);
1357
-
1358
- // Clean up submenu element
1359
- if (state.activeSubmenus.length > 0) {
1360
- state.activeSubmenus.forEach(submenu => {
1361
- if (submenu.element.parentNode) {
1362
- submenu.element.parentNode.removeChild(submenu.element);
1363
- }
1364
- });
1365
- }
1366
-
1367
- originalDestroy();
1368
- };
1369
- }
1370
-
1371
- // Return enhanced component
1372
- return {
1373
- ...component,
1374
- menu: {
1375
- open: (event, interactionType = 'mouse') => {
1376
- openMenu(event, interactionType);
1377
- return component;
1378
- },
1379
-
1380
- close: (event, restoreFocus = true, skipAnimation = false) => {
1381
- closeMenu(event, restoreFocus, skipAnimation);
1382
- return component;
1383
- },
1384
-
1385
- toggle: (event, interactionType = 'mouse') => {
1386
- toggleMenu(event, interactionType);
1387
- return component;
1388
- },
1389
-
1390
- isOpen: () => state.visible,
1391
-
1392
- setItems: (items) => {
1393
- state.items = items;
1394
- renderMenuItems();
1395
- return component;
1396
- },
1397
-
1398
- getItems: () => state.items,
1399
-
1400
- setPosition: (position) => {
1401
- state.position = position;
1402
- if (state.visible) {
1403
- const anchorElement = getAnchorElement();
1404
- if (anchorElement) {
1405
- positioner.positionMenu(anchorElement);
1406
- }
1407
- }
1408
- return component;
1409
- },
1410
-
1411
- getPosition: () => state.position,
1412
-
1413
- setSelected: (itemId: string | null) => {
1414
- updateSelectedState(itemId);
1415
- return component;
1416
- },
1417
-
1418
- getSelected: () => state.selectedItemId
1419
- }
1420
- };
1421
- };
1422
-
1423
- export default withController;