@telesign/boreal-web-components 0.1.0-alpha.5 → 0.1.0-alpha.6

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 (475) hide show
  1. package/components-build/bds-avatar.js +1 -1
  2. package/components-build/bds-breadcrumb-item.d.ts +11 -0
  3. package/components-build/bds-breadcrumb-item.js +1 -0
  4. package/components-build/bds-breadcrumb.d.ts +11 -0
  5. package/components-build/bds-breadcrumb.js +1 -0
  6. package/components-build/bds-button-group.js +1 -1
  7. package/components-build/bds-button.js +1 -1
  8. package/components-build/bds-checkbox-button.d.ts +11 -0
  9. package/components-build/bds-checkbox-button.js +1 -0
  10. package/components-build/bds-checkbox-card.js +1 -1
  11. package/components-build/bds-checkbox-group.d.ts +11 -0
  12. package/components-build/bds-checkbox-group.js +1 -0
  13. package/components-build/bds-checkbox.js +1 -1
  14. package/components-build/bds-dialog.js +1 -1
  15. package/components-build/bds-divider.js +1 -1
  16. package/components-build/bds-flag.js +1 -1
  17. package/components-build/bds-grid-item.js +1 -1
  18. package/components-build/bds-grid.js +1 -1
  19. package/components-build/bds-list-menu-item.js +1 -1
  20. package/components-build/bds-list-menu.js +1 -1
  21. package/components-build/bds-popover.js +1 -1
  22. package/components-build/bds-radio-button.js +1 -1
  23. package/components-build/bds-radio-card.js +1 -1
  24. package/components-build/bds-radio-group.js +1 -1
  25. package/components-build/bds-radio.js +1 -1
  26. package/components-build/bds-select.d.ts +11 -0
  27. package/components-build/bds-select.js +1 -0
  28. package/components-build/bds-slider.d.ts +11 -0
  29. package/components-build/bds-slider.js +1 -0
  30. package/components-build/bds-tag.js +1 -1
  31. package/components-build/bds-text-field.js +1 -1
  32. package/components-build/bds-toggle.js +1 -1
  33. package/components-build/bds-tooltip.js +1 -1
  34. package/components-build/bds-typography.js +1 -1
  35. package/components-build/p-B6e9eIHB.js +1 -0
  36. package/components-build/p-CJBdGD_4.js +1 -0
  37. package/components-build/p-DcR7mHFE.js +1 -0
  38. package/components-build/p-DdOPD9wW.js +1 -0
  39. package/components-build/{p-BSlaf0ff.js → p-DoNZM78n.js} +1 -1
  40. package/components-build/p-DrkDx75U.js +1 -0
  41. package/components-build/p-DuBzr05c.js +1 -0
  42. package/components-build/p-PmjPRW8X.js +1 -0
  43. package/components-build/p-cgdh1LO-.js +1 -0
  44. package/components-build/p-yLNcMg2E.js +1 -0
  45. package/custom-elements.json +2723 -841
  46. package/dist/boreal-web-components/boreal-web-components.esm.js +1 -1
  47. package/dist/boreal-web-components/css/boreal.css +60 -0
  48. package/dist/boreal-web-components/css/theme-connect.css +15 -0
  49. package/dist/boreal-web-components/css/theme-engage.css +15 -0
  50. package/dist/boreal-web-components/css/theme-protect.css +15 -0
  51. package/dist/boreal-web-components/css/theme-proximus.css +15 -0
  52. package/dist/boreal-web-components/{p-7bb88855.entry.js → p-02e53626.entry.js} +1 -1
  53. package/dist/boreal-web-components/p-0357450d.system.entry.js +1 -0
  54. package/dist/boreal-web-components/{p-efe159aa.entry.js → p-1250ba53.entry.js} +1 -1
  55. package/dist/boreal-web-components/p-1911b978.entry.js +1 -0
  56. package/dist/boreal-web-components/p-1mOd23lT.system.js +1 -0
  57. package/dist/boreal-web-components/{p-48ebbfbe.system.entry.js → p-25823f7d.system.entry.js} +1 -1
  58. package/dist/boreal-web-components/p-2db71382.system.entry.js +1 -0
  59. package/dist/boreal-web-components/p-367e40f9.entry.js +1 -0
  60. package/dist/boreal-web-components/p-3946d587.entry.js +1 -0
  61. package/dist/boreal-web-components/{p-4dc01078.system.entry.js → p-44b6fe6c.system.entry.js} +1 -1
  62. package/dist/boreal-web-components/{p-024d90b0.system.entry.js → p-48712a63.system.entry.js} +1 -1
  63. package/dist/boreal-web-components/{p-2adf2cd5.entry.js → p-49ea207a.entry.js} +1 -1
  64. package/dist/boreal-web-components/{p-7aff4c78.entry.js → p-4b028406.entry.js} +1 -1
  65. package/dist/boreal-web-components/p-4b615de8.system.entry.js +1 -0
  66. package/dist/boreal-web-components/{p-e0a41cab.system.entry.js → p-4f431941.system.entry.js} +1 -1
  67. package/dist/boreal-web-components/p-524421f7.system.entry.js +1 -0
  68. package/dist/boreal-web-components/p-530c8c7f.entry.js +1 -0
  69. package/dist/boreal-web-components/p-55aeff0c.system.entry.js +1 -0
  70. package/dist/boreal-web-components/p-5c8650b3.system.entry.js +1 -0
  71. package/dist/boreal-web-components/p-5e590291.system.entry.js +1 -0
  72. package/dist/boreal-web-components/p-5ee0841f.entry.js +1 -0
  73. package/dist/boreal-web-components/{p-93066db4.system.entry.js → p-65923619.system.entry.js} +1 -1
  74. package/dist/boreal-web-components/p-66ec16d9.entry.js +1 -0
  75. package/dist/boreal-web-components/{p-e55f2a8b.entry.js → p-714bf70d.entry.js} +1 -1
  76. package/dist/boreal-web-components/p-7626338e.system.entry.js +1 -0
  77. package/dist/boreal-web-components/p-7G4h4DI7.js +1 -0
  78. package/dist/boreal-web-components/p-800fc096.system.entry.js +1 -0
  79. package/dist/boreal-web-components/p-824485ad.entry.js +1 -0
  80. package/dist/boreal-web-components/p-8dfe3a9f.entry.js +1 -0
  81. package/dist/boreal-web-components/p-90022071.entry.js +1 -0
  82. package/dist/boreal-web-components/{p-7ebc67c9.entry.js → p-9d02057d.entry.js} +1 -1
  83. package/dist/boreal-web-components/{p-wREMI3WA.js → p-CcENtewr.js} +1 -1
  84. package/dist/boreal-web-components/p-Dk9dfU9-.system.js +1 -0
  85. package/dist/boreal-web-components/p-K7DvMlRo.system.js +1 -0
  86. package/dist/boreal-web-components/{p-DYlZcFrB.system.js → p-UEj9YHof.system.js} +1 -1
  87. package/dist/boreal-web-components/{p-147d6652.system.entry.js → p-b54fe67f.system.entry.js} +1 -1
  88. package/dist/boreal-web-components/p-bbcd3a30.system.entry.js +1 -0
  89. package/dist/boreal-web-components/p-bea25d3d.system.entry.js +1 -0
  90. package/dist/boreal-web-components/p-bed506d3.entry.js +1 -0
  91. package/dist/boreal-web-components/p-bfd62034.system.entry.js +1 -0
  92. package/dist/boreal-web-components/p-c07d6b82.system.entry.js +1 -0
  93. package/dist/boreal-web-components/{p-7d3a8f61.entry.js → p-cc5c359f.entry.js} +1 -1
  94. package/dist/boreal-web-components/p-cgdh1LO-.js +1 -0
  95. package/dist/boreal-web-components/p-d2b6ae79.entry.js +1 -0
  96. package/dist/boreal-web-components/p-e21b8416.entry.js +1 -0
  97. package/dist/boreal-web-components/p-e4364a44.system.entry.js +1 -0
  98. package/dist/boreal-web-components/p-e51407b2.system.entry.js +1 -0
  99. package/dist/boreal-web-components/p-e8cde179.system.entry.js +1 -0
  100. package/dist/boreal-web-components/p-eecc3028.system.entry.js +1 -0
  101. package/dist/boreal-web-components/p-f3bd824d.system.entry.js +1 -0
  102. package/dist/boreal-web-components/p-f6eabb16.entry.js +1 -0
  103. package/dist/boreal-web-components/p-f7acf6e5.system.entry.js +1 -0
  104. package/dist/boreal-web-components/{p-6629be14.entry.js → p-f9560509.entry.js} +1 -1
  105. package/dist/boreal-web-components/p-fa128cad.system.entry.js +1 -0
  106. package/dist/boreal-web-components/p-fab1a01d.entry.js +1 -0
  107. package/dist/boreal-web-components/p-fb9ba833.entry.js +1 -0
  108. package/dist/boreal-web-components/p-fbe0c9f2.entry.js +1 -0
  109. package/dist/boreal-web-components/p-fbe88555.entry.js +1 -0
  110. package/dist/boreal-web-components/p-fc1fa966.system.entry.js +1 -0
  111. package/dist/boreal-web-components/p-fc4ffa0e.entry.js +1 -0
  112. package/dist/boreal-web-components/p-fef13445.entry.js +1 -0
  113. package/dist/boreal-web-components/p-qGhMe8Hk.js +1 -0
  114. package/dist/boreal-web-components/p-vzZJGcYF.system.js +1 -1
  115. package/dist/boreal-web-components/scss/maps/_theme-connect.scss +16 -1
  116. package/dist/boreal-web-components/scss/maps/_theme-engage.scss +16 -1
  117. package/dist/boreal-web-components/scss/maps/_theme-protect.scss +16 -1
  118. package/dist/boreal-web-components/scss/maps/_theme-proximus.scss +16 -1
  119. package/dist/boreal-web-components/scss/variables/_theme-connect.scss +16 -1
  120. package/dist/boreal-web-components/scss/variables/_theme-engage.scss +16 -1
  121. package/dist/boreal-web-components/scss/variables/_theme-protect.scss +16 -1
  122. package/dist/boreal-web-components/scss/variables/_theme-proximus.scss +16 -1
  123. package/dist/cjs/KeyboardController-B_g3peyB.js +1067 -0
  124. package/dist/cjs/Keys-DXn16dlA.js +34 -0
  125. package/dist/cjs/bds-avatar.cjs.entry.js +1 -1
  126. package/dist/cjs/bds-breadcrumb-item.cjs.entry.js +106 -0
  127. package/dist/cjs/bds-breadcrumb.cjs.entry.js +127 -0
  128. package/dist/cjs/bds-button-group.cjs.entry.js +23 -2
  129. package/dist/cjs/bds-button.cjs.entry.js +33 -6
  130. package/dist/cjs/bds-checkbox-button.cjs.entry.js +119 -0
  131. package/dist/cjs/bds-checkbox-card.cjs.entry.js +37 -23
  132. package/dist/cjs/bds-checkbox-group.cjs.entry.js +292 -0
  133. package/dist/cjs/bds-checkbox.cjs.entry.js +62 -31
  134. package/dist/cjs/bds-dialog.cjs.entry.js +3 -3
  135. package/dist/cjs/bds-divider.cjs.entry.js +1 -1
  136. package/dist/cjs/bds-flag.cjs.entry.js +1 -1
  137. package/dist/cjs/bds-grid-item.cjs.entry.js +1 -1
  138. package/dist/cjs/bds-grid.cjs.entry.js +1 -1
  139. package/dist/cjs/bds-list-menu-item.cjs.entry.js +19 -8
  140. package/dist/cjs/bds-list-menu.cjs.entry.js +42 -7
  141. package/dist/cjs/bds-popover.cjs.entry.js +13 -5
  142. package/dist/cjs/bds-radio-button.cjs.entry.js +2 -2
  143. package/dist/cjs/bds-radio-card.cjs.entry.js +2 -2
  144. package/dist/cjs/bds-radio-group.cjs.entry.js +14 -8
  145. package/dist/cjs/bds-radio.cjs.entry.js +2 -2
  146. package/dist/cjs/bds-select.cjs.entry.js +394 -0
  147. package/dist/cjs/bds-slider.cjs.entry.js +655 -0
  148. package/dist/cjs/bds-tag.cjs.entry.js +1 -1
  149. package/dist/cjs/bds-text-field.cjs.entry.js +8 -3
  150. package/dist/cjs/bds-toggle.cjs.entry.js +1 -1
  151. package/dist/cjs/bds-tooltip_2.cjs.entry.js +7 -5
  152. package/dist/cjs/boreal-web-components.cjs.js +1 -1
  153. package/dist/cjs/checkbox-form-association-DTEpHXUD.js +43 -0
  154. package/dist/cjs/{getOffset-k4ezB-eT.js → getOffset-CsDHFjPW.js} +1 -1
  155. package/dist/cjs/loader.cjs.js +1 -1
  156. package/dist/collection/collection-manifest.json +16 -10
  157. package/dist/collection/components/actions/bds-button/bds-button.css +36 -36
  158. package/dist/collection/components/actions/bds-button/bds-button.js +29 -5
  159. package/dist/collection/components/actions/bds-button-group/bds-button-group.css +1 -1
  160. package/dist/collection/components/actions/bds-button-group/bds-button-group.js +21 -2
  161. package/dist/collection/components/actions/bds-list-menu/bds-list-menu/bds-list-menu.js +136 -3
  162. package/dist/collection/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.css +5 -2
  163. package/dist/collection/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.js +20 -7
  164. package/dist/collection/components/actions/bds-toggle/bds-toggle.css +3 -3
  165. package/dist/collection/components/feedback/bds-tag/bds-tag.css +6 -6
  166. package/dist/collection/components/forms/bds-checkbox/bds-checkbox/bds-checkbox.css +132 -0
  167. package/dist/collection/components/forms/bds-checkbox/{bds-checkbox.js → bds-checkbox/bds-checkbox.js} +135 -38
  168. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.css +113 -0
  169. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.js +382 -0
  170. package/dist/collection/components/forms/{bds-checkbox-card → bds-checkbox/bds-checkbox-card}/bds-checkbox-card.css +15 -3
  171. package/dist/collection/components/forms/{bds-checkbox-card → bds-checkbox/bds-checkbox-card}/bds-checkbox-card.js +97 -51
  172. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.css +55 -0
  173. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.js +648 -0
  174. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/types/enum.js +5 -0
  175. package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/types/index.js +3 -0
  176. package/dist/collection/components/forms/bds-checkbox/utils/checkbox-form-association.js +39 -0
  177. package/dist/collection/components/forms/bds-checkbox/utils/index.js +1 -0
  178. package/dist/collection/components/forms/bds-flag/bds-flag.js +1 -1
  179. package/dist/collection/components/forms/bds-radio/{bds-radio.css → bds-radio/bds-radio.css} +3 -3
  180. package/dist/collection/components/forms/bds-radio/{bds-radio.js → bds-radio/bds-radio.js} +2 -2
  181. package/dist/collection/components/forms/{bds-radio-button → bds-radio/bds-radio-button}/bds-radio-button.css +12 -8
  182. package/dist/collection/components/forms/{bds-radio-button → bds-radio/bds-radio-button}/bds-radio-button.js +2 -2
  183. package/dist/collection/components/forms/{bds-radio-card → bds-radio/bds-radio-card}/bds-radio-card.css +2 -2
  184. package/dist/collection/components/forms/{bds-radio-card → bds-radio/bds-radio-card}/bds-radio-card.js +9 -9
  185. package/dist/collection/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/bds-radio-group.js +51 -23
  186. package/dist/collection/components/forms/bds-select/bds-select.css +23 -0
  187. package/dist/collection/components/forms/bds-select/bds-select.js +431 -0
  188. package/dist/collection/components/forms/bds-slider/bds-slider.css +213 -0
  189. package/dist/collection/components/forms/bds-slider/bds-slider.js +847 -0
  190. package/dist/collection/components/forms/bds-slider/helpers/SliderDOMController.js +61 -0
  191. package/dist/collection/components/forms/bds-slider/helpers/SliderService.js +93 -0
  192. package/dist/collection/components/forms/bds-slider/helpers/index.js +3 -0
  193. package/dist/collection/components/forms/bds-slider/helpers/parseValues.js +43 -0
  194. package/dist/collection/components/forms/bds-slider/types/ISlider.js +1 -0
  195. package/dist/collection/components/forms/bds-slider/types/ISliderOptions.js +1 -0
  196. package/dist/collection/components/forms/bds-slider/types/enum.js +16 -0
  197. package/dist/collection/components/forms/bds-slider/types/index.js +5 -0
  198. package/dist/collection/components/forms/bds-slider/types/types.js +1 -0
  199. package/dist/collection/components/forms/bds-text-field/bds-text-field.css +10 -3
  200. package/dist/collection/components/forms/bds-text-field/bds-text-field.js +47 -2
  201. package/dist/collection/components/helpers/bds-divider/bds-divider.js +1 -1
  202. package/dist/collection/components/images-icons/bds-avatar/bds-avatar.js +1 -1
  203. package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.js +3 -3
  204. package/dist/collection/components/layouts/bds-grid/bds-grid/types/IGrid.js +1 -0
  205. package/dist/collection/components/layouts/bds-grid/bds-grid/types/types.js +1 -0
  206. package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.js +9 -9
  207. package/dist/collection/components/layouts/bds-grid/bds-grid-item/types/IGridItem.js +1 -0
  208. package/dist/collection/components/layouts/bds-grid/bds-grid-item/types/types.js +1 -0
  209. package/dist/collection/components/navigation/bds-breadcrumb/bds-breadcrumb.css +21 -0
  210. package/dist/collection/components/navigation/bds-breadcrumb/bds-breadcrumb.js +292 -0
  211. package/dist/collection/components/navigation/bds-breadcrumb/types/IBreadcrumb.js +1 -0
  212. package/dist/collection/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.css +64 -0
  213. package/dist/collection/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.js +369 -0
  214. package/dist/collection/components/navigation/bds-breadcrumb-item/types/IBreadcrumbItem.js +1 -0
  215. package/dist/collection/components/overlays/bds-dialog/bds-dialog.css +1 -1
  216. package/dist/collection/components/overlays/bds-dialog/bds-dialog.js +2 -2
  217. package/dist/collection/components/overlays/bds-popover/bds-popover.js +49 -3
  218. package/dist/collection/components/overlays/bds-tooltip/bds-tooltip.js +1 -1
  219. package/dist/collection/components/titles-text/bds-typography/bds-typography.js +30 -47
  220. package/dist/collection/components/titles-text/bds-typography/utils/bds-typography-utils.js +1 -1
  221. package/dist/collection/css/boreal.css +60 -0
  222. package/dist/collection/css/theme-connect.css +15 -0
  223. package/dist/collection/css/theme-engage.css +15 -0
  224. package/dist/collection/css/theme-protect.css +15 -0
  225. package/dist/collection/css/theme-proximus.css +15 -0
  226. package/dist/collection/mixins/menu-behavior.mixin.js +1 -1
  227. package/dist/collection/scss/maps/_theme-connect.scss +16 -1
  228. package/dist/collection/scss/maps/_theme-engage.scss +16 -1
  229. package/dist/collection/scss/maps/_theme-protect.scss +16 -1
  230. package/dist/collection/scss/maps/_theme-proximus.scss +16 -1
  231. package/dist/collection/scss/variables/_theme-connect.scss +16 -1
  232. package/dist/collection/scss/variables/_theme-engage.scss +16 -1
  233. package/dist/collection/scss/variables/_theme-protect.scss +16 -1
  234. package/dist/collection/scss/variables/_theme-proximus.scss +16 -1
  235. package/dist/collection/types/index.js +0 -1
  236. package/dist/collection/utils/a11y/index.js +4 -0
  237. package/dist/collection/utils/a11y/keyboard/KeyboardController.js +566 -0
  238. package/dist/collection/utils/a11y/keyboard/_constants.js +30 -0
  239. package/dist/collection/utils/a11y/keyboard/focus/aria-activedescendant.js +41 -0
  240. package/dist/collection/utils/a11y/keyboard/focus/resolve.js +48 -0
  241. package/dist/collection/utils/a11y/keyboard/focus/roving-tabindex.js +55 -0
  242. package/dist/collection/utils/a11y/keyboard/navigation/grid-navigation.js +194 -0
  243. package/dist/collection/utils/a11y/keyboard/navigation/linear-navigation.js +137 -0
  244. package/dist/collection/utils/a11y/keyboard/types/IKeyboardController.js +1 -0
  245. package/dist/collection/utils/a11y/keyboard/types/index.js +2 -0
  246. package/dist/collection/utils/a11y/keyboard/types/types.js +1 -0
  247. package/dist/collection/utils/constants/common/Keys.js +25 -6
  248. package/dist/collection/utils/dom/elements.js +63 -0
  249. package/dist/css/boreal.css +60 -0
  250. package/dist/css/theme-connect.css +15 -0
  251. package/dist/css/theme-engage.css +15 -0
  252. package/dist/css/theme-protect.css +15 -0
  253. package/dist/css/theme-proximus.css +15 -0
  254. package/dist/esm/KeyboardController-DcnXb5F5.js +1064 -0
  255. package/dist/esm/Keys-7G4h4DI7.js +31 -0
  256. package/dist/esm/bds-avatar.entry.js +1 -1
  257. package/dist/esm/bds-breadcrumb-item.entry.js +104 -0
  258. package/dist/esm/bds-breadcrumb.entry.js +125 -0
  259. package/dist/esm/bds-button-group.entry.js +23 -2
  260. package/dist/esm/bds-button.entry.js +33 -6
  261. package/dist/esm/bds-checkbox-button.entry.js +117 -0
  262. package/dist/esm/bds-checkbox-card.entry.js +37 -23
  263. package/dist/esm/bds-checkbox-group.entry.js +290 -0
  264. package/dist/esm/bds-checkbox.entry.js +62 -31
  265. package/dist/esm/bds-dialog.entry.js +3 -3
  266. package/dist/esm/bds-divider.entry.js +1 -1
  267. package/dist/esm/bds-flag.entry.js +1 -1
  268. package/dist/esm/bds-grid-item.entry.js +1 -1
  269. package/dist/esm/bds-grid.entry.js +1 -1
  270. package/dist/esm/bds-list-menu-item.entry.js +19 -8
  271. package/dist/esm/bds-list-menu.entry.js +38 -3
  272. package/dist/esm/bds-popover.entry.js +13 -5
  273. package/dist/esm/bds-radio-button.entry.js +2 -2
  274. package/dist/esm/bds-radio-card.entry.js +2 -2
  275. package/dist/esm/bds-radio-group.entry.js +14 -8
  276. package/dist/esm/bds-radio.entry.js +2 -2
  277. package/dist/esm/bds-select.entry.js +392 -0
  278. package/dist/esm/bds-slider.entry.js +653 -0
  279. package/dist/esm/bds-tag.entry.js +1 -1
  280. package/dist/esm/bds-text-field.entry.js +8 -3
  281. package/dist/esm/bds-toggle.entry.js +1 -1
  282. package/dist/esm/bds-tooltip_2.entry.js +7 -5
  283. package/dist/esm/boreal-web-components.js +1 -1
  284. package/dist/esm/checkbox-form-association-cgdh1LO-.js +41 -0
  285. package/dist/esm/{getOffset-BYS3c13B.js → getOffset-DCLpJBcp.js} +1 -1
  286. package/dist/esm/loader.js +1 -1
  287. package/dist/esm-es5/KeyboardController-DcnXb5F5.js +1 -0
  288. package/dist/esm-es5/Keys-7G4h4DI7.js +1 -0
  289. package/dist/esm-es5/bds-avatar.entry.js +1 -1
  290. package/dist/esm-es5/bds-breadcrumb-item.entry.js +1 -0
  291. package/dist/esm-es5/bds-breadcrumb.entry.js +1 -0
  292. package/dist/esm-es5/bds-button-group.entry.js +1 -1
  293. package/dist/esm-es5/bds-button.entry.js +1 -1
  294. package/dist/esm-es5/bds-checkbox-button.entry.js +1 -0
  295. package/dist/esm-es5/bds-checkbox-card.entry.js +1 -1
  296. package/dist/esm-es5/bds-checkbox-group.entry.js +1 -0
  297. package/dist/esm-es5/bds-checkbox.entry.js +1 -1
  298. package/dist/esm-es5/bds-dialog.entry.js +1 -1
  299. package/dist/esm-es5/bds-divider.entry.js +1 -1
  300. package/dist/esm-es5/bds-flag.entry.js +1 -1
  301. package/dist/esm-es5/bds-grid-item.entry.js +1 -1
  302. package/dist/esm-es5/bds-grid.entry.js +1 -1
  303. package/dist/esm-es5/bds-list-menu-item.entry.js +1 -1
  304. package/dist/esm-es5/bds-list-menu.entry.js +1 -1
  305. package/dist/esm-es5/bds-popover.entry.js +1 -1
  306. package/dist/esm-es5/bds-radio-button.entry.js +1 -1
  307. package/dist/esm-es5/bds-radio-card.entry.js +1 -1
  308. package/dist/esm-es5/bds-radio-group.entry.js +1 -1
  309. package/dist/esm-es5/bds-radio.entry.js +1 -1
  310. package/dist/esm-es5/bds-select.entry.js +1 -0
  311. package/dist/esm-es5/bds-slider.entry.js +1 -0
  312. package/dist/esm-es5/bds-tag.entry.js +1 -1
  313. package/dist/esm-es5/bds-text-field.entry.js +1 -1
  314. package/dist/esm-es5/bds-toggle.entry.js +1 -1
  315. package/dist/esm-es5/bds-tooltip_2.entry.js +1 -1
  316. package/dist/esm-es5/boreal-web-components.js +1 -1
  317. package/dist/esm-es5/checkbox-form-association-cgdh1LO-.js +1 -0
  318. package/dist/esm-es5/{getOffset-BYS3c13B.js → getOffset-DCLpJBcp.js} +1 -1
  319. package/dist/esm-es5/loader.js +1 -1
  320. package/dist/scss/maps/_theme-connect.scss +16 -1
  321. package/dist/scss/maps/_theme-engage.scss +16 -1
  322. package/dist/scss/maps/_theme-protect.scss +16 -1
  323. package/dist/scss/maps/_theme-proximus.scss +16 -1
  324. package/dist/scss/variables/_theme-connect.scss +16 -1
  325. package/dist/scss/variables/_theme-engage.scss +16 -1
  326. package/dist/scss/variables/_theme-protect.scss +16 -1
  327. package/dist/scss/variables/_theme-proximus.scss +16 -1
  328. package/dist/types/components/actions/bds-button/bds-button.d.ts +4 -0
  329. package/dist/types/components/actions/bds-button-group/bds-button-group.d.ts +4 -0
  330. package/dist/types/components/actions/bds-list-menu/bds-list-menu/bds-list-menu.d.ts +13 -0
  331. package/dist/types/components/actions/bds-list-menu/bds-list-menu/types/IListMenu.d.ts +3 -0
  332. package/dist/types/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.d.ts +4 -1
  333. package/dist/types/components/actions/bds-list-menu/bds-list-menu-item/types/IListMenuItem.d.ts +1 -0
  334. package/dist/types/components/actions/bds-toggle/types/IToggle.d.ts +1 -1
  335. package/dist/types/components/forms/bds-checkbox/{bds-checkbox.d.ts → bds-checkbox/bds-checkbox.d.ts} +23 -10
  336. package/dist/types/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.d.ts +68 -0
  337. package/dist/types/components/forms/bds-checkbox/bds-checkbox-button/types/ICheckboxButton.d.ts +15 -0
  338. package/dist/types/components/forms/{bds-checkbox-card → bds-checkbox/bds-checkbox-card}/bds-checkbox-card.d.ts +15 -22
  339. package/dist/types/components/forms/bds-checkbox/bds-checkbox-card/types/ICheckboxCard.d.ts +9 -0
  340. package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.d.ts +86 -0
  341. package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/ICheckboxGroup.d.ts +27 -0
  342. package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/enum.d.ts +6 -0
  343. package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/index.d.ts +4 -0
  344. package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/types.d.ts +4 -0
  345. package/dist/types/components/forms/bds-checkbox/types/ICheckbox.d.ts +7 -22
  346. package/dist/types/components/forms/bds-checkbox/utils/checkbox-form-association.d.ts +10 -0
  347. package/dist/types/components/forms/bds-checkbox/utils/index.d.ts +2 -0
  348. package/dist/types/components/forms/bds-radio/{bds-radio.d.ts → bds-radio/bds-radio.d.ts} +1 -1
  349. package/dist/types/components/forms/{bds-radio-button → bds-radio/bds-radio-button}/bds-radio-button.d.ts +1 -1
  350. package/dist/types/components/forms/{bds-radio-button → bds-radio/bds-radio-button}/types/IRadioButton.d.ts +1 -0
  351. package/dist/types/components/forms/{bds-radio-card → bds-radio/bds-radio-card}/bds-radio-card.d.ts +1 -1
  352. package/dist/types/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/bds-radio-group.d.ts +13 -10
  353. package/dist/types/components/forms/bds-radio/bds-radio-group/types/IRadioGroup.d.ts +20 -0
  354. package/dist/types/components/forms/bds-select/bds-select.d.ts +98 -0
  355. package/dist/types/components/forms/bds-select/types/ISelect.d.ts +6 -0
  356. package/dist/types/components/forms/bds-slider/bds-slider.d.ts +176 -0
  357. package/dist/types/components/forms/bds-slider/helpers/SliderDOMController.d.ts +38 -0
  358. package/dist/types/components/forms/bds-slider/helpers/SliderService.d.ts +44 -0
  359. package/dist/types/components/forms/bds-slider/helpers/index.d.ts +4 -0
  360. package/dist/types/components/forms/bds-slider/helpers/parseValues.d.ts +18 -0
  361. package/dist/types/components/forms/bds-slider/types/ChangeDetail.d.ts +9 -0
  362. package/dist/types/components/forms/bds-slider/types/ISlider.d.ts +17 -0
  363. package/dist/types/components/forms/bds-slider/types/ISliderOptions.d.ts +31 -0
  364. package/dist/types/components/forms/bds-slider/types/enum.d.ts +17 -0
  365. package/dist/types/components/forms/bds-slider/types/index.d.ts +6 -0
  366. package/dist/types/components/forms/bds-slider/types/types.d.ts +5 -0
  367. package/dist/types/components/forms/bds-text-field/bds-text-field.d.ts +4 -0
  368. package/dist/types/components/navigation/bds-breadcrumb/bds-breadcrumb.d.ts +57 -0
  369. package/dist/types/components/navigation/bds-breadcrumb/types/IBreadcrumb.d.ts +17 -0
  370. package/dist/types/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.d.ts +83 -0
  371. package/dist/types/components/navigation/bds-breadcrumb-item/types/IBreadcrumbItem.d.ts +20 -0
  372. package/dist/types/components/overlays/bds-popover/bds-popover.d.ts +4 -0
  373. package/dist/types/components/titles-text/bds-typography/bds-typography.d.ts +2 -46
  374. package/dist/types/components/titles-text/bds-typography/types/ITypography.d.ts +1 -0
  375. package/dist/types/components.d.ts +1136 -277
  376. package/dist/types/types/form.d.ts +24 -0
  377. package/dist/types/types/index.d.ts +0 -1
  378. package/dist/types/utils/a11y/index.d.ts +4 -0
  379. package/dist/types/utils/a11y/keyboard/KeyboardController.d.ts +321 -0
  380. package/dist/types/utils/a11y/keyboard/_constants.d.ts +28 -0
  381. package/dist/types/utils/a11y/keyboard/focus/aria-activedescendant.d.ts +17 -0
  382. package/dist/types/utils/a11y/keyboard/focus/resolve.d.ts +23 -0
  383. package/dist/types/utils/a11y/keyboard/focus/roving-tabindex.d.ts +20 -0
  384. package/dist/types/utils/a11y/keyboard/navigation/grid-navigation.d.ts +9 -0
  385. package/dist/types/utils/a11y/keyboard/navigation/linear-navigation.d.ts +13 -0
  386. package/dist/types/utils/a11y/keyboard/types/IKeyboardController.d.ts +125 -0
  387. package/dist/types/utils/a11y/keyboard/types/index.d.ts +3 -0
  388. package/dist/types/utils/a11y/keyboard/types/types.d.ts +6 -0
  389. package/dist/types/utils/constants/common/Keys.d.ts +23 -6
  390. package/dist/types/utils/dom/elements.d.ts +41 -0
  391. package/package.json +1 -1
  392. package/components-build/p-Cbibqaz0.js +0 -1
  393. package/components-build/p-ClR-wgiT.js +0 -1
  394. package/components-build/p-N_tqtRV2.js +0 -1
  395. package/components-build/p-bAMuJ4Jx.js +0 -1
  396. package/components-build/p-c2z3tneT.js +0 -1
  397. package/components-build/p-dz9-Q2N8.js +0 -1
  398. package/components-build/p-iq2UuV7c.js +0 -1
  399. package/dist/boreal-web-components/p-06DrzEMB.system.js +0 -1
  400. package/dist/boreal-web-components/p-11e4c0ec.system.entry.js +0 -1
  401. package/dist/boreal-web-components/p-148d510f.entry.js +0 -1
  402. package/dist/boreal-web-components/p-277f142e.system.entry.js +0 -1
  403. package/dist/boreal-web-components/p-2be4aa3a.system.entry.js +0 -1
  404. package/dist/boreal-web-components/p-340896b2.entry.js +0 -1
  405. package/dist/boreal-web-components/p-3d4ce8eb.system.entry.js +0 -1
  406. package/dist/boreal-web-components/p-44a8fde5.entry.js +0 -1
  407. package/dist/boreal-web-components/p-5681b294.system.entry.js +0 -1
  408. package/dist/boreal-web-components/p-5ed75075.entry.js +0 -1
  409. package/dist/boreal-web-components/p-635f676b.system.entry.js +0 -1
  410. package/dist/boreal-web-components/p-68b83680.system.entry.js +0 -1
  411. package/dist/boreal-web-components/p-6a60d134.system.entry.js +0 -1
  412. package/dist/boreal-web-components/p-7e672cce.entry.js +0 -1
  413. package/dist/boreal-web-components/p-81401e2a.system.entry.js +0 -1
  414. package/dist/boreal-web-components/p-8289e506.entry.js +0 -1
  415. package/dist/boreal-web-components/p-82d15372.entry.js +0 -1
  416. package/dist/boreal-web-components/p-851c06f2.entry.js +0 -1
  417. package/dist/boreal-web-components/p-8a3716ec.entry.js +0 -1
  418. package/dist/boreal-web-components/p-8f4f8af0.system.entry.js +0 -1
  419. package/dist/boreal-web-components/p-DlYG-OVB.js +0 -1
  420. package/dist/boreal-web-components/p-DpnBDSUm.system.js +0 -1
  421. package/dist/boreal-web-components/p-a73659ac.system.entry.js +0 -1
  422. package/dist/boreal-web-components/p-b2ad9535.system.entry.js +0 -1
  423. package/dist/boreal-web-components/p-d4c30d2b.entry.js +0 -1
  424. package/dist/boreal-web-components/p-db0c10b9.system.entry.js +0 -1
  425. package/dist/boreal-web-components/p-dca16a3d.entry.js +0 -1
  426. package/dist/boreal-web-components/p-de846c43.system.entry.js +0 -1
  427. package/dist/boreal-web-components/p-df087e23.system.entry.js +0 -1
  428. package/dist/boreal-web-components/p-dfb4854e.entry.js +0 -1
  429. package/dist/boreal-web-components/p-dz9-Q2N8.js +0 -1
  430. package/dist/boreal-web-components/p-e09b4d14.system.entry.js +0 -1
  431. package/dist/boreal-web-components/p-e7af1e1a.entry.js +0 -1
  432. package/dist/boreal-web-components/p-f792aa74.entry.js +0 -1
  433. package/dist/cjs/Keys-rKl2za5q.js +0 -18
  434. package/dist/cjs/menu-BxKaEajh.js +0 -8
  435. package/dist/collection/components/forms/bds-checkbox/bds-checkbox.css +0 -91
  436. package/dist/esm/Keys-DlYG-OVB.js +0 -16
  437. package/dist/esm/menu-dz9-Q2N8.js +0 -6
  438. package/dist/esm-es5/Keys-DlYG-OVB.js +0 -1
  439. package/dist/esm-es5/menu-dz9-Q2N8.js +0 -1
  440. package/dist/types/components/forms/bds-checkbox-card/types/ICheckboxCard.d.ts +0 -8
  441. package/dist/types/components/forms/bds-radio-group/types/IRadioGroup.d.ts +0 -19
  442. package/dist/types/types/IFormProps.d.ts +0 -25
  443. /package/dist/collection/components/forms/{bds-checkbox-card/types/ICheckboxCard.js → bds-checkbox/bds-checkbox-button/types/ICheckboxButton.js} +0 -0
  444. /package/dist/collection/components/forms/{bds-radio-button/types/IRadioButton.js → bds-checkbox/bds-checkbox-card/types/ICheckboxCard.js} +0 -0
  445. /package/dist/collection/components/forms/{bds-radio-card/types/IRadioCard.js → bds-checkbox/bds-checkbox-group/types/ICheckboxGroup.js} +0 -0
  446. /package/dist/collection/components/forms/{bds-radio-group → bds-checkbox/bds-checkbox-group}/types/types.js +0 -0
  447. /package/dist/collection/components/{layouts/bds-grid/grid/types/IGrid.js → forms/bds-radio/bds-radio/types/IRadio.js} +0 -0
  448. /package/dist/collection/components/forms/{bds-radio-group/types/IRadioGroup.js → bds-radio/bds-radio-button/types/IRadioButton.js} +0 -0
  449. /package/dist/collection/components/forms/bds-radio/{types/IRadio.js → bds-radio-card/types/IRadioCard.js} +0 -0
  450. /package/dist/collection/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/bds-radio-group.css +0 -0
  451. /package/dist/collection/components/{layouts/bds-grid/grid-item/types/IGridItem.js → forms/bds-radio/bds-radio-group/types/IRadioGroup.js} +0 -0
  452. /package/dist/collection/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/types/enum.js +0 -0
  453. /package/dist/collection/components/{layouts/bds-grid/grid-item → forms/bds-radio/bds-radio-group}/types/types.js +0 -0
  454. /package/dist/collection/components/{layouts/bds-grid/grid/types/types.js → forms/bds-select/types/ISelect.js} +0 -0
  455. /package/dist/collection/{types/IFormProps.js → components/forms/bds-slider/types/ChangeDetail.js} +0 -0
  456. /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.css +0 -0
  457. /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/types/enum.js +0 -0
  458. /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/types/index.js +0 -0
  459. /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.css +0 -0
  460. /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/enum.js +0 -0
  461. /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/index.js +0 -0
  462. /package/dist/types/components/forms/bds-radio/{types → bds-radio/types}/IRadio.d.ts +0 -0
  463. /package/dist/types/components/forms/{bds-radio-card → bds-radio/bds-radio-card}/types/IRadioCard.d.ts +0 -0
  464. /package/dist/types/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/types/enum.d.ts +0 -0
  465. /package/dist/types/components/forms/{bds-radio-group → bds-radio/bds-radio-group}/types/types.d.ts +0 -0
  466. /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.d.ts +0 -0
  467. /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/IGrid.d.ts +0 -0
  468. /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/enum.d.ts +0 -0
  469. /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/index.d.ts +0 -0
  470. /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/types.d.ts +0 -0
  471. /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.d.ts +0 -0
  472. /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/IGridItem.d.ts +0 -0
  473. /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/enum.d.ts +0 -0
  474. /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/index.d.ts +0 -0
  475. /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/types.d.ts +0 -0
@@ -0,0 +1,1064 @@
1
+ import { K as KEYBOARD, a as KEYBOARD_MODIFIERS } from './Keys-7G4h4DI7.js';
2
+ import { L as Logger } from './Logger-iq2UuV7c.js';
3
+ import { O as ORIENTATIONS } from './orientation-CPvuMm5C.js';
4
+
5
+ // ─── Key trigger constants ────────────────────────────────────────────────────
6
+ /**
7
+ * Event type constants for key bindings.
8
+ * Use instead of hard-coded `'keydown'` / `'keyup'` strings.
9
+ */
10
+ const KEY_TRIGGERS = {
11
+ KEYDOWN: 'keydown',
12
+ KEYUP: 'keyup',
13
+ };
14
+ // ─── Orientation constants ────────────────────────────────────────────────────
15
+ const KEYBOARD_ORIENTATIONS = {
16
+ ...ORIENTATIONS,
17
+ BOTH: 'both',
18
+ };
19
+ // ─── Focus strategy constants ─────────────────────────────────────────────────
20
+ /**
21
+ * Focus management technique identifiers for `KeyboardController`.
22
+ * Use these constants instead of raw string literals when setting or comparing
23
+ * the focus strategy type.
24
+ */
25
+ const FOCUS_STRATEGY = {
26
+ ROVING_TABINDEX: 'roving-tabindex',
27
+ ARIA_ACTIVE_DESCENDANT: 'aria-activedescendant',
28
+ };
29
+ // ─── Navigation boundary constants (internal) ────────────────────────────────
30
+ const NAV_BOUNDARY = {
31
+ FIRST: 'first',
32
+ LAST: 'last',
33
+ };
34
+
35
+ function isHTMLElement$2(cell) {
36
+ return cell != null;
37
+ }
38
+ /**
39
+ * Applies the **roving tabindex** technique with real DOM focus.
40
+ *
41
+ * Order matters for key-repeat: set `tabindex="0"` and call `.focus()` on the
42
+ * new item first, then demote the previous item. This ensures focus is never
43
+ * momentarily absent between steps.
44
+ */
45
+ function applyRovingTabindex(items, activeIndex) {
46
+ const next = items[activeIndex];
47
+ if (next == null)
48
+ return;
49
+ const prevIndex = items.findIndex(item => item.getAttribute('tabindex') === '0');
50
+ next.setAttribute('tabindex', '0');
51
+ next.focus();
52
+ if (prevIndex !== -1 && prevIndex !== activeIndex) {
53
+ items[prevIndex].setAttribute('tabindex', '-1');
54
+ }
55
+ }
56
+ /**
57
+ * Initializes the **roving tabindex** technique without stealing focus.
58
+ * Only sets tabindex attributes; use during component initialization to
59
+ * avoid moving DOM focus away from the previously focused element.
60
+ */
61
+ function initRovingTabindex(items, activeIndex) {
62
+ const activeItem = items[activeIndex];
63
+ if (activeItem == null)
64
+ return;
65
+ items.forEach(item => item.setAttribute('tabindex', '-1'));
66
+ activeItem.setAttribute('tabindex', '0');
67
+ }
68
+ /** Applies roving tabindex to a 2D grid. Moves real DOM focus to `(row, col)`. */
69
+ function applyGridRovingTabindex(items, row, col) {
70
+ items
71
+ .flat()
72
+ .filter(isHTMLElement$2)
73
+ .forEach(item => item.setAttribute('tabindex', '-1'));
74
+ const cell = items[row]?.[col];
75
+ if (cell != null) {
76
+ cell.setAttribute('tabindex', '0');
77
+ cell.focus();
78
+ }
79
+ }
80
+ /** Sets tabindex attributes on a 2D grid without stealing focus; use during initialization. */
81
+ function initGridRovingTabindex(items, row, col) {
82
+ items
83
+ .flat()
84
+ .filter(isHTMLElement$2)
85
+ .forEach(item => item.setAttribute('tabindex', '-1'));
86
+ const cell = items[row]?.[col];
87
+ if (cell != null)
88
+ cell.setAttribute('tabindex', '0');
89
+ }
90
+
91
+ function isHTMLElement$1(cell) {
92
+ return cell != null;
93
+ }
94
+ /**
95
+ * Ensures every item in the list has an `id` attribute.
96
+ * Auto-generates one with a unique suffix when an item lacks it.
97
+ */
98
+ function ensureItemIds(items, prefix) {
99
+ items.forEach((item, i) => {
100
+ if (item.id.length === 0) {
101
+ item.id = `${prefix}-${i}-${Math.random().toString(36).slice(2, 7)}`;
102
+ }
103
+ });
104
+ }
105
+ /**
106
+ * Applies the **aria-activedescendant** technique to a flat list:
107
+ * - Ensures all items have `id` attributes.
108
+ * - Sets `tabindex="-1"` on every item (container holds focus).
109
+ * - Updates `aria-activedescendant` on `container` to point to the active item.
110
+ * - Scrolls the active item into view.
111
+ */
112
+ function applyAriaActiveDescendant(items, activeIndex, container, idPrefix) {
113
+ const activeItem = items[activeIndex];
114
+ if (activeItem == null)
115
+ return;
116
+ ensureItemIds(items, idPrefix);
117
+ items.forEach(item => item.setAttribute('tabindex', '-1'));
118
+ container.setAttribute('aria-activedescendant', activeItem.id);
119
+ activeItem.scrollIntoView({ block: 'nearest' });
120
+ }
121
+ /** Applies aria-activedescendant to a 2D grid container. */
122
+ function applyGridAriaActiveDescendant(items, row, col, container, idPrefix) {
123
+ const flat = items.flat().filter(isHTMLElement$1);
124
+ const cell = items[row]?.[col];
125
+ if (cell == null)
126
+ return;
127
+ ensureItemIds(flat, idPrefix);
128
+ flat.forEach(item => item.setAttribute('tabindex', '-1'));
129
+ container.setAttribute('aria-activedescendant', cell.id);
130
+ cell.scrollIntoView({ block: 'nearest' });
131
+ }
132
+
133
+ /**
134
+ * Resolves the index of the currently active item for the given strategy type.
135
+ *
136
+ * - `'roving-tabindex'`: reads from `document.activeElement`.
137
+ * - `'aria-activedescendant'`: reads `aria-activedescendant` from the container.
138
+ *
139
+ * Returns `-1` when nothing is active yet.
140
+ */
141
+ function resolveCurrentIndex(items, strategyType, container) {
142
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
143
+ const activeId = container?.getAttribute('aria-activedescendant');
144
+ if (activeId == null)
145
+ return -1;
146
+ return items.findIndex(item => item.id === activeId);
147
+ }
148
+ return items.indexOf(document.activeElement);
149
+ }
150
+ /**
151
+ * Resolves the `(row, col)` coordinates of the currently active grid cell.
152
+ *
153
+ * - `'roving-tabindex'`: matches against `document.activeElement`.
154
+ * - `'aria-activedescendant'`: reads `aria-activedescendant` from the container.
155
+ *
156
+ * Returns `{ row: -1, col: -1 }` when nothing is active yet.
157
+ */
158
+ function resolveGridCurrentPos(items, strategyType, container) {
159
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
160
+ const activeId = container?.getAttribute('aria-activedescendant');
161
+ if (activeId != null) {
162
+ for (let r = 0; r < items.length; r++) {
163
+ for (let c = 0; c < items[r].length; c++) {
164
+ if (items[r][c]?.id === activeId)
165
+ return { row: r, col: c };
166
+ }
167
+ }
168
+ }
169
+ return { row: -1, col: -1 };
170
+ }
171
+ const focused = document.activeElement;
172
+ for (let r = 0; r < items.length; r++) {
173
+ for (let c = 0; c < items[r].length; c++) {
174
+ if (items[r][c] === focused)
175
+ return { row: r, col: c };
176
+ }
177
+ }
178
+ return { row: -1, col: -1 };
179
+ }
180
+
181
+ // ─── Setup ────────────────────────────────────────────────────────────────────
182
+ /**
183
+ * Registers all key bindings for linear list navigation on the given controller.
184
+ * Called internally by `KeyboardController.setLinearNavigation`.
185
+ */
186
+ function setupLinearNavigation(config, ctrl) {
187
+ const { orientation = KEYBOARD_ORIENTATIONS.BOTH, wrap = true, focusStrategy = { type: FOCUS_STRATEGY.ROVING_TABINDEX }, initialActiveSelector, onNavigate, onItemActive, onActivate, activateKeys = [KEYBOARD.Enter, KEYBOARD.Space], onEscape, repeatThrottleMs = 100, } = config;
188
+ const strategyType = focusStrategy.type;
189
+ const idPrefix = focusStrategy.type === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT
190
+ ? (focusStrategy.idPrefix ?? `${ctrl.root?.tagName.toLowerCase() ?? 'bds-nav'}-item`)
191
+ : 'bds-nav-item';
192
+ ctrl.onStrategyResolved(strategyType, idPrefix);
193
+ const resolveItems = typeof config.items === 'function'
194
+ ? config.items
195
+ : () => Array.from(ctrl.root?.querySelectorAll(config.items) ?? []);
196
+ let prevIndex = -1;
197
+ const move = (delta, eventTarget) => {
198
+ const items = resolveItems();
199
+ if (items.length === 0)
200
+ return;
201
+ let current = resolveCurrentIndex(items, strategyType, ctrl.root);
202
+ // Fallback: in test environments document.activeElement may not reflect
203
+ // the dispatched event target, so use the event target when current is unresolved.
204
+ if (current === -1 && eventTarget != null) {
205
+ current = items.indexOf(eventTarget);
206
+ }
207
+ let next;
208
+ if (delta === NAV_BOUNDARY.FIRST) {
209
+ next = 0;
210
+ }
211
+ else if (delta === NAV_BOUNDARY.LAST) {
212
+ next = items.length - 1;
213
+ }
214
+ else {
215
+ // When current is -1 (nothing active yet), treat as position -1 so
216
+ // +1 lands on 0 and -1 lands on the last item (with wrap) or 0 (no wrap).
217
+ const raw = (current === -1 ? (delta === 1 ? -1 : 0) : current) + delta;
218
+ if (raw < 0)
219
+ next = wrap ? items.length - 1 : 0;
220
+ else if (raw >= items.length)
221
+ next = wrap ? 0 : items.length - 1;
222
+ else
223
+ next = raw;
224
+ }
225
+ // Call onItemActive before focus strategies (so component can update state).
226
+ // Only used with aria-activedescendant since roving-tabindex uses :focus-visible.
227
+ if (onItemActive != null && strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
228
+ const prevItem = prevIndex >= 0 ? items[prevIndex] : null;
229
+ onItemActive(items[next], prevItem);
230
+ }
231
+ if (onNavigate != null) {
232
+ onNavigate(next, items);
233
+ }
234
+ else if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
235
+ if (ctrl.root != null)
236
+ applyAriaActiveDescendant(items, next, ctrl.root, idPrefix);
237
+ }
238
+ else {
239
+ applyRovingTabindex(items, next);
240
+ }
241
+ prevIndex = next;
242
+ };
243
+ const target = (e) => e.target;
244
+ // Throttle for key-repeat: the browser fires repeat events every ~50 ms
245
+ // (20/sec), which is too fast for smooth visual navigation. We limit repeat
246
+ // events to at most one per `repeatThrottleMs` (default 100 ms ≈ 10/sec).
247
+ let lastRepeatTime = 0;
248
+ const withThrottle = (fn) => (e) => {
249
+ if (e.repeat && repeatThrottleMs > 0) {
250
+ const now = Date.now();
251
+ if (now - lastRepeatTime < repeatThrottleMs)
252
+ return;
253
+ lastRepeatTime = now;
254
+ }
255
+ fn(e);
256
+ };
257
+ // Arrow keys always repeat — holding a key navigates continuously.
258
+ if (orientation === KEYBOARD_ORIENTATIONS.HORIZONTAL || orientation === KEYBOARD_ORIENTATIONS.BOTH) {
259
+ ctrl.register(KEYBOARD.ArrowLeft, withThrottle(e => move(-1, target(e))), { repeat: true });
260
+ ctrl.register(KEYBOARD.ArrowRight, withThrottle(e => move(1, target(e))), { repeat: true });
261
+ }
262
+ if (orientation === KEYBOARD_ORIENTATIONS.VERTICAL || orientation === KEYBOARD_ORIENTATIONS.BOTH) {
263
+ ctrl.register(KEYBOARD.ArrowUp, withThrottle(e => move(-1, target(e))), { repeat: true });
264
+ ctrl.register(KEYBOARD.ArrowDown, withThrottle(e => move(1, target(e))), { repeat: true });
265
+ }
266
+ ctrl.register(KEYBOARD.Home, () => move(NAV_BOUNDARY.FIRST));
267
+ ctrl.register(KEYBOARD.End, () => move(NAV_BOUNDARY.LAST));
268
+ if (onActivate != null && activateKeys.length > 0)
269
+ ctrl.register(activateKeys, onActivate);
270
+ if (onEscape != null)
271
+ ctrl.register(KEYBOARD.Escape, onEscape);
272
+ if (ctrl.root != null && ctrl.abortController != null) {
273
+ const root = ctrl.root;
274
+ const { signal } = ctrl.abortController;
275
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
276
+ // Keep DOM focus on the container at all times (ARIA APG listbox pattern).
277
+ root.addEventListener('mousedown', (e) => {
278
+ e.preventDefault();
279
+ root.focus();
280
+ }, { signal });
281
+ }
282
+ // Auto-initialize focus state so the component doesn't need to manage
283
+ // tabIndex or aria-activedescendant manually after setLinearNavigation.
284
+ const items = resolveItems();
285
+ if (items.length > 0) {
286
+ let initial = resolveCurrentIndex(items, strategyType, root);
287
+ if (initial === -1 && initialActiveSelector != null) {
288
+ initial = items.findIndex(item => item.matches(initialActiveSelector));
289
+ }
290
+ // For roving-tabindex: always fall back to 0 — something must have tabindex="0".
291
+ // For aria-activedescendant: only initialize if a match was found.
292
+ const initialIndex = initial !== -1 ? initial : strategyType === FOCUS_STRATEGY.ROVING_TABINDEX ? 0 : -1;
293
+ if (initialIndex !== -1) {
294
+ if (onItemActive != null && strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
295
+ onItemActive(items[initialIndex], null);
296
+ }
297
+ prevIndex = initialIndex;
298
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
299
+ applyAriaActiveDescendant(items, initialIndex, root, idPrefix);
300
+ }
301
+ else {
302
+ initRovingTabindex(items, initialIndex);
303
+ }
304
+ }
305
+ else {
306
+ // aria-activedescendant with no pre-selected item: ensure items are not
307
+ // directly focusable (container holds focus via tabindex="0").
308
+ items.forEach(item => item.setAttribute('tabindex', '-1'));
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ function isHTMLElement(cell) {
315
+ return cell != null;
316
+ }
317
+ function getPositions(items) {
318
+ const positions = [];
319
+ items.forEach((rowItems, row) => {
320
+ rowItems.forEach((cell, col) => {
321
+ if (isHTMLElement(cell))
322
+ positions.push({ row, col, cell });
323
+ });
324
+ });
325
+ return positions;
326
+ }
327
+ function getRowPositions(items, row) {
328
+ return (items[row]
329
+ ?.map((cell, col) => (isHTMLElement(cell) ? { row, col, cell } : null))
330
+ .filter((pos) => pos != null) ?? []);
331
+ }
332
+ function getCell(items, row, col) {
333
+ if (row < 0 || col < 0)
334
+ return null;
335
+ const cell = items.at(row)?.at(col);
336
+ return isHTMLElement(cell) ? cell : null;
337
+ }
338
+ function closestColumn(positions, col) {
339
+ return positions.reduce((closest, position) => {
340
+ if (closest == null)
341
+ return position;
342
+ return Math.abs(position.col - col) < Math.abs(closest.col - col) ? position : closest;
343
+ }, null);
344
+ }
345
+ function findRowWithCells(items, startRow, step, wrap) {
346
+ const rowCount = items.length;
347
+ let row = startRow;
348
+ for (let checked = 0; checked < rowCount; checked++) {
349
+ if (row < 0 || row >= rowCount) {
350
+ if (!wrap)
351
+ return -1;
352
+ row = row < 0 ? rowCount - 1 : 0;
353
+ }
354
+ if (getRowPositions(items, row).length > 0)
355
+ return row;
356
+ row += step;
357
+ }
358
+ return -1;
359
+ }
360
+ function setupGridNavigation(config, ctrl) {
361
+ const { wrap = false, focusStrategy = { type: FOCUS_STRATEGY.ROVING_TABINDEX }, initialActiveSelector, onNavigate, onActivate, activateKeys = [KEYBOARD.Enter, KEYBOARD.Space], onPageUp, onPageDown, onEscape, repeatThrottleMs = 100, } = config;
362
+ const strategyType = focusStrategy.type;
363
+ const idPrefix = focusStrategy.type === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT
364
+ ? (focusStrategy.idPrefix ?? `${ctrl.root?.tagName.toLowerCase() ?? 'bds-grid'}-item`)
365
+ : 'bds-grid-item';
366
+ ctrl.onStrategyResolved(strategyType, idPrefix);
367
+ const resolveItems = typeof config.items === 'function' ? config.items : () => config.items;
368
+ const applyFocus = (items, row, col) => {
369
+ const cell = items[row]?.[col];
370
+ if (cell == null)
371
+ return;
372
+ if (onNavigate != null) {
373
+ onNavigate(row, col, items);
374
+ return;
375
+ }
376
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
377
+ if (ctrl.root != null)
378
+ applyGridAriaActiveDescendant(items, row, col, ctrl.root, idPrefix);
379
+ }
380
+ else {
381
+ applyGridRovingTabindex(items, row, col);
382
+ }
383
+ };
384
+ const move = (dRow, dCol) => {
385
+ const items = resolveItems();
386
+ const positions = getPositions(items);
387
+ if (positions.length === 0)
388
+ return;
389
+ let { row, col } = resolveGridCurrentPos(items, strategyType, ctrl.root);
390
+ const currentCell = getCell(items, row, col);
391
+ if (!isHTMLElement(currentCell)) {
392
+ row = positions[0].row;
393
+ col = positions[0].col;
394
+ }
395
+ if (dRow !== 0) {
396
+ const nextRow = findRowWithCells(items, row + dRow, dRow > 0 ? 1 : -1, wrap);
397
+ if (nextRow === -1)
398
+ return;
399
+ const nextCell = closestColumn(getRowPositions(items, nextRow), col);
400
+ if (nextCell != null)
401
+ applyFocus(items, nextCell.row, nextCell.col);
402
+ return;
403
+ }
404
+ const rowPositions = getRowPositions(items, row);
405
+ const currentRowIndex = rowPositions.findIndex(position => position.col === col);
406
+ const nextRowIndex = currentRowIndex + dCol;
407
+ if (nextRowIndex >= 0 && nextRowIndex < rowPositions.length) {
408
+ const nextCell = rowPositions[nextRowIndex];
409
+ applyFocus(items, nextCell.row, nextCell.col);
410
+ return;
411
+ }
412
+ if (wrap && items.length > 1) {
413
+ const nextRow = findRowWithCells(items, row + dCol, dCol > 0 ? 1 : -1, true);
414
+ if (nextRow === -1)
415
+ return;
416
+ const nextRowPositions = getRowPositions(items, nextRow);
417
+ const nextCell = dCol > 0 ? nextRowPositions[0] : nextRowPositions[nextRowPositions.length - 1];
418
+ applyFocus(items, nextCell.row, nextCell.col);
419
+ return;
420
+ }
421
+ const edgeCell = dCol > 0 ? rowPositions[rowPositions.length - 1] : rowPositions[0];
422
+ applyFocus(items, edgeCell.row, edgeCell.col);
423
+ };
424
+ const moveToEdge = (boundary) => {
425
+ const items = resolveItems();
426
+ const positions = getPositions(items);
427
+ if (positions.length === 0)
428
+ return;
429
+ let { row } = resolveGridCurrentPos(items, strategyType, ctrl.root);
430
+ if (row === -1 || getRowPositions(items, row).length === 0)
431
+ row = positions[0].row;
432
+ switch (boundary) {
433
+ case 'row-start': {
434
+ const first = getRowPositions(items, row)[0];
435
+ applyFocus(items, first.row, first.col);
436
+ break;
437
+ }
438
+ case 'row-end': {
439
+ const rowPositions = getRowPositions(items, row);
440
+ const last = rowPositions[rowPositions.length - 1];
441
+ applyFocus(items, last.row, last.col);
442
+ break;
443
+ }
444
+ case 'grid-start':
445
+ applyFocus(items, positions[0].row, positions[0].col);
446
+ break;
447
+ case 'grid-end': {
448
+ const last = positions[positions.length - 1];
449
+ applyFocus(items, last.row, last.col);
450
+ break;
451
+ }
452
+ }
453
+ };
454
+ let lastRepeatTime = 0;
455
+ const withThrottle = (fn) => (e) => {
456
+ if (e.repeat && repeatThrottleMs > 0) {
457
+ const now = Date.now();
458
+ if (now - lastRepeatTime < repeatThrottleMs)
459
+ return;
460
+ lastRepeatTime = now;
461
+ }
462
+ fn();
463
+ };
464
+ ctrl.register(KEYBOARD.ArrowLeft, withThrottle(() => move(0, -1)), { repeat: true });
465
+ ctrl.register(KEYBOARD.ArrowRight, withThrottle(() => move(0, 1)), { repeat: true });
466
+ ctrl.register(KEYBOARD.ArrowUp, withThrottle(() => move(-1, 0)), { repeat: true });
467
+ ctrl.register(KEYBOARD.ArrowDown, withThrottle(() => move(1, 0)), { repeat: true });
468
+ ctrl.register(KEYBOARD.Home, () => moveToEdge('row-start'));
469
+ ctrl.register(KEYBOARD.End, () => moveToEdge('row-end'));
470
+ ctrl.register(['control', KEYBOARD.Home], () => moveToEdge('grid-start'));
471
+ ctrl.register(['control', KEYBOARD.End], () => moveToEdge('grid-end'));
472
+ if (onActivate != null && activateKeys.length > 0)
473
+ ctrl.register(activateKeys, onActivate);
474
+ if (onPageUp != null)
475
+ ctrl.register(KEYBOARD.PageUp, onPageUp);
476
+ if (onPageDown != null)
477
+ ctrl.register(KEYBOARD.PageDown, onPageDown);
478
+ if (onEscape != null)
479
+ ctrl.register(KEYBOARD.Escape, onEscape);
480
+ if (ctrl.root != null && ctrl.abortController != null) {
481
+ const items = resolveItems();
482
+ const positions = getPositions(items);
483
+ if (positions.length > 0) {
484
+ const selected = initialActiveSelector != null
485
+ ? positions.find(position => position.cell.matches(initialActiveSelector))
486
+ : undefined;
487
+ const initial = selected ?? positions[0];
488
+ if (strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
489
+ applyGridAriaActiveDescendant(items, initial.row, initial.col, ctrl.root, idPrefix);
490
+ const root = ctrl.root;
491
+ const { signal } = ctrl.abortController;
492
+ root.addEventListener('mousedown', (e) => {
493
+ e.preventDefault();
494
+ root.focus();
495
+ }, { signal });
496
+ }
497
+ else {
498
+ initGridRovingTabindex(items, initial.row, initial.col);
499
+ }
500
+ }
501
+ }
502
+ }
503
+
504
+ // ─── Modifier constants ───────────────────────────────────────────────────────
505
+ /** Maps each modifier key name to the `KeyboardEvent` property that indicates it is active. */
506
+ const MODIFIER_CHECKS = {
507
+ alt: (e) => e.altKey,
508
+ control: (e) => e.ctrlKey,
509
+ meta: (e) => e.metaKey,
510
+ shift: (e) => e.shiftKey,
511
+ };
512
+ /** Returns `true` when `k` is the normalized name of a modifier key (`alt`, `control`, `meta`, `shift`). */
513
+ function isModifier(k) {
514
+ return k in MODIFIER_CHECKS;
515
+ }
516
+ /**
517
+ * Shorthand aliases accepted in key expressions.
518
+ * Normalized before any binding lookup so callers can use familiar names.
519
+ *
520
+ * | Alias | Resolves to |
521
+ * |-------|-------------|
522
+ * | `ctrl` | `control` |
523
+ * | `cmd` / `win` | `meta` |
524
+ * | `option` | `alt` |
525
+ * | `space` | `' '` (Space character) |
526
+ */
527
+ const KEY_ALIASES = {
528
+ ctrl: KEYBOARD_MODIFIERS.Control,
529
+ cmd: KEYBOARD_MODIFIERS.Meta,
530
+ win: KEYBOARD_MODIFIERS.Meta,
531
+ option: KEYBOARD_MODIFIERS.Alt,
532
+ space: KEYBOARD.Space,
533
+ };
534
+ // ─── Key binding helpers ──────────────────────────────────────────────────────
535
+ /**
536
+ * Normalizes a raw key token to its canonical lowercase form.
537
+ *
538
+ * - Trims surrounding whitespace (except a literal space character).
539
+ * - Converts to lowercase.
540
+ * - Applies {@link KEY_ALIASES} (`'ctrl'` → `'control'`, `'space'` → `' '`).
541
+ *
542
+ * @param raw - A single key token as typed by the developer (e.g. `'Ctrl'`, `'ArrowLeft'`).
543
+ * @returns The canonical lowercase key string used internally for binding lookup.
544
+ */
545
+ function normalizeKey(raw) {
546
+ if (raw === ' ')
547
+ return ' '; // preserve the Space key — trim() would erase it
548
+ const lower = raw.trim().toLowerCase();
549
+ return KEY_ALIASES[lower] ?? lower;
550
+ }
551
+ /**
552
+ * Splits a key expression into separate modifier and regular-key arrays.
553
+ *
554
+ * Accepts either a `'+'`-separated combination string (`'ctrl+pagedown'`) or
555
+ * an array of tokens (`['control', 'pagedown']`). Each token is normalized via
556
+ * {@link normalizeKey} before classification.
557
+ *
558
+ * @param input - A key combination string or array of key tokens.
559
+ * @returns An object with `keys` (non-modifier keys) and `modifiers` (modifier keys).
560
+ */
561
+ function parseKeys(input) {
562
+ const tokens = (Array.isArray(input) ? input : input.split('+')).map(normalizeKey);
563
+ const modifiers = tokens.filter(k => isModifier(k));
564
+ const keys = tokens.filter(k => !isModifier(k));
565
+ return { keys, modifiers };
566
+ }
567
+ /**
568
+ * Produces a deterministic lookup key for a `Map` from a set of regular keys and modifiers.
569
+ *
570
+ * Modifiers are sorted in declaration order (`alt`, `control`, `meta`, `shift`) and
571
+ * regular keys are sorted alphabetically, so `'ctrl+shift+s'` and `'shift+ctrl+s'`
572
+ * always produce the same string.
573
+ *
574
+ * @param keys - Non-modifier key names (already normalized).
575
+ * @param modifiers - Modifier key names (already normalized).
576
+ * @returns A canonical string like `'control+shift+s'` used as the `Map` key.
577
+ */
578
+ function createCombinationKey(keys, modifiers) {
579
+ const sortedKeys = [...keys].sort();
580
+ const sortedMods = Object.keys(MODIFIER_CHECKS).filter(m => modifiers.includes(m));
581
+ return [...sortedMods, ...sortedKeys].join('+');
582
+ }
583
+ // ─── Controller ───────────────────────────────────────────────────────────────
584
+ /**
585
+ * Manages keyboard interactions on a Stencil host element.
586
+ *
587
+ * Provides a **fluent API** to register key bindings and high-level navigation
588
+ * patterns following [ARIA APG](https://www.w3.org/WAI/ARIA/apg/) conventions.
589
+ * All event listeners are cleaned up automatically via a single `AbortController`
590
+ * — no manual `removeEventListener` is ever needed.
591
+ *
592
+ * ---
593
+ *
594
+ * ### Lifecycle
595
+ *
596
+ * ```ts
597
+ * private readonly _keyboard = new KeyboardController();
598
+ *
599
+ * componentDidLoad() {
600
+ * this._keyboard.attach(this.el)
601
+ * .setLinearNavigation({ items: 'button', orientation: 'horizontal' });
602
+ * }
603
+ *
604
+ * disconnectedCallback() {
605
+ * this._keyboard.detach();
606
+ * }
607
+ * ```
608
+ *
609
+ * ---
610
+ *
611
+ * ### Key Binding API
612
+ *
613
+ * | Method | Description |
614
+ * |--------|-------------|
615
+ * | `set(key, handler, options?)` | Register a single key, chord, or array of independent keys |
616
+ * | `setActivateHandler(handler)` | Register `Enter` **and** `Space` with one call (ARIA activation pattern) |
617
+ * | `onFocusOut(handler)` | Register a `focusout` listener that auto-removes on `detach()` |
618
+ * | `remove(key)` | Unregister a binding at runtime |
619
+ *
620
+ * ---
621
+ *
622
+ * ### Navigation API
623
+ *
624
+ * | Method | Description |
625
+ * |--------|-------------|
626
+ * | `setLinearNavigation(config)` | Arrow keys + `Home`/`End` for flat list navigation |
627
+ * | `setGridNavigation(config)` | 2D arrow-key navigation for grids, calendars, and tables |
628
+ * | `rovingTabindex(items, index)` | Manually apply roving tabindex to a list |
629
+ * | `ariaActiveDescendant(items, index, options?)` | Manually apply `aria-activedescendant` to a list |
630
+ * | `applyFocus(items, index)` | Strategy-agnostic focus — delegates to whichever strategy was configured |
631
+ */
632
+ class KeyboardController {
633
+ /**
634
+ * @param defaults - Default options applied to every binding registered via `set()`.
635
+ * Defaults to `{ preventDefault: true }` to prevent browser scrolling on arrow keys.
636
+ */
637
+ constructor(defaults = { preventDefault: true }) {
638
+ this._bindings = new Map();
639
+ this._allowedKeys = new Set();
640
+ this._pressedKeys = new Set();
641
+ this._logger = new Logger();
642
+ this._abortController = null;
643
+ this._root = null;
644
+ this._strategyType = FOCUS_STRATEGY.ROVING_TABINDEX;
645
+ this._idPrefix = 'bds-nav-item';
646
+ this._defaults = defaults;
647
+ }
648
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
649
+ /**
650
+ * Attaches the controller to a host element and starts listening for `keydown`
651
+ * and `keyup` events. If already attached, detaches first.
652
+ *
653
+ * Call in `componentDidLoad()`. Supports method chaining.
654
+ *
655
+ * @param el - The Stencil host element (`this.el`) to attach to.
656
+ * @returns `this` for chaining additional configuration calls.
657
+ *
658
+ * @example
659
+ * ```ts
660
+ * componentDidLoad() {
661
+ * this._keyboard.attach(this.el).setLinearNavigation({ items: 'button' });
662
+ * }
663
+ * ```
664
+ */
665
+ attach(el) {
666
+ this.detach();
667
+ this._root = el;
668
+ this._abortController = new AbortController();
669
+ const { signal } = this._abortController;
670
+ const onKey = (e) => this._dispatchKeyEvent(e);
671
+ const onClear = () => this._pressedKeys.clear();
672
+ el.addEventListener(KEY_TRIGGERS.KEYDOWN, onKey, { signal });
673
+ el.addEventListener(KEY_TRIGGERS.KEYUP, onKey, { signal });
674
+ // Clear pressed-key state when the element loses focus to prevent stale
675
+ // combinations after Tab+key sequences.
676
+ el.addEventListener('focusout', onClear, { signal });
677
+ // Also clear when the window itself loses focus (e.g. Alt+Tab to another app).
678
+ globalThis.addEventListener('blur', onClear, { signal });
679
+ return this;
680
+ }
681
+ /**
682
+ * Removes all event listeners registered by this controller and clears
683
+ * internal key-press state. Safe to call multiple times.
684
+ *
685
+ * Call in `disconnectedCallback()`.
686
+ *
687
+ * @example
688
+ * ```ts
689
+ * disconnectedCallback() {
690
+ * this._keyboard.detach();
691
+ * }
692
+ * ```
693
+ */
694
+ detach() {
695
+ this._abortController?.abort();
696
+ this._abortController = null;
697
+ this._root = null;
698
+ this._pressedKeys.clear();
699
+ }
700
+ _ensureAttached(method) {
701
+ if (this._root != null && this._abortController != null)
702
+ return true;
703
+ this._logger.warn('KeyboardController', `${method}() was called before attach(). Call attach(el) first.`);
704
+ return false;
705
+ }
706
+ _rebuildAllowedKeys() {
707
+ this._allowedKeys.clear();
708
+ this._bindings.forEach(binding => {
709
+ for (const k of [...binding.keys, ...binding.modifiers]) {
710
+ this._allowedKeys.add(k);
711
+ }
712
+ });
713
+ }
714
+ // ─── Private ─────────────────────────────────────────────────────────────
715
+ _dispatchKeyEvent(event) {
716
+ const key = event.key.toLowerCase();
717
+ const isDown = event.type === KEY_TRIGGERS.KEYDOWN;
718
+ const isUp = event.type === KEY_TRIGGERS.KEYUP;
719
+ // Fast-path: ignore keys that have no registered binding
720
+ if (!this._allowedKeys.has(key)) {
721
+ if (!isModifier(key) && isUp)
722
+ this._pressedKeys.delete(key);
723
+ return;
724
+ }
725
+ if (!isModifier(key) && isDown) {
726
+ this._pressedKeys.add(key);
727
+ }
728
+ const activeModifiers = Object.keys(MODIFIER_CHECKS).filter(mod => MODIFIER_CHECKS[mod](event));
729
+ const combination = createCombinationKey(Array.from(this._pressedKeys), activeModifiers);
730
+ let binding = this._bindings.get(combination);
731
+ // Fallback: when two non-modifier keys are "simultaneously" pressed (e.g. ArrowDown held
732
+ // while ArrowUp keyup hasn't fired yet), the multi-key combination won't match any
733
+ // single-key binding. Try matching just the current key + active modifiers.
734
+ if (binding === undefined && this._pressedKeys.size > 1) {
735
+ binding = this._bindings.get(createCombinationKey([key], activeModifiers));
736
+ }
737
+ if (binding !== undefined) {
738
+ // Prevent default for all matching keydown events — including auto-repeat —
739
+ // so held keys (e.g. Space on a checkbox) never trigger browser scroll even
740
+ // when the handler is intentionally skipped on repeat.
741
+ if (binding.options.preventDefault && isDown)
742
+ event.preventDefault();
743
+ if (this._matchesTrigger(binding, event)) {
744
+ // Also prevent default on keyup when the binding fires.
745
+ if (binding.options.preventDefault && isUp)
746
+ event.preventDefault();
747
+ if (binding.options.stopPropagation)
748
+ event.stopPropagation();
749
+ binding.handler(event);
750
+ }
751
+ }
752
+ if (!isModifier(key) && isUp) {
753
+ this._pressedKeys.delete(key);
754
+ }
755
+ }
756
+ _matchesTrigger(binding, event) {
757
+ const triggers = binding.options.triggers ?? [KEY_TRIGGERS.KEYDOWN];
758
+ if (event.type === KEY_TRIGGERS.KEYDOWN && triggers.includes(KEY_TRIGGERS.KEYDOWN)) {
759
+ return !event.repeat || Boolean(binding.options.repeat);
760
+ }
761
+ if (event.type === KEY_TRIGGERS.KEYUP && triggers.includes(KEY_TRIGGERS.KEYUP)) {
762
+ return true;
763
+ }
764
+ return false;
765
+ }
766
+ // ─── Public API ──────────────────────────────────────────────────────────
767
+ /**
768
+ * Registers a key binding. Supports single keys, chord combinations, and arrays
769
+ * of independent keys. Returns `this` for chaining.
770
+ *
771
+ * **Accepted `key` formats:**
772
+ *
773
+ * | Format | Example | Description |
774
+ * |--------|---------|-------------|
775
+ * | Single key | `KEYBOARD.Escape` | One key, no modifier |
776
+ * | Combination string | `'ctrl+pagedown'` | `+`-separated chord |
777
+ * | Chord array | `['meta', 'k']` | Same as combination string |
778
+ * | Independent keys | `[KEYBOARD.Enter, KEYBOARD.Space]` | Two separate bindings sharing one handler |
779
+ *
780
+ * @param key - Key expression: a single string, a `'+'`-separated chord, or an array of tokens.
781
+ * @param handler - Callback invoked when the key combination is triggered.
782
+ * @param options - Optional overrides for `preventDefault`, `stopPropagation`, `repeat`, and `triggers`.
783
+ * @returns `this` for chaining.
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * this._keyboard
788
+ * .attach(this.el)
789
+ * .set(KEYBOARD.Escape, () => this.close())
790
+ * .set('ctrl+k', () => this.openSearch(), { stopPropagation: true })
791
+ * .set([KEYBOARD.Enter, KEYBOARD.Space], () => this.activate());
792
+ * ```
793
+ */
794
+ set(key, handler, options) {
795
+ if (Array.isArray(key)) {
796
+ const normalized = key.map(normalizeKey);
797
+ const hasModifier = normalized.some(k => isModifier(k));
798
+ if (!hasModifier) {
799
+ normalized.forEach(k => this.set(k, handler, options));
800
+ return this;
801
+ }
802
+ }
803
+ const { keys, modifiers } = parseKeys(key);
804
+ const combination = createCombinationKey(keys, modifiers);
805
+ const mergedOptions = { ...this._defaults, ...options };
806
+ for (const k of [...keys, ...modifiers]) {
807
+ this._allowedKeys.add(k);
808
+ }
809
+ this._bindings.set(combination, { keys, modifiers, handler, options: mergedOptions });
810
+ return this;
811
+ }
812
+ /**
813
+ * Registers the same handler for `Enter` **and** `Space` in a single call.
814
+ *
815
+ * This follows the [ARIA widget activation pattern](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/)
816
+ * where both keys are expected to activate an interactive element.
817
+ *
818
+ * @param handler - Callback invoked on `Enter` or `Space` keydown.
819
+ * @param options - Optional overrides forwarded to the underlying `set()` calls.
820
+ * @returns `this` for chaining.
821
+ *
822
+ * @example
823
+ * ```ts
824
+ * this._keyboard.attach(this.el).setActivateHandler(() => this.toggle());
825
+ * ```
826
+ */
827
+ setActivateHandler(handler, options) {
828
+ return this.set([KEYBOARD.Enter, KEYBOARD.Space], handler, options);
829
+ }
830
+ /**
831
+ * Registers a `focusout` listener on the attached root element.
832
+ *
833
+ * Unlike `blur`, `focusout` bubbles — it fires when focus leaves **any descendant**,
834
+ * not just the root itself. The listener is removed automatically when `detach()` is called.
835
+ *
836
+ * @param handler - Callback invoked when focus leaves the component subtree.
837
+ * @returns `this` for chaining.
838
+ *
839
+ * @example
840
+ * ```ts
841
+ * this._keyboard.attach(this.el).onFocusOut(() => this.closeDropdown());
842
+ * ```
843
+ */
844
+ onFocusOut(handler) {
845
+ if (this._root == null || this._abortController == null)
846
+ return this;
847
+ this._root.addEventListener('focusout', handler, {
848
+ signal: this._abortController.signal,
849
+ });
850
+ return this;
851
+ }
852
+ /**
853
+ * Applies the **roving tabindex** focus technique to a list of elements.
854
+ *
855
+ * Sets `tabindex="-1"` on all items and `tabindex="0"` + `.focus()` on the
856
+ * item at `activeIndex`. The browser's native Tab key then cycles only through
857
+ * the single active item, keeping focus contained in the widget.
858
+ *
859
+ * `activeIndex` is clamped to `[0, items.length - 1]`.
860
+ *
861
+ * @param items - The full list of navigable elements.
862
+ * @param activeIndex - Index of the element that should receive focus.
863
+ * @returns `this` for chaining.
864
+ *
865
+ * @example
866
+ * ```ts
867
+ * // Manually jump to the last item
868
+ * this._keyboard.rovingTabindex(buttons, buttons.length - 1);
869
+ * ```
870
+ */
871
+ rovingTabindex(items, activeIndex) {
872
+ if (items.length === 0)
873
+ return this;
874
+ const clamped = Math.max(0, Math.min(activeIndex, items.length - 1));
875
+ applyRovingTabindex(items, clamped);
876
+ return this;
877
+ }
878
+ /**
879
+ * Applies the **`aria-activedescendant`** focus technique to a list of elements.
880
+ *
881
+ * DOM focus stays on the container at all times. Navigation updates the
882
+ * container's `aria-activedescendant` attribute to point to the active item's `id`.
883
+ * Item `id`s are auto-generated using `idPrefix` when missing.
884
+ *
885
+ * `activeIndex` is clamped to `[0, items.length - 1]`.
886
+ *
887
+ * @param items - The full list of navigable option/grid-cell elements.
888
+ * @param activeIndex - Index of the logically active item.
889
+ * @param options.idPrefix - Prefix used to generate missing item `id`s. Defaults to `'bds-nav-item'`.
890
+ * @param options.container - The element that receives `aria-activedescendant`. Defaults to the attached root.
891
+ * @returns `this` for chaining.
892
+ *
893
+ * @example
894
+ * ```ts
895
+ * this._keyboard.ariaActiveDescendant(options, 2, { idPrefix: 'my-option' });
896
+ * // -> container.setAttribute('aria-activedescendant', 'my-option-2')
897
+ * ```
898
+ */
899
+ ariaActiveDescendant(items, activeIndex, options) {
900
+ if (items.length === 0)
901
+ return this;
902
+ const container = options?.container ?? this._root;
903
+ if (container == null)
904
+ return this;
905
+ const prefix = options?.idPrefix ?? 'bds-nav-item';
906
+ const clamped = Math.max(0, Math.min(activeIndex, items.length - 1));
907
+ applyAriaActiveDescendant(items, clamped, container, prefix);
908
+ return this;
909
+ }
910
+ /**
911
+ * Moves focus to `items[activeIndex]` using whichever strategy was configured
912
+ * by the last `setLinearNavigation()` or `setGridNavigation()` call
913
+ * (`roving-tabindex` or `aria-activedescendant`).
914
+ *
915
+ * Use this inside `onNavigate` callbacks to stay strategy-agnostic — the component
916
+ * does not need to know which strategy is active.
917
+ *
918
+ * @param items - The full list of navigable elements.
919
+ * @param activeIndex - Index of the element that should become active.
920
+ * @returns `this` for chaining.
921
+ *
922
+ * @example
923
+ * ```ts
924
+ * setLinearNavigation({
925
+ * items: () => this.radioElements,
926
+ * onNavigate: (nextIndex, items) => {
927
+ * this.value = (items[nextIndex] as HTMLBdsRadioElement).value;
928
+ * this._keyboard.applyFocus(items, nextIndex); // works regardless of focus strategy
929
+ * },
930
+ * });
931
+ * ```
932
+ */
933
+ applyFocus(items, activeIndex) {
934
+ if (this._strategyType === FOCUS_STRATEGY.ARIA_ACTIVE_DESCENDANT) {
935
+ return this.ariaActiveDescendant(items, activeIndex, { idPrefix: this._idPrefix });
936
+ }
937
+ return this.rovingTabindex(items, activeIndex);
938
+ }
939
+ /**
940
+ * Registers arrow-key (`ArrowLeft`, `ArrowRight`, `ArrowUp`, `ArrowDown`),
941
+ * `Home`, and `End` bindings for flat list navigation following the
942
+ * [ARIA Composite Widget pattern](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_general_within).
943
+ *
944
+ * Arrow keys support auto-repeat (throttled to ~10 steps/second by default).
945
+ * Focus strategy (`roving-tabindex` or `aria-activedescendant`) is resolved
946
+ * from `config.focusStrategy` and stored for use by `applyFocus()`.
947
+ *
948
+ * @param config - Navigation options. See {@link LinearNavigationConfig} for all fields.
949
+ * @returns `this` for chaining.
950
+ *
951
+ * @example
952
+ * ```ts
953
+ * // Toolbar: horizontal arrows, roving tabindex, Enter/Space clicks
954
+ * this._keyboard.attach(this.el).setLinearNavigation({
955
+ * items: 'button:not([disabled])',
956
+ * orientation: 'horizontal',
957
+ * onActivate: () => (document.activeElement as HTMLElement)?.click(),
958
+ * });
959
+ *
960
+ * // Radio group: all arrows, wrap, selection changes on navigate
961
+ * this._keyboard.attach(this.el).setLinearNavigation({
962
+ * items: () => this.radioElements.filter(el => !el.disabled) as HTMLElement[],
963
+ * orientation: 'both',
964
+ * wrap: true,
965
+ * initialActiveSelector: '[checked]',
966
+ * onNavigate: (nextIndex, items) => {
967
+ * this.value = (items[nextIndex] as HTMLBdsRadioElement).value;
968
+ * this._keyboard.applyFocus(items, nextIndex);
969
+ * },
970
+ * });
971
+ * ```
972
+ */
973
+ setLinearNavigation(config) {
974
+ if (!this._ensureAttached('setLinearNavigation'))
975
+ return this;
976
+ setupLinearNavigation(config, {
977
+ root: this._root,
978
+ abortController: this._abortController,
979
+ register: (key, handler, options) => this.set(key, handler, options),
980
+ onStrategyResolved: (type, prefix) => {
981
+ this._strategyType = type;
982
+ this._idPrefix = prefix;
983
+ },
984
+ });
985
+ return this;
986
+ }
987
+ /**
988
+ * Registers 2D arrow-key navigation for grids, calendars, and data tables following the
989
+ * [ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
990
+ *
991
+ * Registered bindings:
992
+ * - `ArrowLeft / ArrowRight` — move one column left/right
993
+ * - `ArrowUp / ArrowDown` — move one row up/down
994
+ * - `Home / End` — first/last cell in current row
995
+ * - `Ctrl+Home / Ctrl+End` — first/last cell in the entire grid
996
+ * - `PageUp / PageDown` — delegated to `config.onPageUp` / `config.onPageDown` (optional)
997
+ * - `Enter / Space` — delegated to `config.onActivate` (optional)
998
+ *
999
+ * Items are provided as a `HTMLElement[][]` (rows × columns). `null`/`undefined`
1000
+ * cells are skipped. The list is re-evaluated on every keydown.
1001
+ *
1002
+ * @param config - Navigation options. See {@link GridNavigationConfig} for all fields.
1003
+ * @returns `this` for chaining.
1004
+ *
1005
+ * @example
1006
+ * ```ts
1007
+ * // Data table: 2D navigation, Enter selects a cell
1008
+ * this._keyboard.attach(this.el).setGridNavigation({
1009
+ * items: () => this._getRowsOfCells(),
1010
+ * wrap: false,
1011
+ * onActivate: () => this._selectFocusedCell(),
1012
+ * });
1013
+ *
1014
+ * // Calendar: grid navigation + PageUp/Down to change months
1015
+ * this._keyboard.attach(this.el).setGridNavigation({
1016
+ * items: () => this.weekRows.map(row => row.map(day => day.buttonEl)),
1017
+ * wrap: true,
1018
+ * initialActiveSelector: '[data-today]',
1019
+ * onActivate: () => this.selectFocusedDay(),
1020
+ * onPageUp: () => this.prevMonth(),
1021
+ * onPageDown: () => this.nextMonth(),
1022
+ * });
1023
+ * ```
1024
+ */
1025
+ setGridNavigation(config) {
1026
+ if (!this._ensureAttached('setGridNavigation'))
1027
+ return this;
1028
+ setupGridNavigation(config, {
1029
+ root: this._root,
1030
+ abortController: this._abortController,
1031
+ register: (key, handler, options) => this.set(key, handler, options),
1032
+ onStrategyResolved: (type, prefix) => {
1033
+ this._strategyType = type;
1034
+ this._idPrefix = prefix;
1035
+ },
1036
+ });
1037
+ return this;
1038
+ }
1039
+ /**
1040
+ * Removes a previously registered binding so it no longer fires.
1041
+ *
1042
+ * Pass the same key expression that was used in `set()`. Useful for dynamically
1043
+ * enabling or disabling shortcuts (e.g. disabling `Escape` while a sub-modal is open).
1044
+ *
1045
+ * @param key - The key expression to unregister (same format accepted by `set()`).
1046
+ * @returns `this` for chaining.
1047
+ *
1048
+ * @example
1049
+ * ```ts
1050
+ * // Disable Escape while a confirmation dialog is open
1051
+ * this._keyboard.remove(KEYBOARD.Escape);
1052
+ * // Re-enable it after the dialog closes
1053
+ * this._keyboard.set(KEYBOARD.Escape, () => this.close());
1054
+ * ```
1055
+ */
1056
+ remove(key) {
1057
+ const { keys, modifiers } = parseKeys(key);
1058
+ this._bindings.delete(createCombinationKey(keys, modifiers));
1059
+ this._rebuildAllowedKeys();
1060
+ return this;
1061
+ }
1062
+ }
1063
+
1064
+ export { KeyboardController as K, KEY_TRIGGERS as a };