mtrl 0.3.8 → 0.3.9

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 (691) 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 +63 -0
  134. package/dist/components/progress/constants.d.ts +58 -0
  135. package/dist/components/progress/index.d.ts +3 -0
  136. package/dist/components/progress/progress.d.ts +24 -0
  137. package/dist/components/progress/types.d.ts +199 -0
  138. package/dist/components/radios/api.d.ts +37 -0
  139. package/dist/components/radios/config.d.ts +42 -0
  140. package/dist/components/radios/constants.d.ts +114 -0
  141. package/dist/components/radios/index.d.ts +3 -0
  142. package/dist/components/radios/radio.d.ts +8 -0
  143. package/dist/components/radios/radios.d.ts +8 -0
  144. package/dist/components/radios/types.d.ts +189 -0
  145. package/dist/components/search/api.d.ts +55 -0
  146. package/dist/components/search/config.d.ts +73 -0
  147. package/dist/components/search/constants.d.ts +128 -0
  148. package/{src/components/search/features/index.ts → dist/components/search/features/index.d.ts} +1 -2
  149. package/dist/components/search/features/search.d.ts +7 -0
  150. package/dist/components/search/features/states.d.ts +8 -0
  151. package/dist/components/search/features/structure.d.ts +7 -0
  152. package/dist/components/search/index.d.ts +3 -0
  153. package/dist/components/search/search.d.ts +8 -0
  154. package/dist/components/search/types.d.ts +132 -0
  155. package/dist/components/segmented-button/config.d.ts +65 -0
  156. package/dist/components/segmented-button/constants.d.ts +85 -0
  157. package/dist/components/segmented-button/index.d.ts +4 -0
  158. package/dist/components/segmented-button/segment.d.ts +15 -0
  159. package/dist/components/segmented-button/segmented-button.d.ts +49 -0
  160. package/dist/components/segmented-button/types.d.ts +257 -0
  161. package/dist/components/select/api.d.ts +8 -0
  162. package/dist/components/select/config.d.ts +18 -0
  163. package/dist/components/select/constants.d.ts +113 -0
  164. package/dist/components/select/features.d.ts +13 -0
  165. package/{src/components/select/index.ts → dist/components/select/index.d.ts} +7 -20
  166. package/dist/components/select/select.d.ts +36 -0
  167. package/dist/components/select/types.d.ts +302 -0
  168. package/dist/components/sheet/api.d.ts +37 -0
  169. package/dist/components/sheet/config.d.ts +42 -0
  170. package/dist/components/sheet/constants.d.ts +136 -0
  171. package/dist/components/sheet/features/content.d.ts +6 -0
  172. package/dist/components/sheet/features/gestures.d.ts +6 -0
  173. package/{src/components/sheet/features/index.ts → dist/components/sheet/features/index.d.ts} +1 -2
  174. package/dist/components/sheet/features/position.d.ts +7 -0
  175. package/dist/components/sheet/features/state.d.ts +6 -0
  176. package/dist/components/sheet/features/title.d.ts +6 -0
  177. package/dist/components/sheet/index.d.ts +3 -0
  178. package/dist/components/sheet/sheet.d.ts +8 -0
  179. package/dist/components/sheet/types.d.ts +250 -0
  180. package/dist/components/slider/api.d.ts +57 -0
  181. package/dist/components/slider/config.d.ts +75 -0
  182. package/dist/components/slider/constants.d.ts +138 -0
  183. package/dist/components/slider/features/controller.d.ts +9 -0
  184. package/dist/components/slider/features/handlers.d.ts +25 -0
  185. package/dist/components/slider/features/index.d.ts +3 -0
  186. package/dist/components/slider/features/range.d.ts +8 -0
  187. package/dist/components/slider/features/states.d.ts +9 -0
  188. package/dist/components/slider/index.d.ts +3 -0
  189. package/dist/components/slider/schema.d.ts +108 -0
  190. package/dist/components/slider/slider.d.ts +18 -0
  191. package/dist/components/slider/types.d.ts +170 -0
  192. package/dist/components/snackbar/api.d.ts +7 -0
  193. package/dist/components/snackbar/config.d.ts +55 -0
  194. package/dist/components/snackbar/constants.d.ts +88 -0
  195. package/dist/components/snackbar/features.d.ts +13 -0
  196. package/dist/components/snackbar/index.d.ts +3 -0
  197. package/dist/components/snackbar/position.d.ts +15 -0
  198. package/dist/components/snackbar/queue.d.ts +7 -0
  199. package/dist/components/snackbar/snackbar.d.ts +8 -0
  200. package/dist/components/snackbar/types.d.ts +172 -0
  201. package/dist/components/switch/api.d.ts +7 -0
  202. package/dist/components/switch/config.d.ts +34 -0
  203. package/dist/components/switch/constants.d.ts +88 -0
  204. package/dist/components/switch/features.d.ts +59 -0
  205. package/dist/components/switch/index.d.ts +4 -0
  206. package/dist/components/switch/switch.d.ts +8 -0
  207. package/dist/components/switch/types.d.ts +131 -0
  208. package/dist/components/tabs/api.d.ts +52 -0
  209. package/dist/components/tabs/config.d.ts +39 -0
  210. package/dist/components/tabs/constants.d.ts +142 -0
  211. package/dist/components/tabs/features.d.ts +133 -0
  212. package/dist/components/tabs/index.d.ts +11 -0
  213. package/dist/components/tabs/indicator.d.ts +49 -0
  214. package/dist/components/tabs/responsive.d.ts +38 -0
  215. package/dist/components/tabs/scroll-indicators.d.ts +18 -0
  216. package/dist/components/tabs/state.d.ts +53 -0
  217. package/dist/components/tabs/tab-api.d.ts +43 -0
  218. package/dist/components/tabs/tab.d.ts +7 -0
  219. package/dist/components/tabs/tabs.d.ts +27 -0
  220. package/dist/components/tabs/types.d.ts +390 -0
  221. package/dist/components/tabs/utils.d.ts +17 -0
  222. package/dist/components/textfield/api.d.ts +8 -0
  223. package/dist/components/textfield/config.d.ts +34 -0
  224. package/dist/components/textfield/constants.d.ts +148 -0
  225. package/{src/components/textfield/features/index.ts → dist/components/textfield/features/index.d.ts} +1 -6
  226. package/dist/components/textfield/features/leading-icon.d.ts +55 -0
  227. package/dist/components/textfield/features/placement.d.ts +28 -0
  228. package/dist/components/textfield/features/prefix-text.d.ts +54 -0
  229. package/dist/components/textfield/features/suffix-text.d.ts +54 -0
  230. package/dist/components/textfield/features/supporting-text.d.ts +59 -0
  231. package/dist/components/textfield/features/trailing-icon.d.ts +55 -0
  232. package/dist/components/textfield/index.d.ts +3 -0
  233. package/dist/components/textfield/textfield.d.ts +36 -0
  234. package/dist/components/textfield/types.d.ts +217 -0
  235. package/dist/components/timepicker/api.d.ts +24 -0
  236. package/dist/components/timepicker/clockdial.d.ts +34 -0
  237. package/dist/components/timepicker/config.d.ts +75 -0
  238. package/dist/components/timepicker/constants.d.ts +266 -0
  239. package/dist/components/timepicker/index.d.ts +4 -0
  240. package/dist/components/timepicker/render.d.ts +9 -0
  241. package/dist/components/timepicker/timepicker.d.ts +8 -0
  242. package/dist/components/timepicker/types.d.ts +284 -0
  243. package/dist/components/timepicker/utils.d.ts +74 -0
  244. package/dist/components/tooltip/api.d.ts +18 -0
  245. package/dist/components/tooltip/config.d.ts +38 -0
  246. package/dist/components/tooltip/constants.d.ts +108 -0
  247. package/dist/components/tooltip/index.d.ts +3 -0
  248. package/dist/components/tooltip/tooltip.d.ts +8 -0
  249. package/dist/components/tooltip/types.d.ts +188 -0
  250. package/dist/components/top-app-bar/config.d.ts +16 -0
  251. package/dist/components/top-app-bar/constants.d.ts +74 -0
  252. package/{src/components/top-app-bar/index.ts → dist/components/top-app-bar/index.d.ts} +2 -4
  253. package/dist/components/top-app-bar/top-app-bar.d.ts +68 -0
  254. package/dist/components/top-app-bar/types.d.ts +118 -0
  255. package/dist/constants.d.ts +30 -0
  256. package/dist/core/collection/adapters/base.d.ts +47 -0
  257. package/dist/core/collection/adapters/route.d.ts +149 -0
  258. package/dist/core/collection/collection.d.ts +131 -0
  259. package/dist/core/collection/index.d.ts +10 -0
  260. package/dist/core/collection/list-manager/config.d.ts +29 -0
  261. package/dist/core/collection/list-manager/dom-elements.d.ts +30 -0
  262. package/dist/core/collection/list-manager/index.d.ts +61 -0
  263. package/dist/core/collection/list-manager/item-measurement.d.ts +91 -0
  264. package/dist/core/collection/list-manager/renderer.d.ts +31 -0
  265. package/dist/core/collection/list-manager/scroll-tracker.d.ts +20 -0
  266. package/dist/core/collection/list-manager/state.d.ts +60 -0
  267. package/dist/core/collection/list-manager/types.d.ts +361 -0
  268. package/dist/core/collection/list-manager/utils/recycling.d.ts +34 -0
  269. package/dist/core/collection/list-manager/utils/visibility.d.ts +45 -0
  270. package/dist/core/compose/base.d.ts +31 -0
  271. package/dist/core/compose/component.d.ts +61 -0
  272. package/dist/core/compose/features/badge.d.ts +43 -0
  273. package/dist/core/compose/features/checkable.d.ts +59 -0
  274. package/dist/core/compose/features/constants.d.ts +45 -0
  275. package/dist/core/compose/features/debounce.d.ts +84 -0
  276. package/dist/core/compose/features/disabled.d.ts +47 -0
  277. package/dist/core/compose/features/events.d.ts +37 -0
  278. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  279. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  280. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  281. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  282. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  283. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  284. package/dist/core/compose/features/gestures.d.ts +86 -0
  285. package/dist/core/compose/features/icon.d.ts +71 -0
  286. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +7 -8
  287. package/dist/core/compose/features/input.d.ts +71 -0
  288. package/dist/core/compose/features/lifecycle.d.ts +61 -0
  289. package/dist/core/compose/features/position.d.ts +51 -0
  290. package/dist/core/compose/features/ripple.d.ts +61 -0
  291. package/dist/core/compose/features/size.d.ts +17 -0
  292. package/dist/core/compose/features/style.d.ts +16 -0
  293. package/dist/core/compose/features/text.d.ts +63 -0
  294. package/dist/core/compose/features/textinput.d.ts +93 -0
  295. package/dist/core/compose/features/textlabel.d.ts +57 -0
  296. package/dist/core/compose/features/throttle.d.ts +75 -0
  297. package/dist/core/compose/features/track.d.ts +42 -0
  298. package/dist/core/compose/features/variant.d.ts +17 -0
  299. package/dist/core/compose/features/withEvents.d.ts +45 -0
  300. package/dist/core/compose/index.d.ts +17 -0
  301. package/{src/core/compose/pipe.ts → dist/core/compose/pipe.d.ts} +4 -20
  302. package/dist/core/compose/utils/type-guards.d.ts +27 -0
  303. package/dist/core/composition/features/dom.d.ts +19 -0
  304. package/dist/core/composition/features/icon.d.ts +45 -0
  305. package/{src/core/composition/features/index.ts → dist/core/composition/features/index.d.ts} +1 -6
  306. package/dist/core/composition/features/label.d.ts +49 -0
  307. package/{src/core/composition/features/layout.ts → dist/core/composition/features/layout.d.ts} +8 -24
  308. package/{src/core/composition/index.ts → dist/core/composition/index.d.ts} +4 -14
  309. package/{src/core/config/component-config.ts → dist/core/config/component.d.ts} +34 -82
  310. package/dist/core/config.d.ts +130 -0
  311. package/{src/core/dom/attributes.ts → dist/core/dom/attributes.d.ts} +2 -13
  312. package/dist/core/dom/classes.d.ts +42 -0
  313. package/dist/core/dom/create.d.ts +124 -0
  314. package/dist/core/dom/events.d.ts +69 -0
  315. package/{src/core/dom/index.ts → dist/core/dom/index.d.ts} +1 -5
  316. package/dist/core/dom/utils.d.ts +42 -0
  317. package/dist/core/gestures/index.d.ts +12 -0
  318. package/dist/core/gestures/longpress.d.ts +23 -0
  319. package/dist/core/gestures/manager.d.ts +14 -0
  320. package/dist/core/gestures/pan.d.ts +12 -0
  321. package/dist/core/gestures/pinch.d.ts +14 -0
  322. package/dist/core/gestures/rotate.d.ts +14 -0
  323. package/dist/core/gestures/swipe.d.ts +20 -0
  324. package/dist/core/gestures/tap.d.ts +12 -0
  325. package/dist/core/gestures/types.d.ts +320 -0
  326. package/dist/core/gestures/utils.d.ts +57 -0
  327. package/dist/core/index.d.ts +29 -0
  328. package/dist/core/layout/array.d.ts +15 -0
  329. package/dist/core/layout/config.d.ts +32 -0
  330. package/dist/core/layout/create.d.ts +14 -0
  331. package/dist/core/layout/index.d.ts +13 -0
  332. package/dist/core/layout/jsx.d.ts +13 -0
  333. package/dist/core/layout/object.d.ts +14 -0
  334. package/dist/core/layout/processor.d.ts +28 -0
  335. package/dist/core/layout/result.d.ts +12 -0
  336. package/dist/core/layout/template.d.ts +12 -0
  337. package/dist/core/layout/types.d.ts +137 -0
  338. package/dist/core/layout/utils.d.ts +38 -0
  339. package/dist/core/state/disabled.d.ts +32 -0
  340. package/dist/core/state/emitter.d.ts +40 -0
  341. package/dist/core/state/events.d.ts +36 -0
  342. package/{src/core/state/index.ts → dist/core/state/index.d.ts} +1 -7
  343. package/dist/core/state/lifecycle.d.ts +57 -0
  344. package/dist/core/state/store.d.ts +82 -0
  345. package/dist/core/utils/background.d.ts +40 -0
  346. package/dist/core/utils/index.d.ts +23 -0
  347. package/dist/core/utils/mobile.d.ts +54 -0
  348. package/dist/core/utils/object.d.ts +13 -0
  349. package/dist/core/utils/performance.d.ts +79 -0
  350. package/dist/core/utils/validate.d.ts +73 -0
  351. package/dist/index.cjs +64 -0
  352. package/dist/index.cjs.map +285 -0
  353. package/dist/index.d.ts +15 -0
  354. package/dist/index.js +53 -14854
  355. package/dist/index.js.map +285 -0
  356. package/dist/package.json +39 -0
  357. package/dist/styles.css +7 -0
  358. package/package.json +23 -4
  359. package/.env +0 -15
  360. package/.typedocignore +0 -11
  361. package/CONTRIBUTING.md +0 -218
  362. package/DOCS.md +0 -153
  363. package/TESTING.md +0 -214
  364. package/git-user-stats.js +0 -545
  365. package/index.ts +0 -10
  366. package/src/components/badge/api.ts +0 -313
  367. package/src/components/badge/config.ts +0 -153
  368. package/src/components/badge/features.ts +0 -194
  369. package/src/components/badge/index.ts +0 -90
  370. package/src/components/badge/types.ts +0 -279
  371. package/src/components/bottom-app-bar/bottom-app-bar.ts +0 -154
  372. package/src/components/bottom-app-bar/config.ts +0 -29
  373. package/src/components/bottom-app-bar/types.ts +0 -114
  374. package/src/components/button/api.ts +0 -172
  375. package/src/components/button/button.ts +0 -112
  376. package/src/components/button/config.ts +0 -96
  377. package/src/components/button/index.ts +0 -37
  378. package/src/components/button/types.ts +0 -290
  379. package/src/components/card/api.ts +0 -222
  380. package/src/components/card/config.ts +0 -304
  381. package/src/components/card/content.ts +0 -343
  382. package/src/components/card/features.ts +0 -407
  383. package/src/components/card/types.ts +0 -497
  384. package/src/components/carousel/api.ts +0 -147
  385. package/src/components/carousel/carousel.ts +0 -242
  386. package/src/components/carousel/config.ts +0 -91
  387. package/src/components/carousel/constants.ts +0 -181
  388. package/src/components/carousel/features/drag.ts +0 -388
  389. package/src/components/carousel/features/slides.ts +0 -682
  390. package/src/components/carousel/types.ts +0 -327
  391. package/src/components/checkbox/api.ts +0 -82
  392. package/src/components/checkbox/checkbox.ts +0 -142
  393. package/src/components/checkbox/config.ts +0 -89
  394. package/src/components/checkbox/types.ts +0 -342
  395. package/src/components/chips/api.ts +0 -194
  396. package/src/components/chips/chip/api.ts +0 -233
  397. package/src/components/chips/chip/chip.ts +0 -131
  398. package/src/components/chips/chip/config.ts +0 -91
  399. package/src/components/chips/chip/index.ts +0 -3
  400. package/src/components/chips/chips.md +0 -481
  401. package/src/components/chips/chips.ts +0 -75
  402. package/src/components/chips/config.ts +0 -109
  403. package/src/components/chips/constants.ts +0 -61
  404. package/src/components/chips/features/chip-items.ts +0 -33
  405. package/src/components/chips/features/container.ts +0 -77
  406. package/src/components/chips/features/controller.ts +0 -448
  407. package/src/components/chips/features/label.ts +0 -108
  408. package/src/components/chips/index.ts +0 -11
  409. package/src/components/chips/schema.ts +0 -61
  410. package/src/components/chips/types.ts +0 -469
  411. package/src/components/datepicker/api.ts +0 -265
  412. package/src/components/datepicker/config.ts +0 -141
  413. package/src/components/datepicker/datepicker.ts +0 -341
  414. package/src/components/datepicker/index.ts +0 -12
  415. package/src/components/datepicker/render.ts +0 -450
  416. package/src/components/datepicker/types.ts +0 -397
  417. package/src/components/datepicker/utils.ts +0 -289
  418. package/src/components/dialog/api.ts +0 -317
  419. package/src/components/dialog/config.ts +0 -116
  420. package/src/components/dialog/features.ts +0 -907
  421. package/src/components/dialog/index.ts +0 -141
  422. package/src/components/dialog/types.ts +0 -553
  423. package/src/components/divider/config.ts +0 -165
  424. package/src/components/divider/features.ts +0 -233
  425. package/src/components/divider/types.ts +0 -132
  426. package/src/components/extended-fab/api.ts +0 -193
  427. package/src/components/extended-fab/config.ts +0 -140
  428. package/src/components/extended-fab/extended-fab.ts +0 -153
  429. package/src/components/extended-fab/types.ts +0 -749
  430. package/src/components/fab/api.ts +0 -137
  431. package/src/components/fab/config.ts +0 -121
  432. package/src/components/fab/types.ts +0 -615
  433. package/src/components/list/api.ts +0 -82
  434. package/src/components/list/config.ts +0 -63
  435. package/src/components/list/features.ts +0 -229
  436. package/src/components/list/index.ts +0 -67
  437. package/src/components/list/list-item.ts +0 -163
  438. package/src/components/list/list.ts +0 -108
  439. package/src/components/list/types.ts +0 -396
  440. package/src/components/list/utils.ts +0 -98
  441. package/src/components/menu/api.ts +0 -230
  442. package/src/components/menu/config.ts +0 -127
  443. package/src/components/menu/features/anchor.ts +0 -394
  444. package/src/components/menu/features/controller.ts +0 -1423
  445. package/src/components/menu/features/index.ts +0 -13
  446. package/src/components/menu/features/position.ts +0 -353
  447. package/src/components/menu/menu.ts +0 -121
  448. package/src/components/menu/types.ts +0 -392
  449. package/src/components/navigation/api.ts +0 -142
  450. package/src/components/navigation/config.ts +0 -73
  451. package/src/components/navigation/features/controller.ts +0 -273
  452. package/src/components/navigation/features/items.ts +0 -353
  453. package/src/components/navigation/index.ts +0 -11
  454. package/src/components/navigation/nav-item.ts +0 -196
  455. package/src/components/navigation/navigation.ts +0 -115
  456. package/src/components/navigation/system/core.ts +0 -302
  457. package/src/components/navigation/system/events.ts +0 -240
  458. package/src/components/navigation/system/index.ts +0 -184
  459. package/src/components/navigation/system/mobile.ts +0 -278
  460. package/src/components/navigation/system/state.ts +0 -77
  461. package/src/components/navigation/system/types.ts +0 -364
  462. package/src/components/navigation/types.ts +0 -292
  463. package/src/components/progress/api.ts +0 -178
  464. package/src/components/progress/config.ts +0 -122
  465. package/src/components/progress/index.ts +0 -4
  466. package/src/components/progress/progress.ts +0 -159
  467. package/src/components/progress/types.ts +0 -255
  468. package/src/components/radios/api.ts +0 -125
  469. package/src/components/radios/config.ts +0 -59
  470. package/src/components/radios/constants.ts +0 -19
  471. package/src/components/radios/index.ts +0 -3
  472. package/src/components/radios/radio.ts +0 -292
  473. package/src/components/radios/radios.ts +0 -43
  474. package/src/components/radios/types.ts +0 -219
  475. package/src/components/search/api.ts +0 -203
  476. package/src/components/search/config.ts +0 -86
  477. package/src/components/search/features/search.ts +0 -717
  478. package/src/components/search/features/states.ts +0 -169
  479. package/src/components/search/features/structure.ts +0 -197
  480. package/src/components/search/index.ts +0 -7
  481. package/src/components/search/search.ts +0 -52
  482. package/src/components/search/types.ts +0 -175
  483. package/src/components/segmented-button/config.ts +0 -119
  484. package/src/components/segmented-button/index.ts +0 -4
  485. package/src/components/segmented-button/segment.ts +0 -108
  486. package/src/components/segmented-button/segmented-button.ts +0 -361
  487. package/src/components/segmented-button/types.ts +0 -306
  488. package/src/components/select/api.ts +0 -78
  489. package/src/components/select/config.ts +0 -76
  490. package/src/components/select/features.ts +0 -331
  491. package/src/components/select/select.ts +0 -73
  492. package/src/components/select/types.ts +0 -355
  493. package/src/components/sheet/api.ts +0 -96
  494. package/src/components/sheet/config.ts +0 -65
  495. package/src/components/sheet/features/content.ts +0 -51
  496. package/src/components/sheet/features/gestures.ts +0 -177
  497. package/src/components/sheet/features/position.ts +0 -41
  498. package/src/components/sheet/features/state.ts +0 -116
  499. package/src/components/sheet/features/title.ts +0 -86
  500. package/src/components/sheet/index.ts +0 -12
  501. package/src/components/sheet/sheet.ts +0 -56
  502. package/src/components/sheet/types.ts +0 -294
  503. package/src/components/slider/accessibility.md +0 -59
  504. package/src/components/slider/api.ts +0 -192
  505. package/src/components/slider/config.ts +0 -118
  506. package/src/components/slider/features/controller.ts +0 -737
  507. package/src/components/slider/features/handlers.ts +0 -497
  508. package/src/components/slider/features/index.ts +0 -5
  509. package/src/components/slider/features/range.ts +0 -104
  510. package/src/components/slider/features/states.ts +0 -195
  511. package/src/components/slider/index.ts +0 -17
  512. package/src/components/slider/schema.ts +0 -141
  513. package/src/components/slider/slider.ts +0 -76
  514. package/src/components/slider/types.ts +0 -223
  515. package/src/components/snackbar/api.ts +0 -162
  516. package/src/components/snackbar/config.ts +0 -61
  517. package/src/components/snackbar/features.ts +0 -76
  518. package/src/components/snackbar/index.ts +0 -9
  519. package/src/components/snackbar/position.ts +0 -79
  520. package/src/components/snackbar/queue.ts +0 -76
  521. package/src/components/snackbar/snackbar.ts +0 -60
  522. package/src/components/snackbar/types.ts +0 -159
  523. package/src/components/switch/api.ts +0 -93
  524. package/src/components/switch/config.ts +0 -56
  525. package/src/components/switch/features.ts +0 -198
  526. package/src/components/switch/index.ts +0 -8
  527. package/src/components/switch/switch.ts +0 -52
  528. package/src/components/switch/types.ts +0 -168
  529. package/src/components/tabs/api.ts +0 -221
  530. package/src/components/tabs/config.ts +0 -73
  531. package/src/components/tabs/features.ts +0 -403
  532. package/src/components/tabs/index.ts +0 -46
  533. package/src/components/tabs/indicator.ts +0 -285
  534. package/src/components/tabs/responsive.ts +0 -144
  535. package/src/components/tabs/scroll-indicators.ts +0 -149
  536. package/src/components/tabs/state.ts +0 -186
  537. package/src/components/tabs/tab-api.ts +0 -266
  538. package/src/components/tabs/tab.ts +0 -267
  539. package/src/components/tabs/tabs.ts +0 -71
  540. package/src/components/tabs/types.ts +0 -461
  541. package/src/components/tabs/utils.ts +0 -107
  542. package/src/components/textfield/api.ts +0 -197
  543. package/src/components/textfield/config.ts +0 -52
  544. package/src/components/textfield/features/leading-icon.ts +0 -127
  545. package/src/components/textfield/features/placement.ts +0 -149
  546. package/src/components/textfield/features/prefix-text.ts +0 -107
  547. package/src/components/textfield/features/suffix-text.ts +0 -100
  548. package/src/components/textfield/features/supporting-text.ts +0 -113
  549. package/src/components/textfield/features/trailing-icon.ts +0 -108
  550. package/src/components/textfield/index.ts +0 -9
  551. package/src/components/textfield/textfield.ts +0 -92
  552. package/src/components/textfield/types.ts +0 -265
  553. package/src/components/timepicker/README.md +0 -277
  554. package/src/components/timepicker/api.ts +0 -632
  555. package/src/components/timepicker/clockdial.ts +0 -479
  556. package/src/components/timepicker/config.ts +0 -228
  557. package/src/components/timepicker/index.ts +0 -3
  558. package/src/components/timepicker/render.ts +0 -613
  559. package/src/components/timepicker/timepicker.ts +0 -117
  560. package/src/components/timepicker/types.ts +0 -336
  561. package/src/components/timepicker/utils.ts +0 -241
  562. package/src/components/tooltip/api.ts +0 -415
  563. package/src/components/tooltip/config.ts +0 -80
  564. package/src/components/tooltip/index.ts +0 -12
  565. package/src/components/tooltip/tooltip.ts +0 -60
  566. package/src/components/tooltip/types.ts +0 -223
  567. package/src/components/top-app-bar/config.ts +0 -83
  568. package/src/components/top-app-bar/top-app-bar.ts +0 -316
  569. package/src/components/top-app-bar/types.ts +0 -140
  570. package/src/core/build/constants.ts +0 -48
  571. package/src/core/build/icon.ts +0 -137
  572. package/src/core/build/ripple.ts +0 -193
  573. package/src/core/build/text.ts +0 -91
  574. package/src/core/collection/adapters/base.ts +0 -62
  575. package/src/core/collection/adapters/route.ts +0 -201
  576. package/src/core/collection/collection.ts +0 -300
  577. package/src/core/collection/index.ts +0 -57
  578. package/src/core/collection/list-manager.ts +0 -333
  579. package/src/core/compose/base.ts +0 -43
  580. package/src/core/compose/component.ts +0 -255
  581. package/src/core/compose/features/badge.ts +0 -79
  582. package/src/core/compose/features/checkable.ts +0 -155
  583. package/src/core/compose/features/disabled.ts +0 -116
  584. package/src/core/compose/features/events.ts +0 -65
  585. package/src/core/compose/features/icon.ts +0 -71
  586. package/src/core/compose/features/input.ts +0 -174
  587. package/src/core/compose/features/lifecycle.ts +0 -139
  588. package/src/core/compose/features/position.ts +0 -94
  589. package/src/core/compose/features/ripple.ts +0 -58
  590. package/src/core/compose/features/size.ts +0 -29
  591. package/src/core/compose/features/style.ts +0 -31
  592. package/src/core/compose/features/text.ts +0 -44
  593. package/src/core/compose/features/textinput.ts +0 -238
  594. package/src/core/compose/features/textlabel.ts +0 -113
  595. package/src/core/compose/features/track.ts +0 -84
  596. package/src/core/compose/features/variant.ts +0 -29
  597. package/src/core/compose/features/withEvents.ts +0 -137
  598. package/src/core/compose/index.ts +0 -54
  599. package/src/core/composition/features/dom.ts +0 -45
  600. package/src/core/composition/features/icon.ts +0 -131
  601. package/src/core/composition/features/label.ts +0 -155
  602. package/src/core/config.ts +0 -211
  603. package/src/core/dom/classes.ts +0 -132
  604. package/src/core/dom/create.ts +0 -273
  605. package/src/core/dom/events.ts +0 -209
  606. package/src/core/dom/utils.ts +0 -97
  607. package/src/core/index.ts +0 -111
  608. package/src/core/layout/README.md +0 -715
  609. package/src/core/layout/array.ts +0 -180
  610. package/src/core/layout/config.ts +0 -193
  611. package/src/core/layout/create.ts +0 -54
  612. package/src/core/layout/index.ts +0 -36
  613. package/src/core/layout/object.ts +0 -123
  614. package/src/core/layout/processor.ts +0 -106
  615. package/src/core/layout/result.ts +0 -84
  616. package/src/core/layout/types.ts +0 -180
  617. package/src/core/layout/utils.ts +0 -144
  618. package/src/core/state/disabled.ts +0 -81
  619. package/src/core/state/emitter.ts +0 -94
  620. package/src/core/state/events.ts +0 -88
  621. package/src/core/state/lifecycle.ts +0 -131
  622. package/src/core/state/store.ts +0 -197
  623. package/src/core/utils/index.ts +0 -45
  624. package/src/core/utils/mobile.ts +0 -98
  625. package/src/core/utils/object.ts +0 -41
  626. package/src/core/utils/validate.ts +0 -234
  627. package/src/index.ts +0 -90
  628. package/src/styles/abstract/_base.scss +0 -2
  629. package/src/styles/abstract/_config.scss +0 -28
  630. package/src/styles/abstract/_functions.scss +0 -124
  631. package/src/styles/abstract/_mixins.scss +0 -352
  632. package/src/styles/abstract/_theme.scss +0 -269
  633. package/src/styles/abstract/_variables.scss +0 -305
  634. package/src/styles/base/_reset.scss +0 -86
  635. package/src/styles/base/_typography.scss +0 -155
  636. package/src/styles/components/_badge.scss +0 -182
  637. package/src/styles/components/_bottom-app-bar.scss +0 -103
  638. package/src/styles/components/_button.scss +0 -224
  639. package/src/styles/components/_card.scss +0 -401
  640. package/src/styles/components/_carousel.scss +0 -645
  641. package/src/styles/components/_checkbox.scss +0 -231
  642. package/src/styles/components/_chips.scss +0 -638
  643. package/src/styles/components/_datepicker.scss +0 -358
  644. package/src/styles/components/_dialog.scss +0 -259
  645. package/src/styles/components/_divider.scss +0 -57
  646. package/src/styles/components/_extended-fab.scss +0 -267
  647. package/src/styles/components/_fab.scss +0 -225
  648. package/src/styles/components/_list.scss +0 -248
  649. package/src/styles/components/_menu.scss +0 -242
  650. package/src/styles/components/_navigation-mobile.scss +0 -244
  651. package/src/styles/components/_navigation-system.scss +0 -151
  652. package/src/styles/components/_navigation.scss +0 -407
  653. package/src/styles/components/_progress.scss +0 -151
  654. package/src/styles/components/_radios.scss +0 -187
  655. package/src/styles/components/_search.scss +0 -306
  656. package/src/styles/components/_segmented-button.scss +0 -227
  657. package/src/styles/components/_select.scss +0 -272
  658. package/src/styles/components/_sheet.scss +0 -236
  659. package/src/styles/components/_slider.scss +0 -489
  660. package/src/styles/components/_snackbar.scss +0 -211
  661. package/src/styles/components/_switch.scss +0 -298
  662. package/src/styles/components/_tabs.scss +0 -416
  663. package/src/styles/components/_textfield.scss +0 -773
  664. package/src/styles/components/_timepicker.scss +0 -451
  665. package/src/styles/components/_tooltip.scss +0 -241
  666. package/src/styles/components/_top-app-bar.scss +0 -225
  667. package/src/styles/main.scss +0 -175
  668. package/src/styles/themes/_autumn.scss +0 -105
  669. package/src/styles/themes/_base-theme.scss +0 -85
  670. package/src/styles/themes/_baseline.scss +0 -173
  671. package/src/styles/themes/_bluekhaki.scss +0 -125
  672. package/src/styles/themes/_brownbeige.scss +0 -125
  673. package/src/styles/themes/_browngreen.scss +0 -125
  674. package/src/styles/themes/_forest.scss +0 -77
  675. package/src/styles/themes/_greenbeige.scss +0 -125
  676. package/src/styles/themes/_index.scss +0 -6
  677. package/src/styles/themes/_material.scss +0 -125
  678. package/src/styles/themes/_ocean.scss +0 -77
  679. package/src/styles/themes/_sageivory.scss +0 -125
  680. package/src/styles/themes/_spring.scss +0 -77
  681. package/src/styles/themes/_summer.scss +0 -87
  682. package/src/styles/themes/_sunset.scss +0 -60
  683. package/src/styles/themes/_tealcaramel.scss +0 -125
  684. package/src/styles/themes/_winter.scss +0 -77
  685. package/src/styles/utilities/_colors.scss +0 -154
  686. package/src/styles/utilities/_flexbox.scss +0 -194
  687. package/src/styles/utilities/_layout.scss +0 -665
  688. package/src/styles/utilities/_ripple.scss +0 -79
  689. package/src/styles/utilities/_spacing.scss +0 -139
  690. package/src/styles/utilities/_typography.scss +0 -178
  691. 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;