@telesign/boreal-web-components 0.1.0-alpha.4 → 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.
- package/components-build/bds-avatar.js +1 -1
- package/components-build/bds-badge.js +1 -1
- package/components-build/bds-banner.js +1 -1
- package/components-build/bds-breadcrumb-item.d.ts +11 -0
- package/components-build/bds-breadcrumb-item.js +1 -0
- package/components-build/bds-breadcrumb.d.ts +11 -0
- package/components-build/bds-breadcrumb.js +1 -0
- package/components-build/bds-button-group.d.ts +11 -0
- package/components-build/bds-button-group.js +1 -0
- package/components-build/bds-button.js +1 -1
- package/components-build/bds-checkbox-button.d.ts +11 -0
- package/components-build/bds-checkbox-button.js +1 -0
- package/components-build/bds-checkbox-card.d.ts +11 -0
- package/components-build/bds-checkbox-card.js +1 -0
- package/components-build/bds-checkbox-group.d.ts +11 -0
- package/components-build/bds-checkbox-group.js +1 -0
- package/components-build/bds-checkbox.js +1 -1
- package/components-build/bds-dialog.js +1 -1
- package/components-build/bds-divider.js +1 -1
- package/components-build/bds-flag.js +1 -1
- package/components-build/bds-grid-item.js +1 -1
- package/components-build/bds-grid.js +1 -1
- package/components-build/bds-list-menu-item.js +1 -1
- package/components-build/bds-list-menu.js +1 -1
- package/components-build/bds-popover.js +1 -1
- package/components-build/bds-radio-button.d.ts +11 -0
- package/components-build/bds-radio-button.js +1 -0
- package/components-build/bds-radio-card.d.ts +11 -0
- package/components-build/bds-radio-card.js +1 -0
- package/components-build/bds-radio-group.d.ts +11 -0
- package/components-build/bds-radio-group.js +1 -0
- package/components-build/bds-radio.d.ts +11 -0
- package/components-build/bds-radio.js +1 -0
- package/components-build/bds-select.d.ts +11 -0
- package/components-build/bds-select.js +1 -0
- package/components-build/bds-slider.d.ts +11 -0
- package/components-build/bds-slider.js +1 -0
- package/components-build/bds-spinner.js +1 -1
- package/components-build/bds-status.js +1 -1
- package/components-build/bds-tag.js +1 -1
- package/components-build/bds-text-field.js +1 -1
- package/components-build/bds-toggle.js +1 -1
- package/components-build/bds-tooltip.js +1 -1
- package/components-build/bds-typography.js +1 -1
- package/components-build/index.js +1 -1
- package/components-build/p-B6e9eIHB.js +1 -0
- package/components-build/p-CJBdGD_4.js +1 -0
- package/components-build/p-CPvuMm5C.js +1 -0
- package/components-build/p-DcR7mHFE.js +1 -0
- package/components-build/p-DdOPD9wW.js +1 -0
- package/components-build/p-DfaYciGa.js +1 -0
- package/components-build/p-Dh8DSJs_.js +1 -0
- package/components-build/p-DoNZM78n.js +1 -0
- package/components-build/p-DrkDx75U.js +1 -0
- package/components-build/p-DuBzr05c.js +1 -0
- package/components-build/p-PmjPRW8X.js +1 -0
- package/components-build/p-cgdh1LO-.js +1 -0
- package/{dist/boreal-web-components/p-CaHOghy5.js → components-build/p-fUK0GCeC.js} +1 -1
- package/components-build/p-nxYzL9uu.js +1 -0
- package/components-build/p-yLNcMg2E.js +1 -0
- package/custom-elements.json +5078 -1524
- package/dist/boreal-web-components/boreal-web-components.esm.js +1 -1
- package/dist/boreal-web-components/css/boreal.css +60 -0
- package/dist/boreal-web-components/css/theme-connect.css +15 -0
- package/dist/boreal-web-components/css/theme-engage.css +15 -0
- package/dist/boreal-web-components/css/theme-protect.css +15 -0
- package/dist/boreal-web-components/css/theme-proximus.css +15 -0
- package/dist/boreal-web-components/p-02e53626.entry.js +1 -0
- package/dist/boreal-web-components/p-0357450d.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-e0bf8a6e.entry.js → p-1250ba53.entry.js} +1 -1
- package/dist/boreal-web-components/p-1911b978.entry.js +1 -0
- package/dist/boreal-web-components/{p-2158a7cd.entry.js → p-19f9352d.entry.js} +1 -1
- package/dist/boreal-web-components/p-1mOd23lT.system.js +1 -0
- package/dist/boreal-web-components/{p-52600375.system.entry.js → p-25823f7d.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-2db71382.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-251d35df.system.entry.js → p-365e8e5c.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-367e40f9.entry.js +1 -0
- package/dist/boreal-web-components/p-3946d587.entry.js +1 -0
- package/dist/boreal-web-components/p-44b6fe6c.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-7a4efddd.system.entry.js → p-48712a63.system.entry.js} +1 -1
- package/dist/boreal-web-components/{p-251b3002.entry.js → p-49ea207a.entry.js} +1 -1
- package/dist/boreal-web-components/p-4b028406.entry.js +1 -0
- package/dist/boreal-web-components/p-4b615de8.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-edca16b6.system.entry.js → p-4f431941.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-524421f7.system.entry.js +1 -0
- package/dist/boreal-web-components/p-530c8c7f.entry.js +1 -0
- package/dist/boreal-web-components/p-55aeff0c.system.entry.js +1 -0
- package/dist/boreal-web-components/p-5c8650b3.system.entry.js +1 -0
- package/dist/boreal-web-components/p-5e590291.system.entry.js +1 -0
- package/dist/boreal-web-components/p-5ee0841f.entry.js +1 -0
- package/dist/boreal-web-components/{p-871c9a9f.system.entry.js → p-65923619.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-66ec16d9.entry.js +1 -0
- package/dist/boreal-web-components/{p-127a4134.entry.js → p-714bf70d.entry.js} +1 -1
- package/dist/boreal-web-components/{p-66360e63.entry.js → p-71ed1e1d.entry.js} +1 -1
- package/dist/boreal-web-components/{p-b12d4788.system.entry.js → p-732e98f1.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-7626338e.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-80822731.system.entry.js → p-77658a5b.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-7G4h4DI7.js +1 -0
- package/dist/boreal-web-components/p-800fc096.system.entry.js +1 -0
- package/dist/boreal-web-components/p-824485ad.entry.js +1 -0
- package/dist/boreal-web-components/p-8dfe3a9f.entry.js +1 -0
- package/dist/boreal-web-components/p-90022071.entry.js +1 -0
- package/dist/boreal-web-components/{p-1d1bb9d2.entry.js → p-9d02057d.entry.js} +1 -1
- package/dist/boreal-web-components/p-C-g3hhl_.system.js +1 -0
- package/dist/boreal-web-components/p-C0P3gzq5.system.js +1 -0
- package/dist/boreal-web-components/p-C4HM8wQe.js +1 -0
- package/dist/boreal-web-components/p-CPvuMm5C.js +1 -0
- package/dist/boreal-web-components/p-CRMH6mfq.system.js +1 -0
- package/dist/boreal-web-components/p-CU_dRLdk.system.js +1 -0
- package/dist/boreal-web-components/p-CW4-dkCF.system.js +1 -0
- package/dist/boreal-web-components/p-CcENtewr.js +1 -0
- package/dist/boreal-web-components/{p-BuxZeJbu.system.js → p-DSU8tkVw.system.js} +1 -1
- package/dist/boreal-web-components/p-DfaYciGa.js +1 -0
- package/dist/boreal-web-components/p-Dk9dfU9-.system.js +1 -0
- package/dist/boreal-web-components/p-DlVzZK7o.js +1 -0
- package/dist/boreal-web-components/p-DnIhjwCH.system.js +1 -0
- package/dist/boreal-web-components/p-Dwo1hcd9.js +1 -0
- package/dist/boreal-web-components/p-K7DvMlRo.system.js +1 -0
- package/{components-build/p-CaHOghy5.js → dist/boreal-web-components/p-U9yfbs7i.js} +1 -1
- package/dist/boreal-web-components/p-UEj9YHof.system.js +1 -0
- package/dist/boreal-web-components/p-b54fe67f.system.entry.js +1 -0
- package/dist/boreal-web-components/p-bbcd3a30.system.entry.js +1 -0
- package/dist/boreal-web-components/p-bea25d3d.system.entry.js +1 -0
- package/dist/boreal-web-components/p-bed506d3.entry.js +1 -0
- package/dist/boreal-web-components/p-bfd62034.system.entry.js +1 -0
- package/dist/boreal-web-components/p-c07d6b82.system.entry.js +1 -0
- package/dist/boreal-web-components/p-cc5c359f.entry.js +1 -0
- package/dist/boreal-web-components/p-cgdh1LO-.js +1 -0
- package/dist/boreal-web-components/p-d2b6ae79.entry.js +1 -0
- package/dist/boreal-web-components/{p-695d0830.entry.js → p-dc7d429c.entry.js} +1 -1
- package/dist/boreal-web-components/p-e21b8416.entry.js +1 -0
- package/dist/boreal-web-components/p-e4364a44.system.entry.js +1 -0
- package/dist/boreal-web-components/p-e51407b2.system.entry.js +1 -0
- package/dist/boreal-web-components/p-e8cde179.system.entry.js +1 -0
- package/dist/boreal-web-components/p-eecc3028.system.entry.js +1 -0
- package/dist/boreal-web-components/p-f3bd824d.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-44ceff4e.system.entry.js → p-f65e9d0c.system.entry.js} +1 -1
- package/dist/boreal-web-components/p-f6eabb16.entry.js +1 -0
- package/dist/boreal-web-components/p-f7acf6e5.system.entry.js +1 -0
- package/dist/boreal-web-components/{p-7db870b7.entry.js → p-f9560509.entry.js} +1 -1
- package/dist/boreal-web-components/p-fa128cad.system.entry.js +1 -0
- package/dist/boreal-web-components/p-fab1a01d.entry.js +1 -0
- package/dist/boreal-web-components/{p-fc24d963.entry.js → p-fb9a0e94.entry.js} +1 -1
- package/dist/boreal-web-components/p-fb9ba833.entry.js +1 -0
- package/dist/boreal-web-components/p-fbe0c9f2.entry.js +1 -0
- package/dist/boreal-web-components/p-fbe88555.entry.js +1 -0
- package/dist/boreal-web-components/p-fc1fa966.system.entry.js +1 -0
- package/dist/boreal-web-components/p-fc4ffa0e.entry.js +1 -0
- package/dist/boreal-web-components/p-fef13445.entry.js +1 -0
- package/dist/boreal-web-components/p-iq2UuV7c.js +1 -0
- package/dist/boreal-web-components/p-qGhMe8Hk.js +1 -0
- package/dist/boreal-web-components/p-vzZJGcYF.system.js +1 -1
- package/dist/boreal-web-components/scss/maps/_theme-connect.scss +16 -1
- package/dist/boreal-web-components/scss/maps/_theme-engage.scss +16 -1
- package/dist/boreal-web-components/scss/maps/_theme-protect.scss +16 -1
- package/dist/boreal-web-components/scss/maps/_theme-proximus.scss +16 -1
- package/dist/boreal-web-components/scss/variables/_theme-connect.scss +16 -1
- package/dist/boreal-web-components/scss/variables/_theme-engage.scss +16 -1
- package/dist/boreal-web-components/scss/variables/_theme-protect.scss +16 -1
- package/dist/boreal-web-components/scss/variables/_theme-proximus.scss +16 -1
- package/dist/cjs/KeyboardController-B_g3peyB.js +1067 -0
- package/dist/cjs/Keys-DXn16dlA.js +34 -0
- package/dist/cjs/Logger-DnziItRH.js +51 -0
- package/dist/cjs/{attributes-wIHueigW.js → attributes-CgRFplrN.js} +3 -0
- package/dist/cjs/bds-avatar.cjs.entry.js +2 -2
- package/dist/cjs/bds-badge.cjs.entry.js +1 -1
- package/dist/cjs/bds-banner.cjs.entry.js +2 -2
- package/dist/cjs/bds-breadcrumb-item.cjs.entry.js +106 -0
- package/dist/cjs/bds-breadcrumb.cjs.entry.js +127 -0
- package/dist/cjs/bds-button-group.cjs.entry.js +116 -0
- package/dist/cjs/bds-button.cjs.entry.js +38 -17
- package/dist/cjs/bds-checkbox-button.cjs.entry.js +119 -0
- package/dist/cjs/bds-checkbox-card.cjs.entry.js +135 -0
- package/dist/cjs/bds-checkbox-group.cjs.entry.js +292 -0
- package/dist/cjs/bds-checkbox.cjs.entry.js +67 -29
- package/dist/cjs/bds-dialog.cjs.entry.js +4 -4
- package/dist/cjs/bds-divider.cjs.entry.js +2 -2
- package/dist/cjs/bds-flag.cjs.entry.js +1 -1
- package/dist/cjs/bds-grid-item.cjs.entry.js +1 -1
- package/dist/cjs/bds-grid.cjs.entry.js +1 -1
- package/dist/cjs/bds-list-menu-item.cjs.entry.js +59 -23
- package/dist/cjs/bds-list-menu.cjs.entry.js +126 -22
- package/dist/cjs/bds-popover.cjs.entry.js +15 -6
- package/dist/cjs/bds-radio-button.cjs.entry.js +60 -0
- package/dist/cjs/bds-radio-card.cjs.entry.js +76 -0
- package/dist/cjs/bds-radio-group.cjs.entry.js +291 -0
- package/dist/cjs/bds-radio.cjs.entry.js +60 -0
- package/dist/cjs/bds-select.cjs.entry.js +394 -0
- package/dist/cjs/bds-slider.cjs.entry.js +655 -0
- package/dist/cjs/bds-spinner.cjs.entry.js +1 -1
- package/dist/cjs/bds-status.cjs.entry.js +1 -1
- package/dist/cjs/bds-tag.cjs.entry.js +2 -2
- package/dist/cjs/bds-text-field.cjs.entry.js +15 -9
- package/dist/cjs/bds-toggle.cjs.entry.js +7 -6
- package/dist/cjs/bds-tooltip_2.cjs.entry.js +12 -9
- package/dist/cjs/boreal-web-components.cjs.js +1 -1
- package/dist/cjs/checkbox-form-association-DTEpHXUD.js +43 -0
- package/dist/cjs/coreColors-CQGojc0l.js +10 -0
- package/dist/cjs/{enum-DLblRCkQ.js → enum-DeTWfR0D.js} +3 -3
- package/dist/cjs/form-associated.mixin-BCR6bj29.js +33 -0
- package/dist/cjs/{getOffset-m4hBgyVP.js → getOffset-CsDHFjPW.js} +6 -53
- package/dist/cjs/{form-associated.mixin-DXwvF_vW.js → internals-D8x7GMfR.js} +0 -31
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/orientation-DQAIleEJ.js +8 -0
- package/dist/collection/collection-manifest.json +18 -6
- package/dist/collection/components/actions/bds-button/bds-button.css +55 -55
- package/dist/collection/components/actions/bds-button/bds-button.js +35 -11
- package/dist/collection/components/actions/bds-button/types/enum.js +3 -3
- package/dist/collection/components/actions/bds-button-group/bds-button-group.css +77 -0
- package/dist/collection/components/actions/bds-button-group/bds-button-group.js +264 -0
- package/dist/collection/components/actions/bds-list-menu/bds-list-menu/bds-list-menu.css +27 -1
- package/dist/collection/components/actions/bds-list-menu/bds-list-menu/bds-list-menu.js +283 -51
- package/dist/collection/components/actions/bds-list-menu/bds-list-menu/types/enum.js +4 -0
- package/dist/collection/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.css +30 -11
- package/dist/collection/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.js +65 -7
- package/dist/collection/components/actions/bds-toggle/bds-toggle.css +3 -3
- package/dist/collection/components/actions/bds-toggle/bds-toggle.js +1 -1
- package/dist/collection/components/feedback/bds-badge/bds-badge.js +1 -1
- package/dist/collection/components/feedback/bds-banner/bds-banner.js +1 -1
- package/dist/collection/components/feedback/bds-spinner/bds-spinner.js +1 -1
- package/dist/collection/components/feedback/bds-status/bds-status.js +1 -1
- package/dist/collection/components/feedback/bds-tag/bds-tag.css +6 -6
- package/dist/collection/components/feedback/bds-tag/bds-tag.js +1 -1
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox/bds-checkbox.css +132 -0
- package/dist/collection/components/forms/bds-checkbox/{bds-checkbox.js → bds-checkbox/bds-checkbox.js} +138 -35
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.css +113 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.js +382 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-card/bds-checkbox-card.css +139 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-card/bds-checkbox-card.js +443 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.css +55 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.js +648 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/types/enum.js +5 -0
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox-group/types/index.js +3 -0
- package/dist/collection/components/forms/bds-checkbox/utils/checkbox-form-association.js +39 -0
- package/dist/collection/components/forms/bds-checkbox/utils/index.js +1 -0
- package/dist/collection/components/forms/bds-flag/bds-flag.js +1 -1
- package/dist/collection/components/forms/bds-radio/bds-radio/bds-radio.css +121 -0
- package/dist/collection/components/forms/bds-radio/bds-radio/bds-radio.js +238 -0
- package/dist/collection/components/forms/bds-radio/bds-radio/types/IRadio.js +1 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-button/bds-radio-button.css +113 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-button/bds-radio-button.js +238 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-button/types/IRadioButton.js +1 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-card/bds-radio-card.css +156 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-card/bds-radio-card.js +307 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-card/types/IRadioCard.js +1 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-group/bds-radio-group.css +55 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-group/bds-radio-group.js +648 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-group/types/IRadioGroup.js +1 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-group/types/enum.js +5 -0
- package/dist/collection/components/forms/bds-radio/bds-radio-group/types/types.js +1 -0
- package/dist/collection/components/forms/bds-select/bds-select.css +23 -0
- package/dist/collection/components/forms/bds-select/bds-select.js +431 -0
- package/dist/collection/components/forms/bds-select/types/ISelect.js +1 -0
- package/dist/collection/components/forms/bds-slider/bds-slider.css +213 -0
- package/dist/collection/components/forms/bds-slider/bds-slider.js +847 -0
- package/dist/collection/components/forms/bds-slider/helpers/SliderDOMController.js +61 -0
- package/dist/collection/components/forms/bds-slider/helpers/SliderService.js +93 -0
- package/dist/collection/components/forms/bds-slider/helpers/index.js +3 -0
- package/dist/collection/components/forms/bds-slider/helpers/parseValues.js +43 -0
- package/dist/collection/components/forms/bds-slider/types/ChangeDetail.js +1 -0
- package/dist/collection/components/forms/bds-slider/types/ISlider.js +1 -0
- package/dist/collection/components/forms/bds-slider/types/ISliderOptions.js +1 -0
- package/dist/collection/components/forms/bds-slider/types/enum.js +16 -0
- package/dist/collection/components/forms/bds-slider/types/index.js +5 -0
- package/dist/collection/components/forms/bds-slider/types/types.js +1 -0
- package/dist/collection/components/forms/bds-text-field/bds-text-field.css +10 -3
- package/dist/collection/components/forms/bds-text-field/bds-text-field.js +47 -2
- package/dist/collection/components/helpers/{bds-divider.css → bds-divider/bds-divider.css} +12 -4
- package/dist/collection/components/helpers/{bds-divider.js → bds-divider/bds-divider.js} +3 -3
- package/dist/collection/components/helpers/bds-divider/types/IDivider.js +1 -0
- package/dist/collection/components/helpers/bds-divider/types/types.js +1 -0
- package/dist/collection/components/images-icons/bds-avatar/bds-avatar.js +1 -1
- package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.js +3 -3
- package/dist/collection/components/layouts/bds-grid/bds-grid/types/IGrid.js +1 -0
- package/dist/collection/components/layouts/bds-grid/bds-grid/types/types.js +1 -0
- package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.js +9 -9
- package/dist/collection/components/layouts/bds-grid/bds-grid-item/types/IGridItem.js +1 -0
- package/dist/collection/components/layouts/bds-grid/bds-grid-item/types/types.js +1 -0
- package/dist/collection/components/navigation/bds-breadcrumb/bds-breadcrumb.css +21 -0
- package/dist/collection/components/navigation/bds-breadcrumb/bds-breadcrumb.js +292 -0
- package/dist/collection/components/navigation/bds-breadcrumb/types/IBreadcrumb.js +1 -0
- package/dist/collection/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.css +64 -0
- package/dist/collection/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.js +369 -0
- package/dist/collection/components/navigation/bds-breadcrumb-item/types/IBreadcrumbItem.js +1 -0
- package/dist/collection/components/overlays/bds-dialog/bds-dialog.css +1 -1
- package/dist/collection/components/overlays/bds-dialog/bds-dialog.js +2 -2
- package/dist/collection/components/overlays/bds-popover/bds-popover.js +49 -3
- package/dist/collection/components/overlays/bds-tooltip/bds-tooltip.js +3 -3
- package/dist/collection/components/titles-text/bds-typography/bds-typography.css +5 -0
- package/dist/collection/components/titles-text/bds-typography/bds-typography.js +30 -47
- package/dist/collection/components/titles-text/bds-typography/utils/bds-typography-utils.js +1 -1
- package/dist/collection/css/boreal.css +60 -0
- package/dist/collection/css/theme-connect.css +15 -0
- package/dist/collection/css/theme-engage.css +15 -0
- package/dist/collection/css/theme-protect.css +15 -0
- package/dist/collection/css/theme-proximus.css +15 -0
- package/dist/collection/mixins/anchored.mixin.js +2 -2
- package/dist/collection/mixins/links.mixin.js +1 -2
- package/dist/collection/mixins/menu-behavior.mixin.js +12 -6
- package/dist/collection/scss/maps/_theme-connect.scss +16 -1
- package/dist/collection/scss/maps/_theme-engage.scss +16 -1
- package/dist/collection/scss/maps/_theme-protect.scss +16 -1
- package/dist/collection/scss/maps/_theme-proximus.scss +16 -1
- package/dist/collection/scss/variables/_theme-connect.scss +16 -1
- package/dist/collection/scss/variables/_theme-engage.scss +16 -1
- package/dist/collection/scss/variables/_theme-protect.scss +16 -1
- package/dist/collection/scss/variables/_theme-proximus.scss +16 -1
- package/dist/collection/types/index.js +2 -1
- package/dist/collection/types/orientation.js +4 -0
- package/dist/collection/utils/a11y/index.js +4 -0
- package/dist/collection/utils/a11y/keyboard/KeyboardController.js +566 -0
- package/dist/collection/utils/a11y/keyboard/_constants.js +30 -0
- package/dist/collection/utils/a11y/keyboard/focus/aria-activedescendant.js +41 -0
- package/dist/collection/utils/a11y/keyboard/focus/resolve.js +48 -0
- package/dist/collection/utils/a11y/keyboard/focus/roving-tabindex.js +55 -0
- package/dist/collection/utils/a11y/keyboard/navigation/grid-navigation.js +194 -0
- package/dist/collection/utils/a11y/keyboard/navigation/linear-navigation.js +137 -0
- package/dist/collection/utils/a11y/keyboard/types/IKeyboardController.js +1 -0
- package/dist/collection/utils/a11y/keyboard/types/index.js +2 -0
- package/dist/collection/utils/a11y/keyboard/types/types.js +1 -0
- package/dist/collection/utils/constants/common/Keys.js +29 -24
- package/dist/collection/utils/dom/elements.js +63 -0
- package/dist/collection/utils/menu/menu-item.utils.js +9 -8
- package/dist/collection/utils/testing/helpers.js +10 -0
- package/dist/css/boreal.css +60 -0
- package/dist/css/theme-connect.css +15 -0
- package/dist/css/theme-engage.css +15 -0
- package/dist/css/theme-protect.css +15 -0
- package/dist/css/theme-proximus.css +15 -0
- package/dist/esm/KeyboardController-DcnXb5F5.js +1064 -0
- package/dist/esm/Keys-7G4h4DI7.js +31 -0
- package/dist/esm/Logger-iq2UuV7c.js +49 -0
- package/dist/esm/{attributes-CaHOghy5.js → attributes-U9yfbs7i.js} +3 -0
- package/dist/esm/bds-avatar.entry.js +2 -2
- package/dist/esm/bds-badge.entry.js +1 -1
- package/dist/esm/bds-banner.entry.js +2 -2
- package/dist/esm/bds-breadcrumb-item.entry.js +104 -0
- package/dist/esm/bds-breadcrumb.entry.js +125 -0
- package/dist/esm/bds-button-group.entry.js +114 -0
- package/dist/esm/bds-button.entry.js +36 -15
- package/dist/esm/bds-checkbox-button.entry.js +117 -0
- package/dist/esm/bds-checkbox-card.entry.js +133 -0
- package/dist/esm/bds-checkbox-group.entry.js +290 -0
- package/dist/esm/bds-checkbox.entry.js +67 -29
- package/dist/esm/bds-dialog.entry.js +4 -4
- package/dist/esm/bds-divider.entry.js +2 -2
- package/dist/esm/bds-flag.entry.js +1 -1
- package/dist/esm/bds-grid-item.entry.js +1 -1
- package/dist/esm/bds-grid.entry.js +1 -1
- package/dist/esm/bds-list-menu-item.entry.js +59 -23
- package/dist/esm/bds-list-menu.entry.js +126 -22
- package/dist/esm/bds-popover.entry.js +15 -6
- package/dist/esm/bds-radio-button.entry.js +58 -0
- package/dist/esm/bds-radio-card.entry.js +74 -0
- package/dist/esm/bds-radio-group.entry.js +289 -0
- package/dist/esm/bds-radio.entry.js +58 -0
- package/dist/esm/bds-select.entry.js +392 -0
- package/dist/esm/bds-slider.entry.js +653 -0
- package/dist/esm/bds-spinner.entry.js +1 -1
- package/dist/esm/bds-status.entry.js +1 -1
- package/dist/esm/bds-tag.entry.js +2 -2
- package/dist/esm/bds-text-field.entry.js +10 -4
- package/dist/esm/bds-toggle.entry.js +5 -4
- package/dist/esm/bds-tooltip_2.entry.js +12 -9
- package/dist/esm/boreal-web-components.js +1 -1
- package/dist/esm/checkbox-form-association-cgdh1LO-.js +41 -0
- package/dist/esm/coreColors-Dwo1hcd9.js +8 -0
- package/dist/esm/{enum-C8mRvnTA.js → enum-DlVzZK7o.js} +4 -4
- package/dist/esm/form-associated.mixin-C4HM8wQe.js +31 -0
- package/dist/esm/{getOffset-DKPjeBHi.js → getOffset-DCLpJBcp.js} +4 -51
- package/dist/esm/{form-associated.mixin-CvK2d92c.js → internals-DfaYciGa.js} +1 -31
- package/dist/esm/loader.js +1 -1
- package/dist/esm/orientation-CPvuMm5C.js +6 -0
- package/dist/esm-es5/KeyboardController-DcnXb5F5.js +1 -0
- package/dist/esm-es5/Keys-7G4h4DI7.js +1 -0
- package/dist/esm-es5/Logger-iq2UuV7c.js +1 -0
- package/dist/esm-es5/{attributes-CaHOghy5.js → attributes-U9yfbs7i.js} +1 -1
- package/dist/esm-es5/bds-avatar.entry.js +1 -1
- package/dist/esm-es5/bds-badge.entry.js +1 -1
- package/dist/esm-es5/bds-banner.entry.js +1 -1
- package/dist/esm-es5/bds-breadcrumb-item.entry.js +1 -0
- package/dist/esm-es5/bds-breadcrumb.entry.js +1 -0
- package/dist/esm-es5/bds-button-group.entry.js +1 -0
- package/dist/esm-es5/bds-button.entry.js +1 -1
- package/dist/esm-es5/bds-checkbox-button.entry.js +1 -0
- package/dist/esm-es5/bds-checkbox-card.entry.js +1 -0
- package/dist/esm-es5/bds-checkbox-group.entry.js +1 -0
- package/dist/esm-es5/bds-checkbox.entry.js +1 -1
- package/dist/esm-es5/bds-dialog.entry.js +1 -1
- package/dist/esm-es5/bds-divider.entry.js +1 -1
- package/dist/esm-es5/bds-flag.entry.js +1 -1
- package/dist/esm-es5/bds-grid-item.entry.js +1 -1
- package/dist/esm-es5/bds-grid.entry.js +1 -1
- package/dist/esm-es5/bds-list-menu-item.entry.js +1 -1
- package/dist/esm-es5/bds-list-menu.entry.js +1 -1
- package/dist/esm-es5/bds-popover.entry.js +1 -1
- package/dist/esm-es5/bds-radio-button.entry.js +1 -0
- package/dist/esm-es5/bds-radio-card.entry.js +1 -0
- package/dist/esm-es5/bds-radio-group.entry.js +1 -0
- package/dist/esm-es5/bds-radio.entry.js +1 -0
- package/dist/esm-es5/bds-select.entry.js +1 -0
- package/dist/esm-es5/bds-slider.entry.js +1 -0
- package/dist/esm-es5/bds-spinner.entry.js +1 -1
- package/dist/esm-es5/bds-status.entry.js +1 -1
- package/dist/esm-es5/bds-tag.entry.js +1 -1
- package/dist/esm-es5/bds-text-field.entry.js +1 -1
- package/dist/esm-es5/bds-toggle.entry.js +1 -1
- package/dist/esm-es5/bds-tooltip_2.entry.js +1 -1
- package/dist/esm-es5/boreal-web-components.js +1 -1
- package/dist/esm-es5/checkbox-form-association-cgdh1LO-.js +1 -0
- package/dist/esm-es5/coreColors-Dwo1hcd9.js +1 -0
- package/dist/esm-es5/enum-DlVzZK7o.js +1 -0
- package/dist/esm-es5/form-associated.mixin-C4HM8wQe.js +1 -0
- package/dist/esm-es5/getOffset-DCLpJBcp.js +1 -0
- package/dist/esm-es5/internals-DfaYciGa.js +1 -0
- package/dist/esm-es5/loader.js +1 -1
- package/dist/esm-es5/orientation-CPvuMm5C.js +1 -0
- package/dist/scss/maps/_theme-connect.scss +16 -1
- package/dist/scss/maps/_theme-engage.scss +16 -1
- package/dist/scss/maps/_theme-protect.scss +16 -1
- package/dist/scss/maps/_theme-proximus.scss +16 -1
- package/dist/scss/variables/_theme-connect.scss +16 -1
- package/dist/scss/variables/_theme-engage.scss +16 -1
- package/dist/scss/variables/_theme-protect.scss +16 -1
- package/dist/scss/variables/_theme-proximus.scss +16 -1
- package/dist/types/components/actions/bds-button/bds-button.d.ts +8 -4
- package/dist/types/components/actions/bds-button/types/enum.d.ts +3 -3
- package/dist/types/components/actions/bds-button-group/bds-button-group.d.ts +40 -0
- package/dist/types/components/actions/bds-button-group/types/IButtonGroup.d.ts +11 -0
- package/dist/types/components/actions/bds-list-menu/bds-list-menu/bds-list-menu.d.ts +32 -15
- package/dist/types/components/actions/bds-list-menu/bds-list-menu/types/IListMenu.d.ts +17 -0
- package/dist/types/components/actions/bds-list-menu/bds-list-menu/types/enum.d.ts +5 -0
- package/dist/types/components/actions/bds-list-menu/bds-list-menu/types/types.d.ts +3 -0
- package/dist/types/components/actions/bds-list-menu/bds-list-menu-item/bds-list-menu-item.d.ts +10 -1
- package/dist/types/components/actions/bds-list-menu/bds-list-menu-item/types/IListMenuItem.d.ts +2 -0
- package/dist/types/components/actions/bds-toggle/types/IToggle.d.ts +1 -1
- package/dist/types/components/forms/bds-checkbox/{bds-checkbox.d.ts → bds-checkbox/bds-checkbox.d.ts} +24 -9
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-button/bds-checkbox-button.d.ts +68 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-button/types/ICheckboxButton.d.ts +15 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-card/bds-checkbox-card.d.ts +63 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-card/types/ICheckboxCard.d.ts +9 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/bds-checkbox-group.d.ts +86 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/ICheckboxGroup.d.ts +27 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/enum.d.ts +6 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/index.d.ts +4 -0
- package/dist/types/components/forms/bds-checkbox/bds-checkbox-group/types/types.d.ts +4 -0
- package/dist/types/components/forms/bds-checkbox/types/ICheckbox.d.ts +7 -22
- package/dist/types/components/forms/bds-checkbox/utils/checkbox-form-association.d.ts +10 -0
- package/dist/types/components/forms/bds-checkbox/utils/index.d.ts +2 -0
- package/dist/types/components/forms/bds-radio/bds-radio/bds-radio.d.ts +36 -0
- package/dist/types/components/forms/bds-radio/bds-radio/types/IRadio.d.ts +13 -0
- package/dist/types/components/forms/bds-radio/bds-radio-button/bds-radio-button.d.ts +36 -0
- package/dist/types/components/forms/bds-radio/bds-radio-button/types/IRadioButton.d.ts +14 -0
- package/dist/types/components/forms/bds-radio/bds-radio-card/bds-radio-card.d.ts +57 -0
- package/dist/types/components/forms/bds-radio/bds-radio-card/types/IRadioCard.d.ts +14 -0
- package/dist/types/components/forms/bds-radio/bds-radio-group/bds-radio-group.d.ts +85 -0
- package/dist/types/components/forms/bds-radio/bds-radio-group/types/IRadioGroup.d.ts +20 -0
- package/dist/types/components/forms/bds-radio/bds-radio-group/types/enum.d.ts +6 -0
- package/dist/types/components/forms/bds-radio/bds-radio-group/types/types.d.ts +3 -0
- package/dist/types/components/forms/bds-select/bds-select.d.ts +98 -0
- package/dist/types/components/forms/bds-select/types/ISelect.d.ts +6 -0
- package/dist/types/components/forms/bds-slider/bds-slider.d.ts +176 -0
- package/dist/types/components/forms/bds-slider/helpers/SliderDOMController.d.ts +38 -0
- package/dist/types/components/forms/bds-slider/helpers/SliderService.d.ts +44 -0
- package/dist/types/components/forms/bds-slider/helpers/index.d.ts +4 -0
- package/dist/types/components/forms/bds-slider/helpers/parseValues.d.ts +18 -0
- package/dist/types/components/forms/bds-slider/types/ChangeDetail.d.ts +9 -0
- package/dist/types/components/forms/bds-slider/types/ISlider.d.ts +17 -0
- package/dist/types/components/forms/bds-slider/types/ISliderOptions.d.ts +31 -0
- package/dist/types/components/forms/bds-slider/types/enum.d.ts +17 -0
- package/dist/types/components/forms/bds-slider/types/index.d.ts +6 -0
- package/dist/types/components/forms/bds-slider/types/types.d.ts +5 -0
- package/dist/types/components/forms/bds-text-field/bds-text-field.d.ts +4 -0
- package/dist/types/components/navigation/bds-breadcrumb/bds-breadcrumb.d.ts +57 -0
- package/dist/types/components/navigation/bds-breadcrumb/types/IBreadcrumb.d.ts +17 -0
- package/dist/types/components/navigation/bds-breadcrumb-item/bds-breadcrumb-item.d.ts +83 -0
- package/dist/types/components/navigation/bds-breadcrumb-item/types/IBreadcrumbItem.d.ts +20 -0
- package/dist/types/components/overlays/bds-popover/bds-popover.d.ts +4 -0
- package/dist/types/components/titles-text/bds-typography/bds-typography.d.ts +2 -46
- package/dist/types/components/titles-text/bds-typography/types/ITypography.d.ts +1 -0
- package/dist/types/components.d.ts +2187 -450
- package/dist/types/mixins/menu-behavior.mixin.d.ts +5 -1
- package/dist/types/types/form.d.ts +24 -0
- package/dist/types/types/index.d.ts +2 -1
- package/dist/types/types/orientation.d.ts +6 -0
- package/dist/types/utils/a11y/index.d.ts +4 -0
- package/dist/types/utils/a11y/keyboard/KeyboardController.d.ts +321 -0
- package/dist/types/utils/a11y/keyboard/_constants.d.ts +28 -0
- package/dist/types/utils/a11y/keyboard/focus/aria-activedescendant.d.ts +17 -0
- package/dist/types/utils/a11y/keyboard/focus/resolve.d.ts +23 -0
- package/dist/types/utils/a11y/keyboard/focus/roving-tabindex.d.ts +20 -0
- package/dist/types/utils/a11y/keyboard/navigation/grid-navigation.d.ts +9 -0
- package/dist/types/utils/a11y/keyboard/navigation/linear-navigation.d.ts +13 -0
- package/dist/types/utils/a11y/keyboard/types/IKeyboardController.d.ts +125 -0
- package/dist/types/utils/a11y/keyboard/types/index.d.ts +3 -0
- package/dist/types/utils/a11y/keyboard/types/types.d.ts +6 -0
- package/dist/types/utils/constants/common/Keys.d.ts +28 -14
- package/dist/types/utils/dom/elements.d.ts +41 -0
- package/dist/types/utils/menu/menu-item.utils.d.ts +2 -2
- package/dist/types/utils/testing/helpers.d.ts +8 -0
- package/package.json +1 -1
- package/components-build/p-B8n1ru5i.js +0 -1
- package/components-build/p-CGdxFth9.js +0 -1
- package/components-build/p-CrAt6pGl.js +0 -1
- package/components-build/p-DCwT43Kz.js +0 -1
- package/components-build/p-DIY3CDNL.js +0 -1
- package/dist/boreal-web-components/p-0028481c.entry.js +0 -1
- package/dist/boreal-web-components/p-1466de58.system.entry.js +0 -1
- package/dist/boreal-web-components/p-2b8bd1dd.entry.js +0 -1
- package/dist/boreal-web-components/p-32c23246.system.entry.js +0 -1
- package/dist/boreal-web-components/p-3fbcc233.entry.js +0 -1
- package/dist/boreal-web-components/p-401f1aee.system.entry.js +0 -1
- package/dist/boreal-web-components/p-41b48701.system.entry.js +0 -1
- package/dist/boreal-web-components/p-41cac1b8.system.entry.js +0 -1
- package/dist/boreal-web-components/p-627b19a5.system.entry.js +0 -1
- package/dist/boreal-web-components/p-649737e5.entry.js +0 -1
- package/dist/boreal-web-components/p-64cb5825.entry.js +0 -1
- package/dist/boreal-web-components/p-6a99f4c6.system.entry.js +0 -1
- package/dist/boreal-web-components/p-731b88cc.entry.js +0 -1
- package/dist/boreal-web-components/p-74c13bed.system.entry.js +0 -1
- package/dist/boreal-web-components/p-7f7ac40f.entry.js +0 -1
- package/dist/boreal-web-components/p-82d49661.entry.js +0 -1
- package/dist/boreal-web-components/p-83ca193e.entry.js +0 -1
- package/dist/boreal-web-components/p-BQ_zoZa2.js +0 -1
- package/dist/boreal-web-components/p-C8mRvnTA.js +0 -1
- package/dist/boreal-web-components/p-CdKFZYxk.system.js +0 -1
- package/dist/boreal-web-components/p-CtknSula.system.js +0 -1
- package/dist/boreal-web-components/p-CvK2d92c.js +0 -1
- package/dist/boreal-web-components/p-CzYQb3pP.js +0 -1
- package/dist/boreal-web-components/p-DZcx75cy.system.js +0 -1
- package/dist/boreal-web-components/p-Dme-NuTD.system.js +0 -1
- package/dist/boreal-web-components/p-ab5ffa15.system.entry.js +0 -1
- package/dist/boreal-web-components/p-ba2b625a.system.entry.js +0 -1
- package/dist/boreal-web-components/p-bf2ef10b.entry.js +0 -1
- package/dist/boreal-web-components/p-c7aee9f4.system.entry.js +0 -1
- package/dist/cjs/Keys-DbIXSJF2.js +0 -22
- package/dist/collection/components/forms/bds-checkbox/bds-checkbox.css +0 -90
- package/dist/esm/Keys-CzYQb3pP.js +0 -20
- package/dist/esm-es5/Keys-CzYQb3pP.js +0 -1
- package/dist/esm-es5/enum-C8mRvnTA.js +0 -1
- package/dist/esm-es5/form-associated.mixin-CvK2d92c.js +0 -1
- package/dist/esm-es5/getOffset-DKPjeBHi.js +0 -1
- package/dist/types/types/IFormProps.d.ts +0 -25
- /package/dist/collection/components/{helpers/types/IDivider.js → actions/bds-button-group/types/IButtonGroup.js} +0 -0
- /package/dist/collection/components/{helpers/types/types.js → actions/bds-list-menu/bds-list-menu/types/IListMenu.js} +0 -0
- /package/dist/collection/components/{layouts/bds-grid/grid-item → actions/bds-list-menu/bds-list-menu}/types/types.js +0 -0
- /package/dist/collection/components/{layouts/bds-grid/grid-item/types/IGridItem.js → forms/bds-checkbox/bds-checkbox-button/types/ICheckboxButton.js} +0 -0
- /package/dist/collection/components/{layouts/bds-grid/grid/types/IGrid.js → forms/bds-checkbox/bds-checkbox-card/types/ICheckboxCard.js} +0 -0
- /package/dist/collection/components/{layouts/bds-grid/grid/types/types.js → forms/bds-checkbox/bds-checkbox-group/types/ICheckboxGroup.js} +0 -0
- /package/dist/collection/{types/IFormProps.js → components/forms/bds-checkbox/bds-checkbox-group/types/types.js} +0 -0
- /package/dist/collection/components/helpers/{types → bds-divider/types}/enum.js +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.css +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/types/enum.js +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid → bds-grid}/types/index.js +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.css +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/enum.js +0 -0
- /package/dist/collection/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/index.js +0 -0
- /package/dist/types/components/helpers/{bds-divider.d.ts → bds-divider/bds-divider.d.ts} +0 -0
- /package/dist/types/components/helpers/{types → bds-divider/types}/IDivider.d.ts +0 -0
- /package/dist/types/components/helpers/{types → bds-divider/types}/enum.d.ts +0 -0
- /package/dist/types/components/helpers/{types → bds-divider/types}/types.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/bds-grid.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/IGrid.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/enum.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/index.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid → bds-grid}/types/types.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/bds-grid-item.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/IGridItem.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/enum.d.ts +0 -0
- /package/dist/types/components/layouts/bds-grid/{grid-item → bds-grid-item}/types/index.d.ts +0 -0
- /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 };
|