@astryxdesign/core 0.1.2 → 0.1.3
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/CHANGELOG.md +165 -0
- package/README.md +12 -12
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +0 -1
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +82 -43
- package/dist/Calendar/hooks/index.d.ts +0 -2
- package/dist/Calendar/hooks/index.d.ts.map +1 -1
- package/dist/Calendar/hooks/index.js +1 -2
- package/dist/Calendar/index.d.ts +3 -3
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/Calendar/index.js +1 -1
- package/dist/Calendar/styles.d.ts +3 -9
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +3 -11
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +17 -3
- package/dist/Center/Center.d.ts +11 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +9 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +0 -1
- package/dist/Chat/ChatMessageList.d.ts +14 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +2 -0
- package/dist/Chat/useTriggerMenu.d.ts +8 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +51 -9
- package/dist/CheckboxList/CheckboxList.d.ts +13 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +39 -9
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +0 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +9 -5
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +51 -37
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -5
- package/dist/ContextMenu/ContextMenu.d.ts +4 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +93 -11
- package/dist/DateInput/DateInput.d.ts +23 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +167 -99
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +42 -9
- package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +78 -15
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +42 -14
- package/dist/Field/Field.d.ts +19 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -0
- package/dist/Field/FieldLabel.d.ts +21 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +41 -26
- package/dist/FileInput/FileInput.d.ts +22 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +56 -10
- package/dist/Grid/Grid.d.ts +11 -1
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +32 -6
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +16 -6
- package/dist/InputGroup/InputGroupContext.d.ts +4 -0
- package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
- package/dist/InputGroup/InputGroupContext.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +3 -3
- package/dist/InputGroup/index.d.ts +1 -0
- package/dist/InputGroup/index.d.ts.map +1 -1
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +33 -1
- package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +8 -4
- package/dist/Layer/useLayer.d.ts +10 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +64 -9
- package/dist/Layout/Layout.d.ts +4 -11
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +10 -11
- package/dist/Layout/LayoutFooter.d.ts +2 -2
- package/dist/Layout/LayoutFooter.d.ts.map +1 -1
- package/dist/Layout/LayoutHeader.d.ts +2 -2
- package/dist/Layout/LayoutHeader.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.d.ts +2 -2
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.d.ts +51 -0
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +63 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -2
- package/dist/Link/Link.d.ts +16 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +18 -7
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MoreMenu/MoreMenu.d.ts +1 -7
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +0 -2
- package/dist/MultiSelector/MultiSelector.d.ts +27 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +141 -42
- package/dist/MultiSelector/hooks.d.ts +12 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +14 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +34 -3
- package/dist/NumberInput/NumberInput.d.ts +23 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +62 -12
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +15 -1
- package/dist/Popover/Popover.d.ts +18 -2
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +5 -2
- package/dist/Popover/usePopover.d.ts +38 -3
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +24 -9
- package/dist/PowerSearch/PowerSearch.d.ts +11 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +6 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +3 -5
- package/dist/RadioList/RadioList.d.ts +20 -2
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +105 -9
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +78 -44
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
- package/dist/Selector/Selector.d.ts +24 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +122 -43
- package/dist/Selector/hooks.d.ts +8 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +14 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +2 -0
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +1 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +8 -2
- package/dist/Slider/Slider.d.ts +21 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +36 -9
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +1 -1
- package/dist/Stack/Stack.d.ts +38 -23
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +25 -1
- package/dist/Stack/StackItem.d.ts +12 -23
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +8 -1
- package/dist/Switch/Switch.d.ts +23 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +41 -11
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +6 -0
- package/dist/TabList/TabList.d.ts +16 -4
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +87 -10
- package/dist/TabList/TabListContext.d.ts +5 -0
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +5 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +11 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +10 -1
- package/dist/Table/TableCell.d.ts +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +10 -10
- package/dist/Table/TableHeaderCell.d.ts +8 -1
- package/dist/Table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Table/TableHeaderCell.js +16 -16
- package/dist/Table/index.d.ts +2 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +8 -3
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
- package/dist/Table/tableContextMenu.d.ts +33 -0
- package/dist/Table/tableContextMenu.d.ts.map +1 -0
- package/dist/Table/tableContextMenu.js +133 -0
- package/dist/Table/types.d.ts +66 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +22 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +41 -9
- package/dist/TextInput/TextInput.d.ts +23 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +55 -10
- package/dist/TimeInput/TimeInput.d.ts +23 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +147 -76
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +14 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +131 -3
- package/dist/Tokenizer/Tokenizer.d.ts +12 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +26 -4
- package/dist/Toolbar/Toolbar.d.ts +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +51 -7
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +56 -9
- package/dist/TopNav/TopNavHeading.d.ts +7 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +8 -0
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +72 -2
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +51 -12
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
- package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
- package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +69 -6
- package/dist/Typeahead/Typeahead.d.ts +26 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +65 -29
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
- package/dist/VisuallyHidden/index.d.ts +11 -0
- package/dist/VisuallyHidden/index.d.ts.map +1 -0
- package/dist/VisuallyHidden/index.js +13 -0
- package/dist/astryx.css +18 -0
- package/dist/astryx.umd.js +55 -55
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +22 -0
- package/dist/hooks/focusableSelector.d.ts.map +1 -0
- package/dist/hooks/focusableSelector.js +24 -0
- package/dist/hooks/index.d.ts +12 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +7 -2
- package/dist/hooks/useAnnounce.d.ts +40 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -0
- package/dist/hooks/useAnnounce.js +142 -0
- package/dist/hooks/useClickableContainer.d.ts +16 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +16 -4
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +4 -4
- package/dist/hooks/useFocusTrap.d.ts +18 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +87 -3
- package/dist/hooks/useGridFocus.d.ts +88 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +261 -62
- package/dist/hooks/useKeyboardHint.d.ts +82 -0
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHint.js +220 -0
- package/dist/hooks/useListFocus.d.ts +78 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +305 -52
- package/dist/hooks/useLongPress.d.ts +21 -0
- package/dist/hooks/useLongPress.d.ts.map +1 -0
- package/dist/hooks/useLongPress.js +89 -0
- package/dist/hooks/useTreeFocus.d.ts +157 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -0
- package/dist/hooks/useTreeFocus.js +337 -0
- package/dist/hooks/useTypeahead.d.ts +63 -0
- package/dist/hooks/useTypeahead.d.ts.map +1 -0
- package/dist/hooks/useTypeahead.js +117 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/inputAria.d.ts +29 -0
- package/dist/utils/inputAria.d.ts.map +1 -0
- package/dist/utils/inputAria.js +30 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -2
- package/docs.mjs +2 -2
- package/package.json +12 -8
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Button/Button.tsx +3 -16
- package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
- package/src/ButtonGroup/ButtonGroup.tsx +0 -1
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +168 -30
- package/src/Calendar/Calendar.tsx +121 -66
- package/src/Calendar/hooks/index.ts +0 -6
- package/src/Calendar/index.ts +1 -3
- package/src/Calendar/styles.ts +4 -9
- package/src/Carousel/Carousel.doc.mjs +7 -1
- package/src/Carousel/Carousel.test.tsx +20 -0
- package/src/Carousel/Carousel.tsx +16 -1
- package/src/Center/Center.doc.mjs +12 -2
- package/src/Center/Center.tsx +28 -3
- package/src/Chat/ChatComposerInput.test.tsx +26 -7
- package/src/Chat/ChatComposerInput.tsx +0 -1
- package/src/Chat/ChatDictationButton.doc.mjs +1 -1
- package/src/Chat/ChatMessageList.doc.mjs +8 -0
- package/src/Chat/ChatMessageList.test.tsx +18 -0
- package/src/Chat/ChatMessageList.tsx +16 -0
- package/src/Chat/useTriggerMenu.tsx +22 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
- package/src/CheckboxInput/CheckboxInput.tsx +78 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
- package/src/CheckboxList/CheckboxList.test.tsx +192 -26
- package/src/CheckboxList/CheckboxList.tsx +56 -3
- package/src/CheckboxList/CheckboxListContext.tsx +7 -0
- package/src/CheckboxList/CheckboxListItem.tsx +0 -3
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +121 -0
- package/src/Citation/Citation.tsx +15 -3
- package/src/CodeBlock/CodeBlock.test.tsx +115 -0
- package/src/CodeBlock/CodeBlock.tsx +63 -33
- package/src/CommandPalette/CommandPalette.test.tsx +47 -1
- package/src/CommandPalette/CommandPalette.tsx +9 -21
- package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
- package/src/ContextMenu/ContextMenu.test.tsx +155 -1
- package/src/ContextMenu/ContextMenu.tsx +113 -16
- package/src/DateInput/DateInput.doc.mjs +286 -56
- package/src/DateInput/DateInput.test.tsx +248 -2
- package/src/DateInput/DateInput.tsx +196 -100
- package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
- package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
- package/src/DateRangeInput/DateRangeInput.tsx +63 -5
- package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
- package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
- package/src/DateTimeInput/DateTimeInput.tsx +125 -17
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/Dialog/Dialog.tsx +12 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
- package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
- package/src/DropdownMenu/DropdownMenu.tsx +52 -18
- package/src/Field/Field.doc.mjs +59 -18
- package/src/Field/Field.test.tsx +32 -0
- package/src/Field/Field.tsx +23 -5
- package/src/Field/FieldLabel.tsx +55 -17
- package/src/FileInput/FileInput.doc.mjs +17 -0
- package/src/FileInput/FileInput.test.tsx +283 -49
- package/src/FileInput/FileInput.tsx +76 -4
- package/src/Grid/Grid.doc.mjs +17 -4
- package/src/Grid/Grid.test.tsx +43 -30
- package/src/Grid/Grid.tsx +38 -3
- package/src/Icon/Icon.doc.mjs +3 -3
- package/src/InputGroup/InputGroup.doc.mjs +111 -23
- package/src/InputGroup/InputGroup.test.tsx +257 -60
- package/src/InputGroup/InputGroup.tsx +23 -5
- package/src/InputGroup/InputGroupContext.ts +9 -4
- package/src/InputGroup/groupStyles.ts +7 -1
- package/src/InputGroup/index.ts +1 -0
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +35 -3
- package/src/Layer/layerAnimations.stylex.ts +20 -4
- package/src/Layer/useLayer.doc.mjs +5 -0
- package/src/Layer/useLayer.test.tsx +111 -0
- package/src/Layer/useLayer.tsx +90 -9
- package/src/Layout/Layout.tsx +8 -16
- package/src/Layout/LayoutFooter.doc.mjs +4 -4
- package/src/Layout/LayoutFooter.tsx +3 -3
- package/src/Layout/LayoutHeader.doc.mjs +4 -4
- package/src/Layout/LayoutHeader.tsx +3 -3
- package/src/Layout/LayoutPanel.tsx +3 -3
- package/src/Layout/padding.stylex.ts +52 -0
- package/src/Lightbox/Lightbox.doc.mjs +9 -0
- package/src/Lightbox/Lightbox.test.tsx +53 -5
- package/src/Lightbox/Lightbox.tsx +9 -4
- package/src/Link/Link.doc.mjs +14 -0
- package/src/Link/Link.test.tsx +50 -0
- package/src/Link/Link.tsx +39 -4
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +7 -0
- package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
- package/src/MultiSelector/MultiSelector.test.tsx +375 -8
- package/src/MultiSelector/MultiSelector.tsx +194 -48
- package/src/MultiSelector/hooks.ts +27 -0
- package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
- package/src/NavMenu/NavHeadingMenu.tsx +50 -3
- package/src/NumberInput/NumberInput.doc.mjs +17 -0
- package/src/NumberInput/NumberInput.test.tsx +242 -40
- package/src/NumberInput/NumberInput.tsx +89 -11
- package/src/Pagination/Pagination.doc.mjs +3 -3
- package/src/Pagination/Pagination.test.tsx +117 -5
- package/src/Pagination/Pagination.tsx +21 -1
- package/src/Popover/Popover.doc.mjs +26 -0
- package/src/Popover/Popover.test.tsx +121 -0
- package/src/Popover/Popover.tsx +23 -2
- package/src/Popover/usePopover.doc.mjs +9 -3
- package/src/Popover/usePopover.tsx +70 -12
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +97 -1
- package/src/PowerSearch/PowerSearch.tsx +16 -3
- package/src/ProgressBar/ProgressBar.test.tsx +34 -26
- package/src/ProgressBar/ProgressBar.tsx +3 -4
- package/src/RadioList/RadioList.doc.mjs +9 -0
- package/src/RadioList/RadioList.test.tsx +252 -16
- package/src/RadioList/RadioList.tsx +166 -10
- package/src/RadioList/RadioListItem.tsx +14 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
- package/src/SegmentedControl/SegmentedControl.tsx +102 -54
- package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.doc.mjs +26 -0
- package/src/Selector/Selector.test.tsx +387 -15
- package/src/Selector/Selector.tsx +175 -67
- package/src/Selector/hooks.ts +23 -0
- package/src/SideNav/SideNav.test.tsx +48 -0
- package/src/SideNav/SideNavHeading.tsx +8 -2
- package/src/SideNav/SideNavItem.tsx +2 -4
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Slider/Slider.doc.mjs +15 -0
- package/src/Slider/Slider.test.tsx +155 -2
- package/src/Slider/Slider.tsx +53 -5
- package/src/Spinner/Spinner.tsx +7 -1
- package/src/Stack/Stack.doc.mjs +77 -0
- package/src/Stack/Stack.test.tsx +55 -5
- package/src/Stack/Stack.tsx +73 -24
- package/src/Stack/StackItem.test.tsx +23 -0
- package/src/Stack/StackItem.tsx +20 -23
- package/src/Switch/Switch.doc.mjs +15 -0
- package/src/Switch/Switch.test.tsx +162 -27
- package/src/Switch/Switch.tsx +61 -21
- package/src/TabList/Tab.tsx +6 -0
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/TabList/TabList.test.tsx +251 -0
- package/src/TabList/TabList.tsx +103 -12
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +6 -3
- package/src/Table/BaseTable.tsx +21 -17
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.test.tsx +10 -0
- package/src/Table/Table.tsx +8 -0
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/index.ts +3 -2
- package/src/Table/plugins/pagination/paginateData.ts +10 -3
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
- package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
- package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +142 -0
- package/src/Table/types.ts +74 -0
- package/src/Table/useTableColumnResize.doc.mjs +3 -3
- package/src/Table/useTableStickyColumns.doc.mjs +2 -2
- package/src/Text/Text.doc.mjs +18 -18
- package/src/TextArea/TextArea.doc.mjs +17 -0
- package/src/TextArea/TextArea.test.tsx +167 -35
- package/src/TextArea/TextArea.tsx +62 -15
- package/src/TextInput/TextInput.doc.mjs +17 -0
- package/src/TextInput/TextInput.test.tsx +168 -34
- package/src/TextInput/TextInput.tsx +77 -9
- package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
- package/src/TimeInput/TimeInput.doc.mjs +340 -60
- package/src/TimeInput/TimeInput.test.tsx +255 -7
- package/src/TimeInput/TimeInput.tsx +176 -70
- package/src/Timestamp/Timestamp.doc.mjs +19 -19
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +214 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
- package/src/Tokenizer/Tokenizer.test.tsx +105 -1
- package/src/Tokenizer/Tokenizer.tsx +37 -1
- package/src/Toolbar/Toolbar.test.tsx +109 -6
- package/src/Toolbar/Toolbar.tsx +59 -5
- package/src/Tooltip/Tooltip.test.tsx +68 -0
- package/src/Tooltip/useTooltip.tsx +55 -8
- package/src/TopNav/TopNav.test.tsx +50 -0
- package/src/TopNav/TopNavHeading.tsx +17 -1
- package/src/TreeList/TreeList.test.tsx +295 -6
- package/src/TreeList/TreeList.tsx +73 -1
- package/src/TreeList/TreeListItem.tsx +51 -5
- package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
- package/src/Typeahead/BaseTypeahead.tsx +93 -9
- package/src/Typeahead/Typeahead.doc.mjs +141 -30
- package/src/Typeahead/Typeahead.test.tsx +331 -1
- package/src/Typeahead/Typeahead.tsx +105 -33
- package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
- package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
- package/src/VisuallyHidden/index.ts +15 -0
- package/src/docs-script.test.mjs +50 -0
- package/src/hooks/focusableSelector.ts +25 -0
- package/src/hooks/index.ts +28 -2
- package/src/hooks/useAnnounce.test.tsx +101 -0
- package/src/hooks/useAnnounce.ts +168 -0
- package/src/hooks/useClickableContainer.ts +16 -4
- package/src/hooks/useEntryAnimation.ts +16 -4
- package/src/hooks/useFocusTrap.test.tsx +132 -0
- package/src/hooks/useFocusTrap.ts +98 -4
- package/src/hooks/useGridFocus.doc.mjs +40 -2
- package/src/hooks/useGridFocus.test.tsx +132 -0
- package/src/hooks/useGridFocus.ts +343 -67
- package/src/hooks/useKeyboardHint.doc.mjs +101 -0
- package/src/hooks/useKeyboardHint.test.tsx +70 -0
- package/src/hooks/useKeyboardHint.tsx +332 -0
- package/src/hooks/useListFocus.doc.mjs +50 -10
- package/src/hooks/useListFocus.test.tsx +375 -0
- package/src/hooks/useListFocus.ts +395 -57
- package/src/hooks/useLongPress.test.tsx +134 -0
- package/src/hooks/useLongPress.ts +119 -0
- package/src/hooks/useTreeFocus.doc.mjs +140 -0
- package/src/hooks/useTreeFocus.test.tsx +214 -0
- package/src/hooks/useTreeFocus.ts +559 -0
- package/src/hooks/useTypeahead.test.tsx +109 -0
- package/src/hooks/useTypeahead.ts +183 -0
- package/src/index.ts +1 -0
- package/src/theme/derivedVarRegistry.test.ts +10 -5
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/inputAria.test.ts +39 -0
- package/src/utils/inputAria.ts +58 -0
- package/src/utils/timeParser.test.ts +10 -0
- package/src/utils/timeParser.ts +5 -2
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
- package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
- package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useGridFocus.ts
|
|
7
|
-
* @input Uses React useCallback, useRef
|
|
7
|
+
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
|
|
8
8
|
* @output Exports useGridFocus hook for grid keyboard navigation
|
|
9
9
|
* @position Core hook; used by Calendar for date grid navigation
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update:
|
|
12
12
|
* - /packages/core/src/hooks/index.ts
|
|
13
|
-
* - /packages/core/src/hooks/useGridFocus.test.
|
|
13
|
+
* - /packages/core/src/hooks/useGridFocus.test.tsx
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import {useCallback, useRef} from 'react';
|
|
17
|
+
import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Configuration for grid focus behavior
|
|
@@ -26,11 +27,38 @@ export interface UseGridFocusOptions {
|
|
|
26
27
|
columns: number;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Selector for
|
|
30
|
+
* Selector for cells within the grid. This should match ALL grid cell
|
|
31
|
+
* positions in DOM order (including disabled/empty ones) so the hook can
|
|
32
|
+
* preserve the true grid geometry when computing rows and columns.
|
|
33
|
+
*
|
|
34
|
+
* Use {@link UseGridFocusOptions.isCellFocusable} to distinguish cells that
|
|
35
|
+
* can receive focus from those that cannot (disabled or empty). Navigation
|
|
36
|
+
* moves to a target row/column and, if that cell is not focusable, continues
|
|
37
|
+
* in the same direction to the next focusable cell — the geometry is never
|
|
38
|
+
* collapsed to only-focusable cells.
|
|
39
|
+
*
|
|
30
40
|
* @default 'button:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
31
41
|
*/
|
|
32
42
|
cellSelector?: string;
|
|
33
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Predicate determining whether a cell matched by {@link cellSelector} can
|
|
46
|
+
* receive focus. When omitted, every matched cell is considered focusable
|
|
47
|
+
* (backwards-compatible behavior).
|
|
48
|
+
*
|
|
49
|
+
* @param cell A cell element matched by `cellSelector`.
|
|
50
|
+
*/
|
|
51
|
+
isCellFocusable?: (cell: HTMLElement) => boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the element to focus for a given cell. Useful when the cell is a
|
|
55
|
+
* wrapper element (e.g. a `role="gridcell"` div) whose focusable content is a
|
|
56
|
+
* descendant (e.g. a `<button>`). When omitted, the cell itself is focused.
|
|
57
|
+
*
|
|
58
|
+
* @param cell A cell element matched by `cellSelector`.
|
|
59
|
+
*/
|
|
60
|
+
getFocusTarget?: (cell: HTMLElement) => HTMLElement | null;
|
|
61
|
+
|
|
34
62
|
/**
|
|
35
63
|
* Callback when navigation would go before the first cell.
|
|
36
64
|
* Useful for navigating to previous month in calendars.
|
|
@@ -56,6 +84,34 @@ export interface UseGridFocusOptions {
|
|
|
56
84
|
* Callback for Page Down key (e.g., next month).
|
|
57
85
|
*/
|
|
58
86
|
onPageDown?: () => void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether the grid is in a right-to-left context. When true, ArrowLeft and
|
|
90
|
+
* ArrowRight are swapped so horizontal navigation follows visual direction.
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
isRtl?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Roving-tabindex ownership. When true, the hook manages a single tab stop
|
|
97
|
+
* across the grid: exactly one focusable cell carries `tabindex="0"` and the
|
|
98
|
+
* rest `tabindex="-1"`. The tab stop is stamped on mount and repaired
|
|
99
|
+
* whenever cells mount/unmount or toggle focusable, and moves with arrow
|
|
100
|
+
* navigation. Attach the returned {@link UseGridFocusReturn.handleFocus} to
|
|
101
|
+
* the container's `onFocus` to keep the stop in sync after clicks or
|
|
102
|
+
* programmatic focus.
|
|
103
|
+
*
|
|
104
|
+
* The tab stop is stamped on the resolved focus target (see
|
|
105
|
+
* {@link UseGridFocusOptions.getFocusTarget}), not the cell wrapper, so it
|
|
106
|
+
* works when the focusable element is a descendant of the cell. An existing
|
|
107
|
+
* `tabindex="0"` on a focus target is honored, letting the caller seed which
|
|
108
|
+
* cell is initially tabbable.
|
|
109
|
+
*
|
|
110
|
+
* When false (the default), the hook only *moves* focus (`.focus()`) and
|
|
111
|
+
* never touches `tabindex` — the caller owns tab-stop management.
|
|
112
|
+
* @default false
|
|
113
|
+
*/
|
|
114
|
+
hasRovingTabIndex?: boolean;
|
|
59
115
|
}
|
|
60
116
|
|
|
61
117
|
/**
|
|
@@ -72,6 +128,13 @@ export interface UseGridFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
72
128
|
*/
|
|
73
129
|
handleKeyDown: (e: React.KeyboardEvent) => void;
|
|
74
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Focus handler to attach to the container's `onFocus`. Keeps the roving tab
|
|
133
|
+
* stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it
|
|
134
|
+
* is always safe to attach.
|
|
135
|
+
*/
|
|
136
|
+
handleFocus: (e: React.FocusEvent) => void;
|
|
137
|
+
|
|
75
138
|
/**
|
|
76
139
|
* Focus a specific cell by index.
|
|
77
140
|
*/
|
|
@@ -99,6 +162,18 @@ export interface UseGridFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
99
162
|
* - Ctrl+End: Move to last cell in grid
|
|
100
163
|
* - Page Up/Down: Custom callbacks (e.g., month navigation)
|
|
101
164
|
*
|
|
165
|
+
* The hook enumerates ALL cells matched by `cellSelector` in DOM order and
|
|
166
|
+
* computes row/column over that full set, preserving the true grid geometry
|
|
167
|
+
* even when some cells are disabled or empty. When a move lands on a
|
|
168
|
+
* non-focusable cell (per `isCellFocusable`), it continues in the same
|
|
169
|
+
* direction to the next focusable cell.
|
|
170
|
+
*
|
|
171
|
+
* By default the hook only *moves* focus and leaves `tabindex` management to
|
|
172
|
+
* the caller. Opt into {@link UseGridFocusOptions.hasRovingTabIndex} for a hook
|
|
173
|
+
* that owns a single tab stop (roving tabindex) across the grid — stamping and
|
|
174
|
+
* repairing it as cells mount/unmount or toggle focusable, and moving it with
|
|
175
|
+
* arrow navigation.
|
|
176
|
+
*
|
|
102
177
|
* @example
|
|
103
178
|
* ```
|
|
104
179
|
* const {gridRef, handleKeyDown} = useGridFocus({
|
|
@@ -111,6 +186,24 @@ export interface UseGridFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
111
186
|
* {cells.map(cell => <button role="gridcell">{cell}</button>)}
|
|
112
187
|
* </div>
|
|
113
188
|
* ```
|
|
189
|
+
*
|
|
190
|
+
* Roving-tabindex grid (e.g. a date picker where the cell is a wrapper around
|
|
191
|
+
* a focusable button):
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```
|
|
195
|
+
* const {gridRef, handleKeyDown, handleFocus} = useGridFocus({
|
|
196
|
+
* columns: 7,
|
|
197
|
+
* cellSelector: '[role="gridcell"]',
|
|
198
|
+
* isCellFocusable: cell => cell.querySelector('button:not([disabled])') != null,
|
|
199
|
+
* getFocusTarget: cell => cell.querySelector('button'),
|
|
200
|
+
* hasRovingTabIndex: true,
|
|
201
|
+
* });
|
|
202
|
+
*
|
|
203
|
+
* <div ref={gridRef} role="grid" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
204
|
+
* {cells}
|
|
205
|
+
* </div>
|
|
206
|
+
* ```
|
|
114
207
|
*/
|
|
115
208
|
export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
116
209
|
options: UseGridFocusOptions,
|
|
@@ -118,16 +211,21 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
118
211
|
const {
|
|
119
212
|
columns,
|
|
120
213
|
cellSelector = 'button:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
214
|
+
isCellFocusable,
|
|
215
|
+
getFocusTarget,
|
|
121
216
|
onNavigateBefore,
|
|
122
217
|
onNavigateAfter,
|
|
123
218
|
onPageUp,
|
|
124
219
|
onPageDown,
|
|
220
|
+
isRtl = false,
|
|
221
|
+
hasRovingTabIndex = false,
|
|
125
222
|
} = options;
|
|
126
223
|
|
|
127
224
|
const gridRef = useRef<T>(null);
|
|
128
225
|
|
|
129
226
|
/**
|
|
130
|
-
* Get all
|
|
227
|
+
* Get all cells in the grid, in DOM order. This includes disabled and empty
|
|
228
|
+
* cells so the true grid geometry is preserved.
|
|
131
229
|
*/
|
|
132
230
|
const getCells = useCallback((): HTMLElement[] => {
|
|
133
231
|
if (!gridRef.current) {
|
|
@@ -139,41 +237,159 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
139
237
|
}, [cellSelector]);
|
|
140
238
|
|
|
141
239
|
/**
|
|
142
|
-
*
|
|
240
|
+
* Whether a cell can receive focus.
|
|
143
241
|
*/
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
242
|
+
const cellFocusable = useCallback(
|
|
243
|
+
(cell: HTMLElement | undefined): boolean => {
|
|
244
|
+
if (!cell) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return isCellFocusable ? isCellFocusable(cell) : true;
|
|
248
|
+
},
|
|
249
|
+
[isCellFocusable],
|
|
250
|
+
);
|
|
149
251
|
|
|
150
252
|
/**
|
|
151
|
-
*
|
|
152
|
-
* Returns true if focus was successful, false if navigation callback was triggered.
|
|
253
|
+
* Resolve the element that should actually receive focus for a cell.
|
|
153
254
|
*/
|
|
154
|
-
const
|
|
155
|
-
(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return;
|
|
255
|
+
const resolveFocusTarget = useCallback(
|
|
256
|
+
(cell: HTMLElement | undefined): HTMLElement | null => {
|
|
257
|
+
if (!cell) {
|
|
258
|
+
return null;
|
|
159
259
|
}
|
|
260
|
+
return getFocusTarget ? getFocusTarget(cell) : cell;
|
|
261
|
+
},
|
|
262
|
+
[getFocusTarget],
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// --- Roving tabindex ownership (opt-in via `hasRovingTabIndex`) -------------
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Set `tabindex` on an element, but only when it differs (avoids redundant
|
|
269
|
+
* DOM writes). Uses setAttribute so the value reflects even for elements
|
|
270
|
+
* (like `<button>`) whose default tabIndex is already 0.
|
|
271
|
+
*/
|
|
272
|
+
const setTabIndex = useCallback((el: HTMLElement, value: 0 | -1) => {
|
|
273
|
+
if (el.getAttribute('tabindex') !== String(value)) {
|
|
274
|
+
el.setAttribute('tabindex', String(value));
|
|
275
|
+
}
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The resolved focus targets of all focusable cells, in DOM order. These are
|
|
280
|
+
* the elements that carry the roving tab stop (a descendant of each cell when
|
|
281
|
+
* `getFocusTarget` is provided, otherwise the cell itself).
|
|
282
|
+
*/
|
|
283
|
+
const getFocusTargets = useCallback((): HTMLElement[] => {
|
|
284
|
+
return getCells()
|
|
285
|
+
.filter(cell => cellFocusable(cell))
|
|
286
|
+
.map(cell => resolveFocusTarget(cell))
|
|
287
|
+
.filter((el): el is HTMLElement => el !== null);
|
|
288
|
+
}, [getCells, cellFocusable, resolveFocusTarget]);
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Stamp the roving tab stop: exactly one focus target is tabbable (0), the
|
|
292
|
+
* rest are -1. Prefer keeping the currently-tabbable target if it is still
|
|
293
|
+
* focusable; otherwise promote the first focusable one (tab-stop repair).
|
|
294
|
+
*/
|
|
295
|
+
const syncTabStops = useCallback(() => {
|
|
296
|
+
const targets = getFocusTargets();
|
|
297
|
+
if (targets.length === 0) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const current = targets.find(el => el.getAttribute('tabindex') === '0');
|
|
301
|
+
const tabbable = current ?? targets[0];
|
|
302
|
+
for (const el of targets) {
|
|
303
|
+
setTabIndex(el, el === tabbable ? 0 : -1);
|
|
304
|
+
}
|
|
305
|
+
}, [getFocusTargets, setTabIndex]);
|
|
306
|
+
|
|
307
|
+
// Keep the tab stop valid across renders (cells added/removed, focusability
|
|
308
|
+
// toggled). Runs after every commit but only when roving tabindex is on.
|
|
309
|
+
useIsomorphicLayoutEffect(() => {
|
|
310
|
+
if (hasRovingTabIndex) {
|
|
311
|
+
syncTabStops();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
160
314
|
|
|
161
|
-
|
|
162
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Move the roving tab stop so `target` becomes the sole tabbable focus
|
|
317
|
+
* target, then focus it. No-op on tab stops unless roving tabindex is on.
|
|
318
|
+
*/
|
|
319
|
+
const focusTarget = useCallback(
|
|
320
|
+
(target: HTMLElement | null) => {
|
|
321
|
+
if (!target) {
|
|
163
322
|
return;
|
|
164
323
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
324
|
+
if (hasRovingTabIndex) {
|
|
325
|
+
for (const el of getFocusTargets()) {
|
|
326
|
+
setTabIndex(el, el === target ? 0 : -1);
|
|
327
|
+
}
|
|
168
328
|
}
|
|
329
|
+
target.focus();
|
|
330
|
+
},
|
|
331
|
+
[hasRovingTabIndex, getFocusTargets, setTabIndex],
|
|
332
|
+
);
|
|
169
333
|
|
|
170
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Focus a cell by resolving and focusing its target, moving the roving tab
|
|
336
|
+
* stop when enabled.
|
|
337
|
+
*/
|
|
338
|
+
const focusCellWithStop = useCallback(
|
|
339
|
+
(cell: HTMLElement | undefined): boolean => {
|
|
340
|
+
const target = resolveFocusTarget(cell);
|
|
341
|
+
if (!target) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
focusTarget(target);
|
|
345
|
+
return true;
|
|
171
346
|
},
|
|
172
|
-
[
|
|
347
|
+
[resolveFocusTarget, focusTarget],
|
|
173
348
|
);
|
|
174
349
|
|
|
175
350
|
/**
|
|
176
|
-
*
|
|
351
|
+
* Keep the roving stop pointing at whatever ended up focused (e.g. a click
|
|
352
|
+
* or programmatic focus) so the next Tab behaves correctly. No-op unless
|
|
353
|
+
* roving tabindex is enabled.
|
|
354
|
+
*/
|
|
355
|
+
const handleFocus = useCallback(() => {
|
|
356
|
+
if (hasRovingTabIndex) {
|
|
357
|
+
syncTabStops();
|
|
358
|
+
}
|
|
359
|
+
}, [hasRovingTabIndex, syncTabStops]);
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get the currently focused cell index within the full cell set.
|
|
363
|
+
*/
|
|
364
|
+
const getCurrentIndex = useCallback((): number => {
|
|
365
|
+
const cells = getCells();
|
|
366
|
+
const active = document.activeElement;
|
|
367
|
+
return cells.findIndex(cell => cell === active || cell.contains(active));
|
|
368
|
+
}, [getCells]);
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Find the next focusable cell starting at `startIndex`, moving by `step`.
|
|
372
|
+
* Returns the index of the focusable cell, or -1 if none exists in range
|
|
373
|
+
* (i.e. we ran off the start/end of the grid).
|
|
374
|
+
*/
|
|
375
|
+
const findFocusableInDirection = useCallback(
|
|
376
|
+
(cells: HTMLElement[], startIndex: number, step: number): number => {
|
|
377
|
+
let index = startIndex;
|
|
378
|
+
while (index >= 0 && index < cells.length) {
|
|
379
|
+
if (cellFocusable(cells[index])) {
|
|
380
|
+
return index;
|
|
381
|
+
}
|
|
382
|
+
index += step;
|
|
383
|
+
}
|
|
384
|
+
return -1;
|
|
385
|
+
},
|
|
386
|
+
[cellFocusable],
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Focus a cell by index, clamping to valid range. Skips non-focusable cells
|
|
391
|
+
* toward the nearest edge. Used by public focus helpers (no navigation
|
|
392
|
+
* callbacks).
|
|
177
393
|
*/
|
|
178
394
|
const focusCell = useCallback(
|
|
179
395
|
(index: number) => {
|
|
@@ -182,104 +398,162 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
182
398
|
return;
|
|
183
399
|
}
|
|
184
400
|
const clampedIndex = Math.max(0, Math.min(index, cells.length - 1));
|
|
185
|
-
|
|
401
|
+
// Prefer the requested cell; otherwise search forward, then backward.
|
|
402
|
+
let target = findFocusableInDirection(cells, clampedIndex, 1);
|
|
403
|
+
if (target === -1) {
|
|
404
|
+
target = findFocusableInDirection(cells, clampedIndex, -1);
|
|
405
|
+
}
|
|
406
|
+
if (target !== -1) {
|
|
407
|
+
focusCellWithStop(cells[target]);
|
|
408
|
+
}
|
|
186
409
|
},
|
|
187
|
-
[getCells],
|
|
410
|
+
[getCells, findFocusableInDirection, focusCellWithStop],
|
|
188
411
|
);
|
|
189
412
|
|
|
190
413
|
/**
|
|
191
|
-
* Focus the first cell.
|
|
414
|
+
* Focus the first focusable cell.
|
|
192
415
|
*/
|
|
193
416
|
const focusFirst = useCallback(() => {
|
|
194
417
|
const cells = getCells();
|
|
195
|
-
cells
|
|
196
|
-
|
|
418
|
+
const index = findFocusableInDirection(cells, 0, 1);
|
|
419
|
+
if (index !== -1) {
|
|
420
|
+
focusCellWithStop(cells[index]);
|
|
421
|
+
}
|
|
422
|
+
}, [getCells, findFocusableInDirection, focusCellWithStop]);
|
|
197
423
|
|
|
198
424
|
/**
|
|
199
|
-
* Focus the last cell.
|
|
425
|
+
* Focus the last focusable cell.
|
|
200
426
|
*/
|
|
201
427
|
const focusLast = useCallback(() => {
|
|
202
428
|
const cells = getCells();
|
|
203
|
-
cells
|
|
204
|
-
|
|
429
|
+
const index = findFocusableInDirection(cells, cells.length - 1, -1);
|
|
430
|
+
if (index !== -1) {
|
|
431
|
+
focusCellWithStop(cells[index]);
|
|
432
|
+
}
|
|
433
|
+
}, [getCells, findFocusableInDirection, focusCellWithStop]);
|
|
205
434
|
|
|
206
435
|
/**
|
|
207
436
|
* Handle keyboard navigation.
|
|
208
437
|
*/
|
|
209
438
|
const handleKeyDown = useCallback(
|
|
210
439
|
(e: React.KeyboardEvent) => {
|
|
440
|
+
const cells = getCells();
|
|
441
|
+
if (cells.length === 0) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
211
445
|
const currentIndex = getCurrentIndex();
|
|
212
446
|
if (currentIndex === -1) {
|
|
213
447
|
return;
|
|
214
448
|
}
|
|
215
449
|
|
|
216
|
-
const cells = getCells();
|
|
217
450
|
const currentRow = Math.floor(currentIndex / columns);
|
|
218
451
|
const currentCol = currentIndex % columns;
|
|
219
452
|
const totalRows = Math.ceil(cells.length / columns);
|
|
220
453
|
|
|
221
454
|
let handled = true;
|
|
222
455
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
456
|
+
// In RTL, ArrowLeft/ArrowRight are swapped so horizontal navigation
|
|
457
|
+
// follows visual direction. Vertical keys (Up/Down) are unaffected.
|
|
458
|
+
let key = e.key;
|
|
459
|
+
if (isRtl) {
|
|
460
|
+
if (key === 'ArrowLeft') {
|
|
461
|
+
key = 'ArrowRight';
|
|
462
|
+
} else if (key === 'ArrowRight') {
|
|
463
|
+
key = 'ArrowLeft';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
switch (key) {
|
|
468
|
+
case 'ArrowRight': {
|
|
469
|
+
// Move right, skipping non-focusable cells in the same direction.
|
|
470
|
+
const target = findFocusableInDirection(cells, currentIndex + 1, 1);
|
|
471
|
+
if (target !== -1) {
|
|
472
|
+
focusCellWithStop(cells[target]);
|
|
473
|
+
} else {
|
|
474
|
+
onNavigateAfter?.((currentCol + 1) % columns, 1);
|
|
475
|
+
}
|
|
227
476
|
break;
|
|
477
|
+
}
|
|
228
478
|
|
|
229
|
-
case 'ArrowLeft':
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
479
|
+
case 'ArrowLeft': {
|
|
480
|
+
const target = findFocusableInDirection(cells, currentIndex - 1, -1);
|
|
481
|
+
if (target !== -1) {
|
|
482
|
+
focusCellWithStop(cells[target]);
|
|
483
|
+
} else {
|
|
484
|
+
onNavigateBefore?.(
|
|
485
|
+
currentCol === 0 ? columns - 1 : currentCol - 1,
|
|
486
|
+
1,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
236
489
|
break;
|
|
490
|
+
}
|
|
237
491
|
|
|
238
|
-
case 'ArrowDown':
|
|
239
|
-
// Vertical navigation: offset = columns (7 for calendar)
|
|
492
|
+
case 'ArrowDown': {
|
|
240
493
|
if (currentRow < totalRows - 1) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
494
|
+
// Move to the same column one row down, then continue downward
|
|
495
|
+
// (by whole rows) to the next focusable cell in that column.
|
|
496
|
+
const target = findFocusableInDirection(
|
|
497
|
+
cells,
|
|
498
|
+
currentIndex + columns,
|
|
499
|
+
columns,
|
|
500
|
+
);
|
|
501
|
+
if (target !== -1) {
|
|
502
|
+
focusCellWithStop(cells[target]);
|
|
244
503
|
} else {
|
|
245
|
-
|
|
246
|
-
focusCellInternal(cells.length - 1, currentCol, columns);
|
|
504
|
+
onNavigateAfter?.(currentCol, columns);
|
|
247
505
|
}
|
|
248
506
|
} else {
|
|
249
507
|
onNavigateAfter?.(currentCol, columns);
|
|
250
508
|
}
|
|
251
509
|
break;
|
|
510
|
+
}
|
|
252
511
|
|
|
253
|
-
case 'ArrowUp':
|
|
254
|
-
// Vertical navigation: offset = columns (7 for calendar)
|
|
512
|
+
case 'ArrowUp': {
|
|
255
513
|
if (currentRow > 0) {
|
|
256
|
-
|
|
514
|
+
const target = findFocusableInDirection(
|
|
515
|
+
cells,
|
|
516
|
+
currentIndex - columns,
|
|
517
|
+
-columns,
|
|
518
|
+
);
|
|
519
|
+
if (target !== -1) {
|
|
520
|
+
focusCellWithStop(cells[target]);
|
|
521
|
+
} else {
|
|
522
|
+
onNavigateBefore?.(currentCol, columns);
|
|
523
|
+
}
|
|
257
524
|
} else {
|
|
258
525
|
onNavigateBefore?.(currentCol, columns);
|
|
259
526
|
}
|
|
260
527
|
break;
|
|
528
|
+
}
|
|
261
529
|
|
|
262
530
|
case 'Home':
|
|
263
531
|
if (e.ctrlKey || e.metaKey) {
|
|
264
|
-
// Ctrl+Home: first cell in grid
|
|
532
|
+
// Ctrl+Home: first focusable cell in grid
|
|
265
533
|
focusFirst();
|
|
266
534
|
} else {
|
|
267
|
-
// Home: first cell in current row
|
|
268
|
-
|
|
535
|
+
// Home: first focusable cell in current row (searching rightward)
|
|
536
|
+
const rowStart = currentRow * columns;
|
|
537
|
+
const rowEnd = Math.min(rowStart + columns - 1, cells.length - 1);
|
|
538
|
+
const target = findFocusableInDirection(cells, rowStart, 1);
|
|
539
|
+
if (target !== -1 && target <= rowEnd) {
|
|
540
|
+
focusCellWithStop(cells[target]);
|
|
541
|
+
}
|
|
269
542
|
}
|
|
270
543
|
break;
|
|
271
544
|
|
|
272
545
|
case 'End':
|
|
273
546
|
if (e.ctrlKey || e.metaKey) {
|
|
274
|
-
// Ctrl+End: last cell in grid
|
|
547
|
+
// Ctrl+End: last focusable cell in grid
|
|
275
548
|
focusLast();
|
|
276
549
|
} else {
|
|
277
|
-
// End: last cell in current row
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
)
|
|
282
|
-
|
|
550
|
+
// End: last focusable cell in current row (searching leftward)
|
|
551
|
+
const rowStart = currentRow * columns;
|
|
552
|
+
const rowEnd = Math.min(rowStart + columns - 1, cells.length - 1);
|
|
553
|
+
const target = findFocusableInDirection(cells, rowEnd, -1);
|
|
554
|
+
if (target !== -1 && target >= rowStart) {
|
|
555
|
+
focusCellWithStop(cells[target]);
|
|
556
|
+
}
|
|
283
557
|
}
|
|
284
558
|
break;
|
|
285
559
|
|
|
@@ -302,12 +576,13 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
302
576
|
},
|
|
303
577
|
[
|
|
304
578
|
columns,
|
|
305
|
-
|
|
306
|
-
|
|
579
|
+
findFocusableInDirection,
|
|
580
|
+
focusCellWithStop,
|
|
307
581
|
focusFirst,
|
|
308
582
|
focusLast,
|
|
309
583
|
getCells,
|
|
310
584
|
getCurrentIndex,
|
|
585
|
+
isRtl,
|
|
311
586
|
onNavigateAfter,
|
|
312
587
|
onNavigateBefore,
|
|
313
588
|
onPageDown,
|
|
@@ -318,6 +593,7 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
318
593
|
return {
|
|
319
594
|
gridRef,
|
|
320
595
|
handleKeyDown,
|
|
596
|
+
handleFocus,
|
|
321
597
|
focusCell,
|
|
322
598
|
focusFirst,
|
|
323
599
|
focusLast,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../docs-types').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useKeyboardHint',
|
|
6
|
+
displayName: 'useKeyboardHint',
|
|
7
|
+
keywords: ['keyboard', 'hint', 'arrow', 'navigation', 'roving', 'tabindex', 'focus', 'discoverability', 'toolbar', 'tabs', 'segmented', 'affordance', 'a11y'],
|
|
8
|
+
params: [
|
|
9
|
+
{
|
|
10
|
+
name: 'options',
|
|
11
|
+
type: 'UseKeyboardHintOptions',
|
|
12
|
+
description: 'Configuration object for the keyboard hint.',
|
|
13
|
+
required: false,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'options.orientation',
|
|
17
|
+
type: "'horizontal' | 'vertical' | 'both'",
|
|
18
|
+
description: 'Which arrow-key axis the composite navigates, controlling which arrow icons the hint shows (← → for horizontal, ↑ ↓ for vertical, all four for both).',
|
|
19
|
+
default: "'horizontal'",
|
|
20
|
+
required: false,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'options.dismissAfterMs',
|
|
24
|
+
type: 'number',
|
|
25
|
+
description: 'Milliseconds before the hint auto-dismisses after appearing.',
|
|
26
|
+
default: '3000',
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'options.isEnabled',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
description: 'Whether the hint is enabled. Set to false to suppress it for a specific instance (e.g. a disabled or read-only widget).',
|
|
33
|
+
default: 'true',
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
returns: [
|
|
38
|
+
{
|
|
39
|
+
name: 'hintElement',
|
|
40
|
+
type: 'ReactNode',
|
|
41
|
+
description: 'The popover hint element to render inside the composite container (as the last child). Portals to the top layer via popover="manual", renders arrow keys with Kbd, and manages its own visibility — render it unconditionally.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'onFocus',
|
|
45
|
+
type: '(e: React.FocusEvent) => void',
|
|
46
|
+
description: 'Attach to the container onFocus. Shows the hint on the first keyboard-focus (:focus-visible) entry from outside the composite.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'onBlur',
|
|
50
|
+
type: '(e: React.FocusEvent) => void',
|
|
51
|
+
description: 'Attach to the container onBlur. Hides the hint when focus leaves the composite entirely, and re-anchors when focus moves within.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'onKeyDown',
|
|
55
|
+
type: '(e: React.KeyboardEvent) => void',
|
|
56
|
+
description: 'Attach to the container onKeyDown. Dismisses the hint on the first arrow press (the user has discovered the interaction). Never prevents default or stops propagation.',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
usage: {
|
|
60
|
+
description:
|
|
61
|
+
'Shows an ephemeral "← → to navigate" hint anchored to the focused item the first time a roving-tabindex composite (Toolbar, TabList, SegmentedControl, etc.) receives keyboard focus. It teaches sighted keyboard users that arrow keys move within the group. The hint renders arrow keys with Kbd in the top layer (popover="manual") and is CSS-anchor-positioned to the focused element, so overflow containers never clip it. It auto-dismisses on the first arrow press, on timeout, or on blur, and does not re-show for that instance. Toolbar, TabList, and SegmentedControl wire this in automatically — reach for the hook directly only when building a custom roving-tabindex widget.',
|
|
62
|
+
bestPractices: [
|
|
63
|
+
{ guidance: true, description: 'Compose the returned onFocus/onKeyDown with your existing focus handlers rather than replacing them — call onKeyDown first (it only dismisses, never prevents), then your navigation handler.' },
|
|
64
|
+
{ guidance: true, description: 'Render hintElement as the last child of the composite container; it is position:fixed in the top layer and aria-hidden, so it never affects layout or the accessibility tree.' },
|
|
65
|
+
{ guidance: true, description: 'Match orientation to the arrow keys your widget actually responds to so the hint shows the correct icons.' },
|
|
66
|
+
{ guidance: false, description: 'Use for single controls or widgets without roving-tabindex navigation — the hint only makes sense where arrows move focus within a group.' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
relatedComponents: ['Toolbar', 'TabList', 'SegmentedControl'],
|
|
70
|
+
relatedHooks: ['useListFocus', 'useGridFocus', 'useTreeFocus'],
|
|
71
|
+
importPath: '@astryxdesign/core/hooks',
|
|
72
|
+
category: 'focus',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** @type {import('../docs-types').HookTranslationDoc} */
|
|
76
|
+
export const docsDense = {
|
|
77
|
+
description:
|
|
78
|
+
'Ephemeral "← → to navigate" hint anchored to the focused item on first keyboard focus of a roving-tabindex composite (Toolbar/TabList/SegmentedControl). Teaches sighted keyboard users that arrows move within the group. Kbd-rendered arrows in a top-layer popover="manual", CSS-anchor-positioned (never clipped by overflow), aria-hidden. Auto-dismisses on first arrow press, timeout, or blur; never re-shows for that instance.',
|
|
79
|
+
paramDescriptions: {
|
|
80
|
+
options: 'config for the keyboard hint.',
|
|
81
|
+
'options.orientation': "arrow axis: 'horizontal' (← →), 'vertical' (↑ ↓), 'both' (all four). Controls shown icons.",
|
|
82
|
+
'options.dismissAfterMs': 'ms before auto-dismiss after appearing.',
|
|
83
|
+
'options.isEnabled': 'whether the hint is enabled; false suppresses it (e.g. disabled/read-only widget).',
|
|
84
|
+
},
|
|
85
|
+
returnDescriptions: {
|
|
86
|
+
hintElement: 'popover hint to render as last child of the container. Kbd-rendered arrows, top-layer, self-managing — render unconditionally.',
|
|
87
|
+
onFocus: 'container onFocus: shows hint on first :focus-visible entry from outside.',
|
|
88
|
+
onBlur: 'container onBlur: hides on leaving the composite; re-anchors on internal moves.',
|
|
89
|
+
onKeyDown: 'container onKeyDown: dismisses on first arrow press. Never prevents default.',
|
|
90
|
+
},
|
|
91
|
+
usage: {
|
|
92
|
+
description:
|
|
93
|
+
'Ephemeral "← → to navigate" hint on first keyboard focus of a roving-tabindex composite. Top-layer anchored popover, aria-hidden. Auto-dismisses on first arrow press, timeout, or blur; no re-show. Toolbar/TabList/SegmentedControl wire it in automatically.',
|
|
94
|
+
bestPractices: [
|
|
95
|
+
{ guidance: true, description: 'Compose returned onFocus/onKeyDown with existing handlers (call onKeyDown first — it only dismisses).' },
|
|
96
|
+
{ guidance: true, description: 'Render hintElement as last child; top-layer + aria-hidden, no layout/a11y impact.' },
|
|
97
|
+
{ guidance: true, description: 'Match orientation to the arrows the widget responds to.' },
|
|
98
|
+
{ guidance: false, description: 'Use for single controls / non-roving widgets.' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
};
|