@octanejs/aria 0.0.1 → 0.0.4
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/package.json +6 -3
- package/src/breadcrumbs/useBreadcrumbItem.ts +95 -0
- package/src/breadcrumbs/useBreadcrumbs.ts +51 -0
- package/src/button/useButton.ts +273 -0
- package/src/button/useToggleButton.ts +131 -0
- package/src/button/useToggleButtonGroup.ts +131 -0
- package/src/checkbox/useCheckbox.ts +114 -0
- package/src/checkbox/useCheckboxGroup.ts +107 -0
- package/src/checkbox/useCheckboxGroupItem.ts +132 -0
- package/src/checkbox/utils.ts +15 -0
- package/src/combobox/useComboBox.ts +602 -0
- package/src/dialog/useDialog.ts +147 -0
- package/src/disclosure/useDisclosure.ts +180 -0
- package/src/focus/FocusRing.ts +69 -0
- package/src/focus/FocusScope.ts +1205 -0
- package/src/focus/useFocusRing.ts +125 -0
- package/src/focus/useHasTabbableChild.ts +82 -0
- package/src/focus/virtualFocus.ts +38 -0
- package/src/form/useFormValidation.ts +205 -0
- package/src/grid/useGridSelectionAnnouncement.ts +177 -0
- package/src/grid/useGridSelectionCheckbox.ts +86 -0
- package/src/grid/useHighlightSelectionDescription.ts +81 -0
- package/src/gridlist/useGridList.ts +236 -0
- package/src/gridlist/useGridListItem.ts +512 -0
- package/src/gridlist/useGridListSection.ts +78 -0
- package/src/gridlist/useGridListSelectionCheckbox.ts +47 -0
- package/src/gridlist/utils.ts +36 -0
- package/src/i18n/I18nProvider.ts +97 -0
- package/src/i18n/server.ts +115 -0
- package/src/i18n/useCollator.ts +42 -0
- package/src/i18n/useDateFormatter.ts +64 -0
- package/src/i18n/useDefaultLocale.ts +93 -0
- package/src/i18n/useFilter.ts +107 -0
- package/src/i18n/useListFormatter.ts +33 -0
- package/src/i18n/useLocalizedStringFormatter.ts +90 -0
- package/src/i18n/useNumberFormatter.ts +33 -0
- package/src/i18n/utils.ts +67 -0
- package/src/index.ts +268 -0
- package/src/intl/breadcrumbs/ar-AE.json +3 -0
- package/src/intl/breadcrumbs/bg-BG.json +3 -0
- package/src/intl/breadcrumbs/cs-CZ.json +3 -0
- package/src/intl/breadcrumbs/da-DK.json +3 -0
- package/src/intl/breadcrumbs/de-DE.json +3 -0
- package/src/intl/breadcrumbs/el-GR.json +3 -0
- package/src/intl/breadcrumbs/en-US.json +3 -0
- package/src/intl/breadcrumbs/es-ES.json +3 -0
- package/src/intl/breadcrumbs/et-EE.json +3 -0
- package/src/intl/breadcrumbs/fi-FI.json +3 -0
- package/src/intl/breadcrumbs/fr-FR.json +3 -0
- package/src/intl/breadcrumbs/he-IL.json +3 -0
- package/src/intl/breadcrumbs/hr-HR.json +3 -0
- package/src/intl/breadcrumbs/hu-HU.json +3 -0
- package/src/intl/breadcrumbs/index.ts +75 -0
- package/src/intl/breadcrumbs/it-IT.json +3 -0
- package/src/intl/breadcrumbs/ja-JP.json +3 -0
- package/src/intl/breadcrumbs/ko-KR.json +3 -0
- package/src/intl/breadcrumbs/lt-LT.json +3 -0
- package/src/intl/breadcrumbs/lv-LV.json +3 -0
- package/src/intl/breadcrumbs/nb-NO.json +3 -0
- package/src/intl/breadcrumbs/nl-NL.json +3 -0
- package/src/intl/breadcrumbs/pl-PL.json +3 -0
- package/src/intl/breadcrumbs/pt-BR.json +3 -0
- package/src/intl/breadcrumbs/pt-PT.json +3 -0
- package/src/intl/breadcrumbs/ro-RO.json +3 -0
- package/src/intl/breadcrumbs/ru-RU.json +3 -0
- package/src/intl/breadcrumbs/sk-SK.json +3 -0
- package/src/intl/breadcrumbs/sl-SI.json +3 -0
- package/src/intl/breadcrumbs/sr-SP.json +3 -0
- package/src/intl/breadcrumbs/sv-SE.json +3 -0
- package/src/intl/breadcrumbs/tr-TR.json +3 -0
- package/src/intl/breadcrumbs/uk-UA.json +3 -0
- package/src/intl/breadcrumbs/zh-CN.json +3 -0
- package/src/intl/breadcrumbs/zh-TW.json +3 -0
- package/src/intl/combobox/ar-AE.json +7 -0
- package/src/intl/combobox/bg-BG.json +7 -0
- package/src/intl/combobox/cs-CZ.json +7 -0
- package/src/intl/combobox/da-DK.json +7 -0
- package/src/intl/combobox/de-DE.json +7 -0
- package/src/intl/combobox/el-GR.json +7 -0
- package/src/intl/combobox/en-US.json +7 -0
- package/src/intl/combobox/es-ES.json +7 -0
- package/src/intl/combobox/et-EE.json +7 -0
- package/src/intl/combobox/fi-FI.json +7 -0
- package/src/intl/combobox/fr-FR.json +7 -0
- package/src/intl/combobox/he-IL.json +7 -0
- package/src/intl/combobox/hr-HR.json +7 -0
- package/src/intl/combobox/hu-HU.json +7 -0
- package/src/intl/combobox/index.ts +75 -0
- package/src/intl/combobox/it-IT.json +7 -0
- package/src/intl/combobox/ja-JP.json +7 -0
- package/src/intl/combobox/ko-KR.json +7 -0
- package/src/intl/combobox/lt-LT.json +7 -0
- package/src/intl/combobox/lv-LV.json +7 -0
- package/src/intl/combobox/nb-NO.json +7 -0
- package/src/intl/combobox/nl-NL.json +7 -0
- package/src/intl/combobox/pl-PL.json +7 -0
- package/src/intl/combobox/pt-BR.json +7 -0
- package/src/intl/combobox/pt-PT.json +7 -0
- package/src/intl/combobox/ro-RO.json +7 -0
- package/src/intl/combobox/ru-RU.json +7 -0
- package/src/intl/combobox/sk-SK.json +7 -0
- package/src/intl/combobox/sl-SI.json +7 -0
- package/src/intl/combobox/sr-SP.json +7 -0
- package/src/intl/combobox/sv-SE.json +7 -0
- package/src/intl/combobox/tr-TR.json +7 -0
- package/src/intl/combobox/uk-UA.json +7 -0
- package/src/intl/combobox/zh-CN.json +7 -0
- package/src/intl/combobox/zh-TW.json +7 -0
- package/src/intl/grid/ar-AE.json +8 -0
- package/src/intl/grid/bg-BG.json +8 -0
- package/src/intl/grid/cs-CZ.json +8 -0
- package/src/intl/grid/da-DK.json +8 -0
- package/src/intl/grid/de-DE.json +8 -0
- package/src/intl/grid/el-GR.json +8 -0
- package/src/intl/grid/en-US.json +8 -0
- package/src/intl/grid/es-ES.json +8 -0
- package/src/intl/grid/et-EE.json +8 -0
- package/src/intl/grid/fi-FI.json +8 -0
- package/src/intl/grid/fr-FR.json +8 -0
- package/src/intl/grid/he-IL.json +8 -0
- package/src/intl/grid/hr-HR.json +8 -0
- package/src/intl/grid/hu-HU.json +8 -0
- package/src/intl/grid/index.ts +75 -0
- package/src/intl/grid/it-IT.json +8 -0
- package/src/intl/grid/ja-JP.json +8 -0
- package/src/intl/grid/ko-KR.json +8 -0
- package/src/intl/grid/lt-LT.json +8 -0
- package/src/intl/grid/lv-LV.json +8 -0
- package/src/intl/grid/nb-NO.json +8 -0
- package/src/intl/grid/nl-NL.json +8 -0
- package/src/intl/grid/pl-PL.json +8 -0
- package/src/intl/grid/pt-BR.json +8 -0
- package/src/intl/grid/pt-PT.json +8 -0
- package/src/intl/grid/ro-RO.json +8 -0
- package/src/intl/grid/ru-RU.json +8 -0
- package/src/intl/grid/sk-SK.json +8 -0
- package/src/intl/grid/sl-SI.json +8 -0
- package/src/intl/grid/sr-SP.json +8 -0
- package/src/intl/grid/sv-SE.json +8 -0
- package/src/intl/grid/tr-TR.json +8 -0
- package/src/intl/grid/uk-UA.json +8 -0
- package/src/intl/grid/zh-CN.json +8 -0
- package/src/intl/grid/zh-TW.json +8 -0
- package/src/intl/gridlist/ar-AE.json +4 -0
- package/src/intl/gridlist/bg-BG.json +4 -0
- package/src/intl/gridlist/cs-CZ.json +4 -0
- package/src/intl/gridlist/da-DK.json +4 -0
- package/src/intl/gridlist/de-DE.json +4 -0
- package/src/intl/gridlist/el-GR.json +4 -0
- package/src/intl/gridlist/en-US.json +4 -0
- package/src/intl/gridlist/es-ES.json +4 -0
- package/src/intl/gridlist/et-EE.json +4 -0
- package/src/intl/gridlist/fi-FI.json +4 -0
- package/src/intl/gridlist/fr-FR.json +4 -0
- package/src/intl/gridlist/he-IL.json +4 -0
- package/src/intl/gridlist/hr-HR.json +4 -0
- package/src/intl/gridlist/hu-HU.json +4 -0
- package/src/intl/gridlist/index.ts +75 -0
- package/src/intl/gridlist/it-IT.json +4 -0
- package/src/intl/gridlist/ja-JP.json +4 -0
- package/src/intl/gridlist/ko-KR.json +4 -0
- package/src/intl/gridlist/lt-LT.json +4 -0
- package/src/intl/gridlist/lv-LV.json +4 -0
- package/src/intl/gridlist/nb-NO.json +4 -0
- package/src/intl/gridlist/nl-NL.json +4 -0
- package/src/intl/gridlist/pl-PL.json +4 -0
- package/src/intl/gridlist/pt-BR.json +4 -0
- package/src/intl/gridlist/pt-PT.json +4 -0
- package/src/intl/gridlist/ro-RO.json +4 -0
- package/src/intl/gridlist/ru-RU.json +4 -0
- package/src/intl/gridlist/sk-SK.json +4 -0
- package/src/intl/gridlist/sl-SI.json +4 -0
- package/src/intl/gridlist/sr-SP.json +4 -0
- package/src/intl/gridlist/sv-SE.json +4 -0
- package/src/intl/gridlist/tr-TR.json +4 -0
- package/src/intl/gridlist/uk-UA.json +4 -0
- package/src/intl/gridlist/zh-CN.json +4 -0
- package/src/intl/gridlist/zh-TW.json +4 -0
- package/src/intl/menu/ar-AE.json +3 -0
- package/src/intl/menu/bg-BG.json +3 -0
- package/src/intl/menu/cs-CZ.json +3 -0
- package/src/intl/menu/da-DK.json +3 -0
- package/src/intl/menu/de-DE.json +3 -0
- package/src/intl/menu/el-GR.json +3 -0
- package/src/intl/menu/en-US.json +3 -0
- package/src/intl/menu/es-ES.json +3 -0
- package/src/intl/menu/et-EE.json +3 -0
- package/src/intl/menu/fi-FI.json +3 -0
- package/src/intl/menu/fr-FR.json +3 -0
- package/src/intl/menu/he-IL.json +3 -0
- package/src/intl/menu/hr-HR.json +3 -0
- package/src/intl/menu/hu-HU.json +3 -0
- package/src/intl/menu/index.ts +75 -0
- package/src/intl/menu/it-IT.json +3 -0
- package/src/intl/menu/ja-JP.json +3 -0
- package/src/intl/menu/ko-KR.json +3 -0
- package/src/intl/menu/lt-LT.json +3 -0
- package/src/intl/menu/lv-LV.json +3 -0
- package/src/intl/menu/nb-NO.json +3 -0
- package/src/intl/menu/nl-NL.json +3 -0
- package/src/intl/menu/pl-PL.json +3 -0
- package/src/intl/menu/pt-BR.json +3 -0
- package/src/intl/menu/pt-PT.json +3 -0
- package/src/intl/menu/ro-RO.json +3 -0
- package/src/intl/menu/ru-RU.json +3 -0
- package/src/intl/menu/sk-SK.json +3 -0
- package/src/intl/menu/sl-SI.json +3 -0
- package/src/intl/menu/sr-SP.json +3 -0
- package/src/intl/menu/sv-SE.json +3 -0
- package/src/intl/menu/tr-TR.json +3 -0
- package/src/intl/menu/uk-UA.json +3 -0
- package/src/intl/menu/zh-CN.json +3 -0
- package/src/intl/menu/zh-TW.json +3 -0
- package/src/intl/numberfield/ar-AE.json +5 -0
- package/src/intl/numberfield/bg-BG.json +5 -0
- package/src/intl/numberfield/cs-CZ.json +5 -0
- package/src/intl/numberfield/da-DK.json +5 -0
- package/src/intl/numberfield/de-DE.json +5 -0
- package/src/intl/numberfield/el-GR.json +5 -0
- package/src/intl/numberfield/en-US.json +5 -0
- package/src/intl/numberfield/es-ES.json +5 -0
- package/src/intl/numberfield/et-EE.json +5 -0
- package/src/intl/numberfield/fi-FI.json +5 -0
- package/src/intl/numberfield/fr-FR.json +5 -0
- package/src/intl/numberfield/he-IL.json +5 -0
- package/src/intl/numberfield/hr-HR.json +5 -0
- package/src/intl/numberfield/hu-HU.json +5 -0
- package/src/intl/numberfield/index.ts +75 -0
- package/src/intl/numberfield/it-IT.json +5 -0
- package/src/intl/numberfield/ja-JP.json +5 -0
- package/src/intl/numberfield/ko-KR.json +5 -0
- package/src/intl/numberfield/lt-LT.json +5 -0
- package/src/intl/numberfield/lv-LV.json +5 -0
- package/src/intl/numberfield/nb-NO.json +5 -0
- package/src/intl/numberfield/nl-NL.json +5 -0
- package/src/intl/numberfield/pl-PL.json +5 -0
- package/src/intl/numberfield/pt-BR.json +5 -0
- package/src/intl/numberfield/pt-PT.json +5 -0
- package/src/intl/numberfield/ro-RO.json +5 -0
- package/src/intl/numberfield/ru-RU.json +5 -0
- package/src/intl/numberfield/sk-SK.json +5 -0
- package/src/intl/numberfield/sl-SI.json +5 -0
- package/src/intl/numberfield/sr-SP.json +5 -0
- package/src/intl/numberfield/sv-SE.json +5 -0
- package/src/intl/numberfield/tr-TR.json +5 -0
- package/src/intl/numberfield/uk-UA.json +5 -0
- package/src/intl/numberfield/zh-CN.json +5 -0
- package/src/intl/numberfield/zh-TW.json +5 -0
- package/src/intl/overlays/ar-AE.json +3 -0
- package/src/intl/overlays/bg-BG.json +3 -0
- package/src/intl/overlays/cs-CZ.json +3 -0
- package/src/intl/overlays/da-DK.json +3 -0
- package/src/intl/overlays/de-DE.json +3 -0
- package/src/intl/overlays/el-GR.json +3 -0
- package/src/intl/overlays/en-US.json +3 -0
- package/src/intl/overlays/es-ES.json +3 -0
- package/src/intl/overlays/et-EE.json +3 -0
- package/src/intl/overlays/fi-FI.json +3 -0
- package/src/intl/overlays/fr-FR.json +3 -0
- package/src/intl/overlays/he-IL.json +3 -0
- package/src/intl/overlays/hr-HR.json +3 -0
- package/src/intl/overlays/hu-HU.json +3 -0
- package/src/intl/overlays/index.ts +75 -0
- package/src/intl/overlays/it-IT.json +3 -0
- package/src/intl/overlays/ja-JP.json +3 -0
- package/src/intl/overlays/ko-KR.json +3 -0
- package/src/intl/overlays/lt-LT.json +3 -0
- package/src/intl/overlays/lv-LV.json +3 -0
- package/src/intl/overlays/nb-NO.json +3 -0
- package/src/intl/overlays/nl-NL.json +3 -0
- package/src/intl/overlays/pl-PL.json +3 -0
- package/src/intl/overlays/pt-BR.json +3 -0
- package/src/intl/overlays/pt-PT.json +3 -0
- package/src/intl/overlays/ro-RO.json +3 -0
- package/src/intl/overlays/ru-RU.json +3 -0
- package/src/intl/overlays/sk-SK.json +3 -0
- package/src/intl/overlays/sl-SI.json +3 -0
- package/src/intl/overlays/sr-SP.json +3 -0
- package/src/intl/overlays/sv-SE.json +3 -0
- package/src/intl/overlays/tr-TR.json +3 -0
- package/src/intl/overlays/uk-UA.json +3 -0
- package/src/intl/overlays/zh-CN.json +3 -0
- package/src/intl/overlays/zh-TW.json +3 -0
- package/src/intl/searchfield/ar-AE.json +3 -0
- package/src/intl/searchfield/bg-BG.json +3 -0
- package/src/intl/searchfield/cs-CZ.json +3 -0
- package/src/intl/searchfield/da-DK.json +3 -0
- package/src/intl/searchfield/de-DE.json +3 -0
- package/src/intl/searchfield/el-GR.json +3 -0
- package/src/intl/searchfield/en-US.json +3 -0
- package/src/intl/searchfield/es-ES.json +3 -0
- package/src/intl/searchfield/et-EE.json +3 -0
- package/src/intl/searchfield/fi-FI.json +3 -0
- package/src/intl/searchfield/fr-FR.json +3 -0
- package/src/intl/searchfield/he-IL.json +3 -0
- package/src/intl/searchfield/hr-HR.json +3 -0
- package/src/intl/searchfield/hu-HU.json +3 -0
- package/src/intl/searchfield/index.ts +75 -0
- package/src/intl/searchfield/it-IT.json +3 -0
- package/src/intl/searchfield/ja-JP.json +3 -0
- package/src/intl/searchfield/ko-KR.json +3 -0
- package/src/intl/searchfield/lt-LT.json +3 -0
- package/src/intl/searchfield/lv-LV.json +3 -0
- package/src/intl/searchfield/nb-NO.json +3 -0
- package/src/intl/searchfield/nl-NL.json +3 -0
- package/src/intl/searchfield/pl-PL.json +3 -0
- package/src/intl/searchfield/pt-BR.json +3 -0
- package/src/intl/searchfield/pt-PT.json +3 -0
- package/src/intl/searchfield/ro-RO.json +3 -0
- package/src/intl/searchfield/ru-RU.json +3 -0
- package/src/intl/searchfield/sk-SK.json +3 -0
- package/src/intl/searchfield/sl-SI.json +3 -0
- package/src/intl/searchfield/sr-SP.json +3 -0
- package/src/intl/searchfield/sv-SE.json +3 -0
- package/src/intl/searchfield/tr-TR.json +3 -0
- package/src/intl/searchfield/uk-UA.json +3 -0
- package/src/intl/searchfield/zh-CN.json +3 -0
- package/src/intl/searchfield/zh-TW.json +3 -0
- package/src/intl/spinbutton/ar-AE.json +3 -0
- package/src/intl/spinbutton/bg-BG.json +3 -0
- package/src/intl/spinbutton/cs-CZ.json +3 -0
- package/src/intl/spinbutton/da-DK.json +3 -0
- package/src/intl/spinbutton/de-DE.json +3 -0
- package/src/intl/spinbutton/el-GR.json +3 -0
- package/src/intl/spinbutton/en-US.json +3 -0
- package/src/intl/spinbutton/es-ES.json +3 -0
- package/src/intl/spinbutton/et-EE.json +3 -0
- package/src/intl/spinbutton/fi-FI.json +3 -0
- package/src/intl/spinbutton/fr-FR.json +3 -0
- package/src/intl/spinbutton/he-IL.json +3 -0
- package/src/intl/spinbutton/hr-HR.json +3 -0
- package/src/intl/spinbutton/hu-HU.json +3 -0
- package/src/intl/spinbutton/index.ts +75 -0
- package/src/intl/spinbutton/it-IT.json +3 -0
- package/src/intl/spinbutton/ja-JP.json +3 -0
- package/src/intl/spinbutton/ko-KR.json +3 -0
- package/src/intl/spinbutton/lt-LT.json +3 -0
- package/src/intl/spinbutton/lv-LV.json +3 -0
- package/src/intl/spinbutton/nb-NO.json +3 -0
- package/src/intl/spinbutton/nl-NL.json +3 -0
- package/src/intl/spinbutton/pl-PL.json +3 -0
- package/src/intl/spinbutton/pt-BR.json +3 -0
- package/src/intl/spinbutton/pt-PT.json +3 -0
- package/src/intl/spinbutton/ro-RO.json +3 -0
- package/src/intl/spinbutton/ru-RU.json +3 -0
- package/src/intl/spinbutton/sk-SK.json +3 -0
- package/src/intl/spinbutton/sl-SI.json +3 -0
- package/src/intl/spinbutton/sr-SP.json +3 -0
- package/src/intl/spinbutton/sv-SE.json +3 -0
- package/src/intl/spinbutton/tr-TR.json +3 -0
- package/src/intl/spinbutton/uk-UA.json +3 -0
- package/src/intl/spinbutton/zh-CN.json +3 -0
- package/src/intl/spinbutton/zh-TW.json +3 -0
- package/src/intl/tag/ar-AE.json +4 -0
- package/src/intl/tag/bg-BG.json +4 -0
- package/src/intl/tag/cs-CZ.json +4 -0
- package/src/intl/tag/da-DK.json +4 -0
- package/src/intl/tag/de-DE.json +4 -0
- package/src/intl/tag/el-GR.json +4 -0
- package/src/intl/tag/en-US.json +4 -0
- package/src/intl/tag/es-ES.json +4 -0
- package/src/intl/tag/et-EE.json +4 -0
- package/src/intl/tag/fi-FI.json +4 -0
- package/src/intl/tag/fr-FR.json +4 -0
- package/src/intl/tag/he-IL.json +4 -0
- package/src/intl/tag/hr-HR.json +4 -0
- package/src/intl/tag/hu-HU.json +4 -0
- package/src/intl/tag/index.ts +75 -0
- package/src/intl/tag/it-IT.json +4 -0
- package/src/intl/tag/ja-JP.json +4 -0
- package/src/intl/tag/ko-KR.json +4 -0
- package/src/intl/tag/lt-LT.json +4 -0
- package/src/intl/tag/lv-LV.json +4 -0
- package/src/intl/tag/nb-NO.json +4 -0
- package/src/intl/tag/nl-NL.json +4 -0
- package/src/intl/tag/pl-PL.json +4 -0
- package/src/intl/tag/pt-BR.json +4 -0
- package/src/intl/tag/pt-PT.json +4 -0
- package/src/intl/tag/ro-RO.json +4 -0
- package/src/intl/tag/ru-RU.json +4 -0
- package/src/intl/tag/sk-SK.json +4 -0
- package/src/intl/tag/sl-SI.json +4 -0
- package/src/intl/tag/sr-SP.json +4 -0
- package/src/intl/tag/sv-SE.json +4 -0
- package/src/intl/tag/tr-TR.json +4 -0
- package/src/intl/tag/uk-UA.json +4 -0
- package/src/intl/tag/zh-CN.json +4 -0
- package/src/intl/tag/zh-TW.json +4 -0
- package/src/label/useField.ts +67 -0
- package/src/label/useLabel.ts +78 -0
- package/src/link/useLink.ts +108 -0
- package/src/listbox/useListBox.ts +224 -0
- package/src/listbox/useListBoxSection.ts +74 -0
- package/src/listbox/useOption.ts +212 -0
- package/src/listbox/utils.ts +38 -0
- package/src/live-announcer/LiveAnnouncer.ts +172 -0
- package/src/menu/useMenu.ts +139 -0
- package/src/menu/useMenuItem.ts +453 -0
- package/src/menu/useMenuSection.ts +70 -0
- package/src/menu/useMenuTrigger.ts +189 -0
- package/src/menu/useSafelyMouseToSubmenu.ts +236 -0
- package/src/menu/useSubmenuTrigger.ts +357 -0
- package/src/menu/utils.ts +16 -0
- package/src/meter/useMeter.ts +43 -0
- package/src/numberfield/useNumberField.ts +572 -0
- package/src/overlays/DismissButton.ts +50 -0
- package/src/overlays/Overlay.ts +106 -0
- package/src/overlays/PortalProvider.ts +37 -0
- package/src/overlays/ariaHideOutside.ts +299 -0
- package/src/overlays/calculatePosition.ts +856 -0
- package/src/overlays/useCloseOnScroll.ts +75 -0
- package/src/overlays/useModal.ts +224 -0
- package/src/overlays/useModalOverlay.ts +103 -0
- package/src/overlays/useOverlay.ts +199 -0
- package/src/overlays/useOverlayPosition.ts +462 -0
- package/src/overlays/useOverlayTrigger.ts +94 -0
- package/src/overlays/usePopover.ts +170 -0
- package/src/overlays/usePreventScroll.ts +316 -0
- package/src/progress/useProgressBar.ts +115 -0
- package/src/radio/useRadio.ts +219 -0
- package/src/radio/useRadioGroup.ts +184 -0
- package/src/radio/utils.ts +15 -0
- package/src/searchfield/useSearchField.ts +156 -0
- package/src/select/HiddenSelect.ts +261 -0
- package/src/select/useSelect.ts +332 -0
- package/src/selection/DOMLayoutDelegate.ts +51 -0
- package/src/selection/ListKeyboardDelegate.ts +392 -0
- package/src/selection/useSelectableCollection.ts +750 -0
- package/src/selection/useSelectableItem.ts +499 -0
- package/src/selection/useSelectableList.ts +132 -0
- package/src/selection/useTypeSelect.ts +192 -0
- package/src/selection/utils.ts +50 -0
- package/src/separator/useSeparator.ts +41 -0
- package/src/slider/useSlider.ts +277 -0
- package/src/slider/useSliderThumb.ts +375 -0
- package/src/slider/utils.ts +20 -0
- package/src/spinbutton/useSpinButton.ts +380 -0
- package/src/stately/checkbox/useCheckboxGroupState.ts +177 -0
- package/src/stately/collections/CollectionBuilder.ts +332 -0
- package/src/stately/collections/Item.ts +78 -0
- package/src/stately/collections/Section.ts +52 -0
- package/src/stately/collections/getChildNodes.ts +94 -0
- package/src/stately/collections/getItemCount.ts +29 -0
- package/src/stately/collections/types.ts +20 -0
- package/src/stately/collections/useCollection.ts +54 -0
- package/src/stately/combobox/useComboBoxState.ts +763 -0
- package/src/stately/disclosure/useDisclosureGroupState.ts +108 -0
- package/src/stately/disclosure/useDisclosureState.ts +84 -0
- package/src/stately/form/useFormValidationState.ts +331 -0
- package/src/stately/index.ts +111 -0
- package/src/stately/list/ListCollection.ts +104 -0
- package/src/stately/list/useListState.ts +178 -0
- package/src/stately/list/useSingleSelectListState.ts +101 -0
- package/src/stately/menu/useMenuTriggerState.ts +120 -0
- package/src/stately/menu/useSubmenuTriggerState.ts +116 -0
- package/src/stately/numberfield/useNumberFieldState.ts +430 -0
- package/src/stately/overlays/useOverlayTriggerState.ts +85 -0
- package/src/stately/radio/useRadioGroupState.ts +150 -0
- package/src/stately/searchfield/useSearchFieldState.ts +79 -0
- package/src/stately/select/useSelectState.ts +322 -0
- package/src/stately/selection/Selection.ts +23 -0
- package/src/stately/selection/SelectionManager.ts +541 -0
- package/src/stately/selection/types.ts +117 -0
- package/src/stately/selection/useMultipleSelectionState.ts +178 -0
- package/src/stately/slider/useSliderState.ts +458 -0
- package/src/stately/tabs/useTabListState.ts +149 -0
- package/src/stately/toggle/useToggleGroupState.ts +109 -0
- package/src/stately/toggle/useToggleState.ts +100 -0
- package/src/stately/tooltip/useTooltipTriggerState.ts +218 -0
- package/src/stately/tree/TreeCollection.ts +92 -0
- package/src/stately/tree/useTreeState.ts +121 -0
- package/src/stately/utils/useControlledState.ts +7 -0
- package/src/switch/useSwitch.ts +77 -0
- package/src/tabs/TabsKeyboardDelegate.ts +99 -0
- package/src/tabs/useTab.ts +117 -0
- package/src/tabs/useTabList.ts +105 -0
- package/src/tabs/useTabPanel.ts +75 -0
- package/src/tabs/utils.ts +32 -0
- package/src/tag/useTag.ts +162 -0
- package/src/tag/useTagGroup.ts +192 -0
- package/src/textfield/useFormattedTextField.ts +203 -0
- package/src/textfield/useTextField.ts +248 -0
- package/src/toggle/useToggle.ts +265 -0
- package/src/toolbar/useToolbar.ts +147 -0
- package/src/tooltip/useTooltip.ts +64 -0
- package/src/tooltip/useTooltipTrigger.ts +187 -0
- package/src/utils/filterDOMProps.ts +121 -0
- package/src/utils/getScrollParent.ts +16 -0
- package/src/utils/getScrollParents.ts +20 -0
- package/src/utils/keyboard.ts +38 -0
- package/src/utils/scrollIntoView.ts +178 -0
- package/src/utils/useDeepMemo.ts +30 -0
- package/src/utils/useFormReset.ts +54 -0
- package/src/utils/useId.ts +5 -0
- package/src/utils/useLabels.ts +53 -0
- package/src/utils/useResizeObserver.ts +70 -0
- package/src/utils/useSlot.ts +78 -0
- package/src/utils/useUpdateEffect.ts +54 -0
- package/src/utils/useUpdateLayoutEffect.ts +58 -0
- package/src/visually-hidden/VisuallyHidden.ts +100 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/focus/useFocusRing.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `DOMAttributes` from '@react-types/shared' drags React attribute types; a local
|
|
4
|
+
// structural alias replaces it (the focus props are octane NATIVE delegated event props).
|
|
5
|
+
// - Public-hook slot threading (splitSlot/subSlot) per the binding convention; sibling
|
|
6
|
+
// ported hooks (useFocus, useFocusWithin, useFocusVisibleListener) are called with a
|
|
7
|
+
// trailing `subSlot(slot, ...)`. Explicit dep arrays are preserved exactly.
|
|
8
|
+
|
|
9
|
+
import { useCallback, useRef, useState } from 'octane';
|
|
10
|
+
|
|
11
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
12
|
+
import { isFocusVisible, useFocusVisibleListener } from '../interactions/useFocusVisible';
|
|
13
|
+
import { useFocus } from '../interactions/useFocus';
|
|
14
|
+
import { useFocusWithin } from '../interactions/useFocusWithin';
|
|
15
|
+
|
|
16
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React attribute types).
|
|
17
|
+
export type DOMAttributes = Record<string, any>;
|
|
18
|
+
|
|
19
|
+
export interface AriaFocusRingProps {
|
|
20
|
+
/**
|
|
21
|
+
* Whether to show the focus ring when something
|
|
22
|
+
* inside the container element has focus (true), or
|
|
23
|
+
* only if the container itself has focus (false).
|
|
24
|
+
*
|
|
25
|
+
* @default 'false'
|
|
26
|
+
*/
|
|
27
|
+
within?: boolean;
|
|
28
|
+
|
|
29
|
+
/** Whether the element is a text input. */
|
|
30
|
+
isTextInput?: boolean;
|
|
31
|
+
|
|
32
|
+
/** Whether the element will be auto focused. */
|
|
33
|
+
autoFocus?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FocusRingAria {
|
|
37
|
+
/** Whether the element is currently focused. */
|
|
38
|
+
isFocused: boolean;
|
|
39
|
+
|
|
40
|
+
/** Whether keyboard focus should be visible. */
|
|
41
|
+
isFocusVisible: boolean;
|
|
42
|
+
|
|
43
|
+
/** Props to apply to the container element with the focus ring. */
|
|
44
|
+
focusProps: DOMAttributes;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Determines whether a focus ring should be shown to indicate keyboard focus.
|
|
49
|
+
* Focus rings are visible only when the user is interacting with a keyboard,
|
|
50
|
+
* not with a mouse, touch, or other input methods.
|
|
51
|
+
*/
|
|
52
|
+
export function useFocusRing(props?: AriaFocusRingProps): FocusRingAria;
|
|
53
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
54
|
+
export function useFocusRing(
|
|
55
|
+
props: AriaFocusRingProps | undefined,
|
|
56
|
+
slot: symbol | undefined,
|
|
57
|
+
): FocusRingAria;
|
|
58
|
+
export function useFocusRing(...args: any[]): FocusRingAria {
|
|
59
|
+
const [user, slotArg] = splitSlot(args);
|
|
60
|
+
const slot = slotArg ?? S('useFocusRing');
|
|
61
|
+
const props = (user[0] as AriaFocusRingProps) ?? {};
|
|
62
|
+
|
|
63
|
+
let { autoFocus = false, isTextInput, within } = props;
|
|
64
|
+
let state = useRef(
|
|
65
|
+
{
|
|
66
|
+
isFocused: false,
|
|
67
|
+
isFocusVisible: autoFocus || isFocusVisible(),
|
|
68
|
+
},
|
|
69
|
+
subSlot(slot, 'state'),
|
|
70
|
+
);
|
|
71
|
+
let [isFocused, setFocused] = useState(false, subSlot(slot, 'focused'));
|
|
72
|
+
let [isFocusVisibleState, setFocusVisible] = useState(
|
|
73
|
+
() => state.current.isFocused && state.current.isFocusVisible,
|
|
74
|
+
subSlot(slot, 'visible'),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
let updateState = useCallback(
|
|
78
|
+
() => setFocusVisible(state.current.isFocused && state.current.isFocusVisible),
|
|
79
|
+
[],
|
|
80
|
+
subSlot(slot, 'update'),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
let onFocusChange = useCallback(
|
|
84
|
+
(isFocused: boolean) => {
|
|
85
|
+
state.current.isFocused = isFocused;
|
|
86
|
+
state.current.isFocusVisible = isFocusVisible();
|
|
87
|
+
setFocused(isFocused);
|
|
88
|
+
updateState();
|
|
89
|
+
},
|
|
90
|
+
[updateState],
|
|
91
|
+
subSlot(slot, 'change'),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
useFocusVisibleListener(
|
|
95
|
+
(isFocusVisible) => {
|
|
96
|
+
state.current.isFocusVisible = isFocusVisible;
|
|
97
|
+
updateState();
|
|
98
|
+
},
|
|
99
|
+
[isTextInput, isFocused],
|
|
100
|
+
{ enabled: isFocused, isTextInput },
|
|
101
|
+
subSlot(slot, 'listener'),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
let { focusProps } = useFocus(
|
|
105
|
+
{
|
|
106
|
+
isDisabled: within,
|
|
107
|
+
onFocusChange,
|
|
108
|
+
},
|
|
109
|
+
subSlot(slot, 'focus'),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
let { focusWithinProps } = useFocusWithin(
|
|
113
|
+
{
|
|
114
|
+
isDisabled: !within,
|
|
115
|
+
onFocusWithinChange: onFocusChange,
|
|
116
|
+
},
|
|
117
|
+
subSlot(slot, 'within'),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
isFocused,
|
|
122
|
+
isFocusVisible: isFocusVisibleState,
|
|
123
|
+
focusProps: within ? focusWithinProps : focusProps,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/focus/useHasTabbableChild.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - Public-hook slot threading (splitSlot/subSlot) per the binding convention.
|
|
4
|
+
// - Upstream's no-deps `useLayoutEffect` (re-runs after every render) becomes an explicit
|
|
5
|
+
// `null` deps argument — octane's convention for run-every-render effects.
|
|
6
|
+
|
|
7
|
+
import { getFocusableTreeWalker } from './FocusScope';
|
|
8
|
+
import type { RefObject } from '@react-types/shared';
|
|
9
|
+
import { useState } from 'octane';
|
|
10
|
+
|
|
11
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
12
|
+
import { useLayoutEffect } from '../utils/useLayoutEffect';
|
|
13
|
+
|
|
14
|
+
interface AriaHasTabbableChildOptions {
|
|
15
|
+
isDisabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// This was created for a special empty case of a component that can have child or
|
|
19
|
+
// be empty, like Collection/Virtualizer/Table/ListView/etc. When these components
|
|
20
|
+
// are empty they can have a message with a tabbable element, which is like them
|
|
21
|
+
// being not empty, when it comes to focus and tab order.
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns whether an element has a tabbable child, and updates as children change.
|
|
25
|
+
*
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
export function useHasTabbableChild(
|
|
29
|
+
ref: RefObject<Element | null>,
|
|
30
|
+
options?: AriaHasTabbableChildOptions,
|
|
31
|
+
): boolean;
|
|
32
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
33
|
+
export function useHasTabbableChild(
|
|
34
|
+
ref: RefObject<Element | null>,
|
|
35
|
+
options: AriaHasTabbableChildOptions | undefined,
|
|
36
|
+
slot: symbol | undefined,
|
|
37
|
+
): boolean;
|
|
38
|
+
export function useHasTabbableChild(...args: any[]): boolean {
|
|
39
|
+
const [user, slotArg] = splitSlot(args);
|
|
40
|
+
const slot = slotArg ?? S('useHasTabbableChild');
|
|
41
|
+
const ref = user[0] as RefObject<Element | null>;
|
|
42
|
+
const options = user[1] as AriaHasTabbableChildOptions | undefined;
|
|
43
|
+
|
|
44
|
+
let isDisabled = options?.isDisabled;
|
|
45
|
+
let [hasTabbableChild, setHasTabbableChild] = useState(false, subSlot(slot, 'has'));
|
|
46
|
+
|
|
47
|
+
useLayoutEffect(
|
|
48
|
+
() => {
|
|
49
|
+
if (ref?.current && !isDisabled) {
|
|
50
|
+
let update = () => {
|
|
51
|
+
if (ref.current) {
|
|
52
|
+
let walker = getFocusableTreeWalker(ref.current, { tabbable: true });
|
|
53
|
+
setHasTabbableChild(!!walker.nextNode());
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
update();
|
|
58
|
+
|
|
59
|
+
// Update when new elements are inserted, or the tabIndex/disabled attribute updates.
|
|
60
|
+
let observer = new MutationObserver(update);
|
|
61
|
+
observer.observe(ref.current, {
|
|
62
|
+
subtree: true,
|
|
63
|
+
childList: true,
|
|
64
|
+
attributes: true,
|
|
65
|
+
attributeFilter: ['tabIndex', 'disabled'],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
// Disconnect mutation observer when an update occurs on the top-level component
|
|
70
|
+
// so we update synchronously after re-rendering. Otherwise React will emit act warnings
|
|
71
|
+
// in tests since mutation observers fire asynchronously. The mutation observer is necessary
|
|
72
|
+
// so we also update if a child component re-renders and adds/removes something tabbable.
|
|
73
|
+
observer.disconnect();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
null,
|
|
78
|
+
subSlot(slot, 'observe'),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return isDisabled ? false : hasTabbableChild;
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/focus/virtualFocus.ts).
|
|
2
|
+
// Verbatim — no octane adaptations. The dispatched FocusEvents are real native events, so
|
|
3
|
+
// octane's capture-delegated focus handlers observe them exactly like browser-initiated ones.
|
|
4
|
+
|
|
5
|
+
import { getActiveElement } from '../utils/shadowdom/DOMFunctions';
|
|
6
|
+
import { getOwnerDocument } from '../utils/domHelpers';
|
|
7
|
+
|
|
8
|
+
export function moveVirtualFocus(to: Element | null): void {
|
|
9
|
+
let from = getVirtuallyFocusedElement(getOwnerDocument(to));
|
|
10
|
+
if (from !== to) {
|
|
11
|
+
if (from) {
|
|
12
|
+
dispatchVirtualBlur(from, to);
|
|
13
|
+
}
|
|
14
|
+
if (to) {
|
|
15
|
+
dispatchVirtualFocus(to, from);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function dispatchVirtualBlur(from: Element, to: Element | null): void {
|
|
21
|
+
from.dispatchEvent(new FocusEvent('blur', { relatedTarget: to }));
|
|
22
|
+
from.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: to }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function dispatchVirtualFocus(to: Element, from: Element | null): void {
|
|
26
|
+
to.dispatchEvent(new FocusEvent('focus', { relatedTarget: from }));
|
|
27
|
+
to.dispatchEvent(new FocusEvent('focusin', { bubbles: true, relatedTarget: from }));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getVirtuallyFocusedElement(document: Document): Element | null {
|
|
31
|
+
let activeElement = getActiveElement(document);
|
|
32
|
+
let activeDescendant = activeElement?.getAttribute('aria-activedescendant');
|
|
33
|
+
if (activeDescendant) {
|
|
34
|
+
return document.getElementById(activeDescendant) || activeElement;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return activeElement;
|
|
38
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/form/useFormValidation.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `react-stately/private/form/useFormValidationState` → the ported
|
|
4
|
+
// `../stately/form/useFormValidationState`.
|
|
5
|
+
// - Public-hook slot threading (splitSlot/subSlot) per the binding convention; upstream's
|
|
6
|
+
// dep-less `useLayoutEffect` becomes an explicit `null`; the explicit
|
|
7
|
+
// `[ref, validationBehavior]` effect deps are preserved exactly.
|
|
8
|
+
// - The native `invalid`/`change`/`reset` listeners are direct `addEventListener` wiring
|
|
9
|
+
// (never a synthetic layer), exactly like upstream: native `change` is the platform's
|
|
10
|
+
// value-commit event, so it is NOT renamed to `input` (the onInput rule covers event
|
|
11
|
+
// *props* on text controls, not native commit listeners).
|
|
12
|
+
// - The `form.reset` interception ports verbatim: upstream added it to ignore React
|
|
13
|
+
// server-action auto-resets; in octane it still (harmlessly) ignores programmatic
|
|
14
|
+
// `form.reset()` calls that occur outside a user event.
|
|
15
|
+
import type { RefObject, Validation, ValidationResult } from '@react-types/shared';
|
|
16
|
+
import { useEffect, useRef } from 'octane';
|
|
17
|
+
|
|
18
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
19
|
+
import type { FormValidationState } from '../stately/form/useFormValidationState';
|
|
20
|
+
import { setInteractionModality } from '../interactions/useFocusVisible';
|
|
21
|
+
import { getEventTarget } from '../utils/shadowdom/DOMFunctions';
|
|
22
|
+
import { useEffectEvent } from '../utils/useEffectEvent';
|
|
23
|
+
import { useLayoutEffect } from '../utils/useLayoutEffect';
|
|
24
|
+
|
|
25
|
+
type ValidatableElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
26
|
+
|
|
27
|
+
interface FormValidationProps<T> extends Validation<T> {
|
|
28
|
+
focus?: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useFormValidation<T>(
|
|
32
|
+
props: FormValidationProps<T>,
|
|
33
|
+
state: FormValidationState,
|
|
34
|
+
ref: RefObject<ValidatableElement | null> | undefined,
|
|
35
|
+
): void;
|
|
36
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
37
|
+
export function useFormValidation<T>(
|
|
38
|
+
props: FormValidationProps<T>,
|
|
39
|
+
state: FormValidationState,
|
|
40
|
+
ref: RefObject<ValidatableElement | null> | undefined,
|
|
41
|
+
slot: symbol | undefined,
|
|
42
|
+
): void;
|
|
43
|
+
export function useFormValidation(...args: any[]): void {
|
|
44
|
+
const [user, slotArg] = splitSlot(args);
|
|
45
|
+
const slot = slotArg ?? S('useFormValidation');
|
|
46
|
+
const props = user[0] as FormValidationProps<any>;
|
|
47
|
+
const state = user[1] as FormValidationState;
|
|
48
|
+
const ref = user[2] as RefObject<ValidatableElement | null> | undefined;
|
|
49
|
+
|
|
50
|
+
let { validationBehavior, focus } = props;
|
|
51
|
+
|
|
52
|
+
// This is a useLayoutEffect so that it runs before the useEffect in useFormValidationState, which commits the validation change.
|
|
53
|
+
useLayoutEffect(
|
|
54
|
+
() => {
|
|
55
|
+
if (
|
|
56
|
+
validationBehavior === 'native' &&
|
|
57
|
+
ref?.current &&
|
|
58
|
+
'setCustomValidity' in ref.current &&
|
|
59
|
+
!ref.current.disabled
|
|
60
|
+
) {
|
|
61
|
+
let errorMessage = state.realtimeValidation.isInvalid
|
|
62
|
+
? state.realtimeValidation.validationErrors.join(' ') || 'Invalid value.'
|
|
63
|
+
: '';
|
|
64
|
+
ref.current.setCustomValidity(errorMessage);
|
|
65
|
+
|
|
66
|
+
// Prevent default tooltip for validation message.
|
|
67
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=605277
|
|
68
|
+
if (!ref.current.hasAttribute('title')) {
|
|
69
|
+
ref.current.title = '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!state.realtimeValidation.isInvalid) {
|
|
73
|
+
state.updateValidation(getNativeValidity(ref.current));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
null,
|
|
78
|
+
subSlot(slot, 'native'),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
let isIgnoredReset = useRef(false, subSlot(slot, 'ignoredReset'));
|
|
82
|
+
let onReset = useEffectEvent(
|
|
83
|
+
() => {
|
|
84
|
+
if (!isIgnoredReset.current) {
|
|
85
|
+
state.resetValidation();
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
subSlot(slot, 'reset'),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
let onInvalid = useEffectEvent(
|
|
92
|
+
(e: Event) => {
|
|
93
|
+
// Only commit validation if we are not already displaying one.
|
|
94
|
+
// This avoids clearing server errors that the user didn't actually fix.
|
|
95
|
+
if (!state.displayValidation.isInvalid) {
|
|
96
|
+
state.commitValidation();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Auto focus the first invalid input in a form, unless the error already had its default prevented.
|
|
100
|
+
let form = ref?.current?.form;
|
|
101
|
+
if (!e.defaultPrevented && ref && form && getFirstInvalidInput(form) === ref.current) {
|
|
102
|
+
if (focus) {
|
|
103
|
+
focus();
|
|
104
|
+
} else {
|
|
105
|
+
ref.current?.focus();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Always show focus ring.
|
|
109
|
+
setInteractionModality('keyboard');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Prevent default browser error UI from appearing.
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
},
|
|
115
|
+
subSlot(slot, 'invalid'),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
let onChange = useEffectEvent(
|
|
119
|
+
() => {
|
|
120
|
+
state.commitValidation();
|
|
121
|
+
},
|
|
122
|
+
subSlot(slot, 'change'),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
useEffect(
|
|
126
|
+
() => {
|
|
127
|
+
let input = ref?.current;
|
|
128
|
+
if (!input) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let form = input.form;
|
|
133
|
+
|
|
134
|
+
let reset = form?.reset;
|
|
135
|
+
if (form) {
|
|
136
|
+
// Try to detect React's automatic form reset behavior so we don't clear
|
|
137
|
+
// validation errors that are returned by server actions.
|
|
138
|
+
// To do this, we ignore programmatic form resets that occur outside a user event.
|
|
139
|
+
// This is best-effort. There may be false positives, e.g. setTimeout.
|
|
140
|
+
form.reset = () => {
|
|
141
|
+
// React uses MessageChannel for scheduling, so ignore 'message' events.
|
|
142
|
+
isIgnoredReset.current =
|
|
143
|
+
!window.event ||
|
|
144
|
+
(window.event.type === 'message' &&
|
|
145
|
+
getEventTarget(window.event) instanceof MessagePort);
|
|
146
|
+
reset?.call(form);
|
|
147
|
+
isIgnoredReset.current = false;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
input.addEventListener('invalid', onInvalid);
|
|
152
|
+
input.addEventListener('change', onChange);
|
|
153
|
+
form?.addEventListener('reset', onReset);
|
|
154
|
+
return () => {
|
|
155
|
+
input!.removeEventListener('invalid', onInvalid);
|
|
156
|
+
input!.removeEventListener('change', onChange);
|
|
157
|
+
form?.removeEventListener('reset', onReset);
|
|
158
|
+
if (form) {
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
form.reset = reset;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
[ref, validationBehavior],
|
|
165
|
+
subSlot(slot, 'listen'),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getValidity(input: ValidatableElement) {
|
|
170
|
+
// The native ValidityState object is live, meaning each property is a getter that returns the current state.
|
|
171
|
+
// We need to create a snapshot of the validity state at the time this function is called to avoid unpredictable React renders.
|
|
172
|
+
let validity = input.validity;
|
|
173
|
+
return {
|
|
174
|
+
badInput: validity.badInput,
|
|
175
|
+
customError: validity.customError,
|
|
176
|
+
patternMismatch: validity.patternMismatch,
|
|
177
|
+
rangeOverflow: validity.rangeOverflow,
|
|
178
|
+
rangeUnderflow: validity.rangeUnderflow,
|
|
179
|
+
stepMismatch: validity.stepMismatch,
|
|
180
|
+
tooLong: validity.tooLong,
|
|
181
|
+
tooShort: validity.tooShort,
|
|
182
|
+
typeMismatch: validity.typeMismatch,
|
|
183
|
+
valueMissing: validity.valueMissing,
|
|
184
|
+
valid: validity.valid,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getNativeValidity(input: ValidatableElement): ValidationResult {
|
|
189
|
+
return {
|
|
190
|
+
isInvalid: !input.validity.valid,
|
|
191
|
+
validationDetails: getValidity(input),
|
|
192
|
+
validationErrors: input.validationMessage ? [input.validationMessage] : [],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getFirstInvalidInput(form: HTMLFormElement): ValidatableElement | null {
|
|
197
|
+
for (let i = 0; i < form.elements.length; i++) {
|
|
198
|
+
let element = form.elements[i] as ValidatableElement;
|
|
199
|
+
if (element.validity?.valid === false) {
|
|
200
|
+
return element;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/grid/useGridSelectionAnnouncement.ts).
|
|
2
|
+
// GRID SUBSET: only the grid-area hooks that the gridlist area actually imports are
|
|
3
|
+
// ported (this file, useHighlightSelectionDescription, useGridSelectionCheckbox) — the
|
|
4
|
+
// rest of the grid area (useGrid/useGridRow/useGridCell/GridKeyboardDelegate/…) is out
|
|
5
|
+
// of scope until a table/grid port needs it.
|
|
6
|
+
// octane adaptations:
|
|
7
|
+
// - `SelectionManager` comes from the ported stately selection engine.
|
|
8
|
+
// - The Parcel glob intl import becomes the generated src/intl/grid index (verbatim
|
|
9
|
+
// dictionaries).
|
|
10
|
+
// - Public-hook slot threading (splitSlot/subSlot); explicit dependency arrays are kept
|
|
11
|
+
// verbatim.
|
|
12
|
+
import { announce } from '../live-announcer/LiveAnnouncer';
|
|
13
|
+
import type { Collection, Key, Node, Selection } from '@react-types/shared';
|
|
14
|
+
import intlMessages from '../intl/grid';
|
|
15
|
+
import type { SelectionManager } from '../stately/selection/SelectionManager';
|
|
16
|
+
import { useCallback, useRef } from 'octane';
|
|
17
|
+
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
18
|
+
import { useUpdateEffect } from '../utils/useUpdateEffect';
|
|
19
|
+
|
|
20
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
21
|
+
|
|
22
|
+
export interface GridSelectionAnnouncementProps {
|
|
23
|
+
/**
|
|
24
|
+
* A function that returns the text that should be announced by assistive technology when a row is
|
|
25
|
+
* added or removed from selection.
|
|
26
|
+
*
|
|
27
|
+
* @default (key) => state.collection.getItem(key)?.textValue
|
|
28
|
+
*/
|
|
29
|
+
getRowText?: (key: Key) => string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface GridSelectionState<T> {
|
|
33
|
+
/** A collection of items in the grid. */
|
|
34
|
+
collection: Collection<Node<T>>;
|
|
35
|
+
/** A set of items that are disabled. */
|
|
36
|
+
disabledKeys: Set<Key>;
|
|
37
|
+
/** A selection manager to read and update multiple selection state. */
|
|
38
|
+
selectionManager: SelectionManager;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useGridSelectionAnnouncement<T>(
|
|
42
|
+
props: GridSelectionAnnouncementProps,
|
|
43
|
+
state: GridSelectionState<T>,
|
|
44
|
+
): void;
|
|
45
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
46
|
+
export function useGridSelectionAnnouncement<T>(
|
|
47
|
+
props: GridSelectionAnnouncementProps,
|
|
48
|
+
state: GridSelectionState<T>,
|
|
49
|
+
slot: symbol | undefined,
|
|
50
|
+
): void;
|
|
51
|
+
export function useGridSelectionAnnouncement(...args: any[]): void {
|
|
52
|
+
const [user, slotArg] = splitSlot(args);
|
|
53
|
+
const slot = slotArg ?? S('useGridSelectionAnnouncement');
|
|
54
|
+
const props = user[0] as GridSelectionAnnouncementProps;
|
|
55
|
+
const state = user[1] as GridSelectionState<any>;
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
getRowText = (key) =>
|
|
59
|
+
(state.collection as any).getTextValue?.(key) ?? state.collection.getItem(key)?.textValue,
|
|
60
|
+
} = props;
|
|
61
|
+
let stringFormatter = useLocalizedStringFormatter(
|
|
62
|
+
intlMessages,
|
|
63
|
+
'@react-aria/grid',
|
|
64
|
+
subSlot(slot, 'strings'),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Many screen readers do not announce when items in a grid are selected/deselected.
|
|
68
|
+
// We do this using an ARIA live region.
|
|
69
|
+
let selection = state.selectionManager.rawSelection;
|
|
70
|
+
let lastSelection = useRef(selection, subSlot(slot, 'lastSelection'));
|
|
71
|
+
let announceSelectionChange = useCallback(
|
|
72
|
+
() => {
|
|
73
|
+
if (!state.selectionManager.isFocused || selection === lastSelection.current) {
|
|
74
|
+
lastSelection.current = selection;
|
|
75
|
+
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let addedKeys = diffSelection(selection, lastSelection.current);
|
|
80
|
+
let removedKeys = diffSelection(lastSelection.current, selection);
|
|
81
|
+
|
|
82
|
+
// If adding or removing a single row from the selection, announce the name of that item.
|
|
83
|
+
let isReplace = state.selectionManager.selectionBehavior === 'replace';
|
|
84
|
+
let messages: string[] = [];
|
|
85
|
+
|
|
86
|
+
if (state.selectionManager.selectedKeys.size === 1 && isReplace) {
|
|
87
|
+
let firstKey = state.selectionManager.selectedKeys.keys().next().value;
|
|
88
|
+
if (firstKey != null && state.collection.getItem(firstKey)) {
|
|
89
|
+
let currentSelectionText = getRowText(firstKey);
|
|
90
|
+
if (currentSelectionText) {
|
|
91
|
+
messages.push(stringFormatter.format('selectedItem', { item: currentSelectionText }));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (addedKeys.size === 1 && removedKeys.size === 0) {
|
|
95
|
+
let firstKey = addedKeys.keys().next().value;
|
|
96
|
+
if (firstKey != null) {
|
|
97
|
+
let addedText = getRowText(firstKey);
|
|
98
|
+
if (addedText) {
|
|
99
|
+
messages.push(stringFormatter.format('selectedItem', { item: addedText }));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else if (removedKeys.size === 1 && addedKeys.size === 0) {
|
|
103
|
+
let firstKey = removedKeys.keys().next().value;
|
|
104
|
+
if (firstKey != null && state.collection.getItem(firstKey)) {
|
|
105
|
+
let removedText = getRowText(firstKey);
|
|
106
|
+
if (removedText) {
|
|
107
|
+
messages.push(stringFormatter.format('deselectedItem', { item: removedText }));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Announce how many items are selected, except when selecting the first item.
|
|
113
|
+
if (state.selectionManager.selectionMode === 'multiple') {
|
|
114
|
+
if (
|
|
115
|
+
messages.length === 0 ||
|
|
116
|
+
selection === 'all' ||
|
|
117
|
+
selection.size > 1 ||
|
|
118
|
+
lastSelection.current === 'all' ||
|
|
119
|
+
(lastSelection.current as any)?.size > 1
|
|
120
|
+
) {
|
|
121
|
+
messages.push(
|
|
122
|
+
selection === 'all'
|
|
123
|
+
? stringFormatter.format('selectedAll')
|
|
124
|
+
: stringFormatter.format('selectedCount', { count: selection.size }),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (messages.length > 0) {
|
|
130
|
+
announce(messages.join(' '));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
lastSelection.current = selection;
|
|
134
|
+
},
|
|
135
|
+
[
|
|
136
|
+
selection,
|
|
137
|
+
state.selectionManager.selectedKeys,
|
|
138
|
+
state.selectionManager.isFocused,
|
|
139
|
+
state.selectionManager.selectionBehavior,
|
|
140
|
+
state.selectionManager.selectionMode,
|
|
141
|
+
state.collection,
|
|
142
|
+
getRowText,
|
|
143
|
+
stringFormatter,
|
|
144
|
+
],
|
|
145
|
+
subSlot(slot, 'announceChange'),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// useUpdateEffect will handle using useEffectEvent, no need to stabilize anything on this end
|
|
149
|
+
useUpdateEffect(
|
|
150
|
+
() => {
|
|
151
|
+
if (state.selectionManager.isFocused) {
|
|
152
|
+
announceSelectionChange();
|
|
153
|
+
} else {
|
|
154
|
+
// Wait a frame in case the collection is about to become focused (e.g. on mouse down).
|
|
155
|
+
let raf = requestAnimationFrame(announceSelectionChange);
|
|
156
|
+
return () => cancelAnimationFrame(raf);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
[selection, state.selectionManager.isFocused],
|
|
160
|
+
subSlot(slot, 'updateFx'),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function diffSelection(a: Selection, b: Selection): Set<Key> {
|
|
165
|
+
let res = new Set<Key>();
|
|
166
|
+
if (a === 'all' || b === 'all') {
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (let key of a.keys()) {
|
|
171
|
+
if (!b.has(key)) {
|
|
172
|
+
res.add(key);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return res;
|
|
177
|
+
}
|