@astryxdesign/core 0.1.2 → 0.1.3-canary.6d57c8d
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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for tree focus behavior.
|
|
3
|
+
*
|
|
4
|
+
* The tree keyboard model differs from a linear list (useListFocus): while
|
|
5
|
+
* ArrowUp/ArrowDown/Home/End roam linearly over the *visible* treeitems,
|
|
6
|
+
* ArrowRight/ArrowLeft carry tree semantics (expand/collapse, move to
|
|
7
|
+
* first-child / parent). The hook stays generic by taking callbacks for the
|
|
8
|
+
* tree-specific bits (expansion toggling, activation) — mirroring how
|
|
9
|
+
* useGridFocus takes `isCellFocusable` rather than hardcoding disabled logic.
|
|
10
|
+
*/
|
|
11
|
+
export interface UseTreeFocusOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Selector for treeitems within the tree. Matches ALL visible treeitems in
|
|
14
|
+
* DOM order (collapsed subtrees are not rendered, so they are naturally
|
|
15
|
+
* excluded). Disabled treeitems are still matched and then skipped via
|
|
16
|
+
* {@link UseTreeFocusOptions.isItemDisabled}.
|
|
17
|
+
* @default '[role="treeitem"]'
|
|
18
|
+
*/
|
|
19
|
+
itemSelector?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Predicate determining whether a treeitem matched by `itemSelector` is
|
|
22
|
+
* disabled and must be skipped during arrow/Home/End navigation. A `.focus()`
|
|
23
|
+
* on a disabled item silently no-ops, so navigation would otherwise stall.
|
|
24
|
+
*
|
|
25
|
+
* @default reads `data-tree-disabled` (present ⇒ disabled)
|
|
26
|
+
*/
|
|
27
|
+
isItemDisabled?: (item: HTMLElement) => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Reads the nesting level (aria-level style, 1-based) of a treeitem. Used to
|
|
30
|
+
* resolve first-child (ArrowRight) and parent (ArrowLeft) targets from the
|
|
31
|
+
* flat visible-item list.
|
|
32
|
+
*
|
|
33
|
+
* @default reads the `aria-level` attribute (falling back to `1`)
|
|
34
|
+
*/
|
|
35
|
+
getLevel?: (item: HTMLElement) => number;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a treeitem is an expanded parent. Read DOM-side (aria-expanded)
|
|
38
|
+
* by default so it reflects the rendered tree without prop plumbing.
|
|
39
|
+
*
|
|
40
|
+
* @default `aria-expanded === 'true'`
|
|
41
|
+
*/
|
|
42
|
+
isExpanded?: (item: HTMLElement) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a treeitem is a collapsed parent (has children but is closed).
|
|
45
|
+
*
|
|
46
|
+
* @default `aria-expanded === 'false'`
|
|
47
|
+
*/
|
|
48
|
+
isCollapsed?: (item: HTMLElement) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the stable id for a treeitem, passed to `onToggleExpand` /
|
|
51
|
+
* `onActivate`. Returns `undefined` when the element carries no id.
|
|
52
|
+
*
|
|
53
|
+
* @default reads the `data-tree-id` attribute
|
|
54
|
+
*/
|
|
55
|
+
getItemId?: (item: HTMLElement) => string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Called to expand/collapse the treeitem with the given id (ArrowRight on a
|
|
58
|
+
* collapsed parent, ArrowLeft on an expanded parent, and Enter/Space on a
|
|
59
|
+
* parent that has no inner action).
|
|
60
|
+
*/
|
|
61
|
+
onToggleExpand?: (id: string) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Called when Enter/Space activates a treeitem. Return `true` if the
|
|
64
|
+
* activation was handled (e.g. an inner link/button was clicked); return
|
|
65
|
+
* `false`/`undefined` to let the hook fall back to toggling expansion for a
|
|
66
|
+
* parent. When omitted, the hook falls straight through to expansion
|
|
67
|
+
* toggling for parents.
|
|
68
|
+
*
|
|
69
|
+
* @param item The focused treeitem element.
|
|
70
|
+
* @param id The treeitem's id (per `getItemId`), if any.
|
|
71
|
+
*/
|
|
72
|
+
onActivate?: (item: HTMLElement, id: string | undefined) => boolean | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Whether typeahead (jump to next item whose text starts with the typed
|
|
75
|
+
* characters) is enabled.
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
typeahead?: boolean;
|
|
79
|
+
/** Reset delay for the typeahead buffer, in ms. @default 500 */
|
|
80
|
+
typeaheadResetMs?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Notified whenever the hook moves focus to a treeitem, with its id (if any).
|
|
83
|
+
* TreeList uses this to move its single roving tab stop.
|
|
84
|
+
*/
|
|
85
|
+
onActiveChange?: (id: string | undefined) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Roving-tabindex ownership. When true, the hook manages a single tab stop
|
|
88
|
+
* across the visible treeitems: exactly one enabled treeitem carries
|
|
89
|
+
* `tabindex="0"` and the rest `tabindex="-1"`. The tab stop is repaired on
|
|
90
|
+
* mount and whenever items mount/unmount or toggle disabled, and moves with
|
|
91
|
+
* keyboard navigation. Attach the returned {@link UseTreeFocusReturn.handleFocus}
|
|
92
|
+
* to the container's `onFocus` to keep the stop in sync after clicks or
|
|
93
|
+
* programmatic focus.
|
|
94
|
+
*
|
|
95
|
+
* On mount the hook preserves an existing `tabindex="0"` treeitem (so a
|
|
96
|
+
* consumer can seed the active item in its render); if none exists it
|
|
97
|
+
* promotes the first enabled treeitem.
|
|
98
|
+
*
|
|
99
|
+
* When false (the default), the hook only *moves* focus (`.focus()`) and
|
|
100
|
+
* never touches `tabindex` — the caller owns tab-stop management.
|
|
101
|
+
* @default false
|
|
102
|
+
*/
|
|
103
|
+
hasRovingTabIndex?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Return type for useTreeFocus hook.
|
|
107
|
+
*/
|
|
108
|
+
export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
109
|
+
/** Ref to attach to the tree container element (role="tree"). */
|
|
110
|
+
treeRef: React.RefObject<T | null>;
|
|
111
|
+
/** Key down handler to attach to the tree container. */
|
|
112
|
+
handleKeyDown: (e: React.KeyboardEvent) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Focus handler to attach to the container's `onFocus`. Keeps the roving tab
|
|
115
|
+
* stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it
|
|
116
|
+
* is always safe to attach.
|
|
117
|
+
*/
|
|
118
|
+
handleFocus: (e: React.FocusEvent) => void;
|
|
119
|
+
/** Focus the first enabled visible treeitem. */
|
|
120
|
+
focusFirst: () => void;
|
|
121
|
+
/** Focus the last enabled visible treeitem. */
|
|
122
|
+
focusLast: () => void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Hook for managing roving-tabindex focus + the WAI-ARIA tree keyboard model.
|
|
126
|
+
*
|
|
127
|
+
* A tree is not a linear list: ArrowUp/ArrowDown/Home/End roam linearly over
|
|
128
|
+
* the *visible* treeitems (skipping disabled ones), but ArrowRight/ArrowLeft
|
|
129
|
+
* carry tree semantics. This is why a tree needs its own hook rather than
|
|
130
|
+
* reusing `useListFocus` — the same reason 2D grids get `useGridFocus`.
|
|
131
|
+
*
|
|
132
|
+
* - ArrowDown / ArrowUp: move to next/previous visible treeitem (skip disabled)
|
|
133
|
+
* - ArrowRight: collapsed parent → expand; expanded parent → first child;
|
|
134
|
+
* leaf → no-op
|
|
135
|
+
* - ArrowLeft: expanded parent → collapse; otherwise → move to parent treeitem
|
|
136
|
+
* - Home / End: first / last visible treeitem
|
|
137
|
+
* - Enter / Space: activate (`onActivate`), falling back to expansion toggle
|
|
138
|
+
* - Printable characters: typeahead to the next matching treeitem
|
|
139
|
+
*
|
|
140
|
+
* The hook is DOM-query based (reads aria-expanded/aria-level and data-*
|
|
141
|
+
* attributes) and stays generic via option callbacks for the tree-specific
|
|
142
|
+
* bits (`onToggleExpand`, `onActivate`, `getLevel`, `isItemDisabled`, …).
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```
|
|
146
|
+
* const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
|
|
147
|
+
* onToggleExpand: id => toggle(id),
|
|
148
|
+
* hasRovingTabIndex: true,
|
|
149
|
+
* });
|
|
150
|
+
*
|
|
151
|
+
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
152
|
+
* {items.map(item => <li role="treeitem" tabIndex={-1}>{item.label}</li>)}
|
|
153
|
+
* </ul>
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function useTreeFocus<T extends HTMLElement = HTMLElement>(options?: UseTreeFocusOptions): UseTreeFocusReturn<T>;
|
|
157
|
+
//# sourceMappingURL=useTreeFocus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTreeFocus.d.ts","sourceRoot":"","sources":["../../src/hooks/useTreeFocus.ts"],"names":[],"mappings":"AAkCA;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAEhD;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAC;IAEzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAE5C;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAE7C;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAC;IAEtD;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAEtC;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,GAAG,SAAS,KACnB,OAAO,GAAG,SAAS,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAElD;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE,iEAAiE;IACjE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEnC,wDAAwD;IACxD,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAEhD;;;;OAIG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAE3C,gDAAgD;IAChD,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAAC,CAAC,CAAC,CA2VvB"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTreeFocus.ts
|
|
7
|
+
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
|
|
8
|
+
* @output Exports useTreeFocus hook for WAI-ARIA tree keyboard navigation
|
|
9
|
+
* @position Core hook; used by TreeList for roving tabindex + APG tree keyboard model
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/hooks/index.ts
|
|
13
|
+
* - /packages/core/src/hooks/useTreeFocus.doc.mjs
|
|
14
|
+
* - /packages/core/src/hooks/useTreeFocus.test.tsx
|
|
15
|
+
*/
|
|
16
|
+
import { useCallback, useRef } from 'react';
|
|
17
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
|
|
18
|
+
|
|
19
|
+
/** Keys handled by the tree keyboard model (used to gate typeahead). */
|
|
20
|
+
const NAVIGATION_KEYS = new Set(['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' ']);
|
|
21
|
+
|
|
22
|
+
/** Default reset delay for the typeahead buffer. */
|
|
23
|
+
const DEFAULT_TYPEAHEAD_RESET_MS = 500;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for tree focus behavior.
|
|
27
|
+
*
|
|
28
|
+
* The tree keyboard model differs from a linear list (useListFocus): while
|
|
29
|
+
* ArrowUp/ArrowDown/Home/End roam linearly over the *visible* treeitems,
|
|
30
|
+
* ArrowRight/ArrowLeft carry tree semantics (expand/collapse, move to
|
|
31
|
+
* first-child / parent). The hook stays generic by taking callbacks for the
|
|
32
|
+
* tree-specific bits (expansion toggling, activation) — mirroring how
|
|
33
|
+
* useGridFocus takes `isCellFocusable` rather than hardcoding disabled logic.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Return type for useTreeFocus hook.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook for managing roving-tabindex focus + the WAI-ARIA tree keyboard model.
|
|
42
|
+
*
|
|
43
|
+
* A tree is not a linear list: ArrowUp/ArrowDown/Home/End roam linearly over
|
|
44
|
+
* the *visible* treeitems (skipping disabled ones), but ArrowRight/ArrowLeft
|
|
45
|
+
* carry tree semantics. This is why a tree needs its own hook rather than
|
|
46
|
+
* reusing `useListFocus` — the same reason 2D grids get `useGridFocus`.
|
|
47
|
+
*
|
|
48
|
+
* - ArrowDown / ArrowUp: move to next/previous visible treeitem (skip disabled)
|
|
49
|
+
* - ArrowRight: collapsed parent → expand; expanded parent → first child;
|
|
50
|
+
* leaf → no-op
|
|
51
|
+
* - ArrowLeft: expanded parent → collapse; otherwise → move to parent treeitem
|
|
52
|
+
* - Home / End: first / last visible treeitem
|
|
53
|
+
* - Enter / Space: activate (`onActivate`), falling back to expansion toggle
|
|
54
|
+
* - Printable characters: typeahead to the next matching treeitem
|
|
55
|
+
*
|
|
56
|
+
* The hook is DOM-query based (reads aria-expanded/aria-level and data-*
|
|
57
|
+
* attributes) and stays generic via option callbacks for the tree-specific
|
|
58
|
+
* bits (`onToggleExpand`, `onActivate`, `getLevel`, `isItemDisabled`, …).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```
|
|
62
|
+
* const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
|
|
63
|
+
* onToggleExpand: id => toggle(id),
|
|
64
|
+
* hasRovingTabIndex: true,
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
68
|
+
* {items.map(item => <li role="treeitem" tabIndex={-1}>{item.label}</li>)}
|
|
69
|
+
* </ul>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function useTreeFocus(options = {}) {
|
|
73
|
+
const {
|
|
74
|
+
itemSelector = '[role="treeitem"]',
|
|
75
|
+
isItemDisabled,
|
|
76
|
+
getLevel,
|
|
77
|
+
isExpanded,
|
|
78
|
+
isCollapsed,
|
|
79
|
+
getItemId,
|
|
80
|
+
onToggleExpand,
|
|
81
|
+
onActivate,
|
|
82
|
+
typeahead = true,
|
|
83
|
+
typeaheadResetMs = DEFAULT_TYPEAHEAD_RESET_MS,
|
|
84
|
+
onActiveChange,
|
|
85
|
+
hasRovingTabIndex = false
|
|
86
|
+
} = options;
|
|
87
|
+
const treeRef = useRef(null);
|
|
88
|
+
const typeaheadRef = useRef({
|
|
89
|
+
buffer: '',
|
|
90
|
+
timer: null
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** Visible treeitems in DOM order (collapsed subtrees are not rendered). */
|
|
94
|
+
const getItems = useCallback(() => {
|
|
95
|
+
const root = treeRef.current;
|
|
96
|
+
if (root == null) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return Array.from(root.querySelectorAll(itemSelector));
|
|
100
|
+
}, [itemSelector]);
|
|
101
|
+
const itemDisabled = useCallback(el => isItemDisabled ? isItemDisabled(el) : el.dataset.treeDisabled != null || el.getAttribute('aria-disabled') === 'true', [isItemDisabled]);
|
|
102
|
+
const levelOf = useCallback(el => getLevel ? getLevel(el) : Number(el.getAttribute('aria-level') ?? '1'), [getLevel]);
|
|
103
|
+
const expandedOf = useCallback(el => isExpanded ? isExpanded(el) : el.getAttribute('aria-expanded') === 'true', [isExpanded]);
|
|
104
|
+
const collapsedOf = useCallback(el => isCollapsed ? isCollapsed(el) : el.getAttribute('aria-expanded') === 'false', [isCollapsed]);
|
|
105
|
+
const idOf = useCallback(el => getItemId ? getItemId(el) : el.dataset.treeId, [getItemId]);
|
|
106
|
+
|
|
107
|
+
// --- Roving tabindex ownership (opt-in via `hasRovingTabIndex`) -------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Set `tabindex` on a treeitem, but only when it differs (avoids redundant
|
|
111
|
+
* DOM writes).
|
|
112
|
+
*/
|
|
113
|
+
const setTabIndex = useCallback((el, value) => {
|
|
114
|
+
if (el.getAttribute('tabindex') !== String(value)) {
|
|
115
|
+
el.setAttribute('tabindex', String(value));
|
|
116
|
+
}
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Make `target` the sole tabbable treeitem: 0 on it, -1 on every other
|
|
121
|
+
* visible treeitem.
|
|
122
|
+
*/
|
|
123
|
+
const moveTabStop = useCallback((items, target) => {
|
|
124
|
+
for (const el of items) {
|
|
125
|
+
setTabIndex(el, el === target ? 0 : -1);
|
|
126
|
+
}
|
|
127
|
+
}, [setTabIndex]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Repair the roving tab stop: exactly one enabled treeitem is tabbable (0),
|
|
131
|
+
* the rest are -1. Prefer an existing `tabindex="0"` treeitem (so a consumer
|
|
132
|
+
* can seed the active item in its render); otherwise promote the first
|
|
133
|
+
* enabled treeitem.
|
|
134
|
+
*/
|
|
135
|
+
const syncTabStops = useCallback(() => {
|
|
136
|
+
const items = getItems();
|
|
137
|
+
const enabled = items.filter(el => !itemDisabled(el));
|
|
138
|
+
if (enabled.length === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const current = enabled.find(el => el.getAttribute('tabindex') === '0');
|
|
142
|
+
moveTabStop(items, current ?? enabled[0]);
|
|
143
|
+
}, [getItems, itemDisabled, moveTabStop]);
|
|
144
|
+
|
|
145
|
+
// Keep the tab stop valid across renders (items added/removed, disabled
|
|
146
|
+
// toggled). Runs after every commit but only when roving tabindex is on.
|
|
147
|
+
useIsomorphicLayoutEffect(() => {
|
|
148
|
+
if (hasRovingTabIndex) {
|
|
149
|
+
syncTabStops();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/** Move focus to a treeitem and notify the active-change listener. */
|
|
154
|
+
const focusItem = useCallback(el => {
|
|
155
|
+
if (el == null) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (hasRovingTabIndex) {
|
|
159
|
+
moveTabStop(getItems(), el);
|
|
160
|
+
}
|
|
161
|
+
onActiveChange?.(idOf(el));
|
|
162
|
+
el.focus();
|
|
163
|
+
}, [idOf, onActiveChange, hasRovingTabIndex, moveTabStop, getItems]);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Focus the first enabled treeitem from `start`, moving by `dir`. No wrap —
|
|
167
|
+
* a tree's linear roam clamps at the ends.
|
|
168
|
+
*/
|
|
169
|
+
const focusEnabledFrom = useCallback((items, start, dir) => {
|
|
170
|
+
for (let i = start; i >= 0 && i < items.length; i += dir) {
|
|
171
|
+
const candidate = items[i];
|
|
172
|
+
if (candidate != null && !itemDisabled(candidate)) {
|
|
173
|
+
focusItem(candidate);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, [itemDisabled, focusItem]);
|
|
178
|
+
const focusFirst = useCallback(() => {
|
|
179
|
+
focusEnabledFrom(getItems(), 0, 1);
|
|
180
|
+
}, [getItems, focusEnabledFrom]);
|
|
181
|
+
const focusLast = useCallback(() => {
|
|
182
|
+
const items = getItems();
|
|
183
|
+
focusEnabledFrom(items, items.length - 1, -1);
|
|
184
|
+
}, [getItems, focusEnabledFrom]);
|
|
185
|
+
const runTypeahead = useCallback((e, items, currentIndex) => {
|
|
186
|
+
const state = typeaheadRef.current;
|
|
187
|
+
if (state.timer != null) {
|
|
188
|
+
clearTimeout(state.timer);
|
|
189
|
+
}
|
|
190
|
+
state.buffer += e.key.toLowerCase();
|
|
191
|
+
state.timer = setTimeout(() => {
|
|
192
|
+
typeaheadRef.current.buffer = '';
|
|
193
|
+
}, typeaheadResetMs);
|
|
194
|
+
const query = state.buffer;
|
|
195
|
+
const start = currentIndex < 0 ? 0 : currentIndex;
|
|
196
|
+
const ordered = [...items.slice(start + 1), ...items.slice(0, start + 1)];
|
|
197
|
+
const match = ordered.find(item => !itemDisabled(item) && (item.textContent ?? '').trim().toLowerCase().startsWith(query));
|
|
198
|
+
if (match != null) {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
focusItem(match);
|
|
201
|
+
}
|
|
202
|
+
}, [typeaheadResetMs, itemDisabled, focusItem]);
|
|
203
|
+
const handleKeyDown = useCallback(e => {
|
|
204
|
+
const items = getItems();
|
|
205
|
+
if (items.length === 0) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const active = document.activeElement;
|
|
209
|
+
// Resolve the treeitem that owns focus: the nearest treeitem ancestor of
|
|
210
|
+
// the active element (never an outer treeitem that merely contains it).
|
|
211
|
+
const activeItem = active instanceof Element ? active.closest('[role="treeitem"]') : null;
|
|
212
|
+
const currentIndex = items.findIndex(item => item === activeItem);
|
|
213
|
+
const current = currentIndex >= 0 ? items[currentIndex] : undefined;
|
|
214
|
+
|
|
215
|
+
// Typeahead: printable single characters jump to the next matching item.
|
|
216
|
+
if (typeahead && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && NAVIGATION_KEYS.has(e.key) === false) {
|
|
217
|
+
runTypeahead(e, items, currentIndex);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!NAVIGATION_KEYS.has(e.key)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
switch (e.key) {
|
|
224
|
+
case 'ArrowDown':
|
|
225
|
+
{
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
focusEnabledFrom(items, currentIndex < 0 ? 0 : currentIndex + 1, 1);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'ArrowUp':
|
|
231
|
+
{
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
focusEnabledFrom(items, currentIndex < 0 ? items.length - 1 : currentIndex - 1, -1);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case 'ArrowRight':
|
|
237
|
+
{
|
|
238
|
+
if (current == null) {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
if (collapsedOf(current)) {
|
|
243
|
+
// Collapsed parent → expand.
|
|
244
|
+
const id = idOf(current);
|
|
245
|
+
if (id != null) {
|
|
246
|
+
onToggleExpand?.(id);
|
|
247
|
+
}
|
|
248
|
+
} else if (expandedOf(current)) {
|
|
249
|
+
// Expanded parent → move to first child.
|
|
250
|
+
const next = items[currentIndex + 1];
|
|
251
|
+
if (next != null && levelOf(next) > levelOf(current)) {
|
|
252
|
+
focusItem(next);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Leaf → no-op.
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'ArrowLeft':
|
|
259
|
+
{
|
|
260
|
+
if (current == null) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
if (expandedOf(current)) {
|
|
265
|
+
// Expanded parent → collapse.
|
|
266
|
+
const id = idOf(current);
|
|
267
|
+
if (id != null) {
|
|
268
|
+
onToggleExpand?.(id);
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Otherwise → move to parent treeitem (nearest shallower item
|
|
272
|
+
// scanning upward in visible order).
|
|
273
|
+
const currentLevel = levelOf(current);
|
|
274
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
275
|
+
const candidate = items[i];
|
|
276
|
+
if (candidate != null && levelOf(candidate) < currentLevel) {
|
|
277
|
+
focusItem(candidate);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'Home':
|
|
285
|
+
{
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
focusEnabledFrom(items, 0, 1);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'End':
|
|
291
|
+
{
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
focusEnabledFrom(items, items.length - 1, -1);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'Enter':
|
|
297
|
+
case ' ':
|
|
298
|
+
{
|
|
299
|
+
if (current == null || itemDisabled(current)) {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
const id = idOf(current);
|
|
304
|
+
// Activate the item: prefer the consumer's onActivate (e.g. click an
|
|
305
|
+
// inner link/button). Fall back to toggling expansion for a parent
|
|
306
|
+
// without its own action.
|
|
307
|
+
const handled = onActivate ? onActivate(current, id) === true : false;
|
|
308
|
+
if (!handled && current.getAttribute('aria-expanded') != null) {
|
|
309
|
+
if (id != null) {
|
|
310
|
+
onToggleExpand?.(id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
default:
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}, [getItems, typeahead, runTypeahead, focusEnabledFrom, focusItem, collapsedOf, expandedOf, idOf, levelOf, itemDisabled, onToggleExpand, onActivate]);
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Keep the roving stop pointing at whatever ended up focused (e.g. a click
|
|
322
|
+
* or programmatic focus) so the next Tab behaves correctly. No-op unless
|
|
323
|
+
* roving tabindex is enabled.
|
|
324
|
+
*/
|
|
325
|
+
const handleFocus = useCallback(() => {
|
|
326
|
+
if (hasRovingTabIndex) {
|
|
327
|
+
syncTabStops();
|
|
328
|
+
}
|
|
329
|
+
}, [hasRovingTabIndex, syncTabStops]);
|
|
330
|
+
return {
|
|
331
|
+
treeRef,
|
|
332
|
+
handleKeyDown,
|
|
333
|
+
handleFocus,
|
|
334
|
+
focusFirst,
|
|
335
|
+
focusLast
|
|
336
|
+
};
|
|
337
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface UseTypeaheadOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Returns the current, in-DOM-order list of item labels to match against.
|
|
4
|
+
* An entry may be `null`/empty to mark a non-matchable slot (its index is
|
|
5
|
+
* preserved so it maps 1:1 to the caller's items).
|
|
6
|
+
*/
|
|
7
|
+
getItemLabels: () => ReadonlyArray<string | null | undefined>;
|
|
8
|
+
/**
|
|
9
|
+
* Called with the index of the matched item so the caller can focus/select
|
|
10
|
+
* it (e.g. `useListFocus`'s `focusItem`).
|
|
11
|
+
*/
|
|
12
|
+
onMatch: (index: number) => void;
|
|
13
|
+
/**
|
|
14
|
+
* The index to start searching from — typically the currently focused item,
|
|
15
|
+
* so repeated presses of the same letter cycle through matches. Defaults to
|
|
16
|
+
* -1 (search from the top).
|
|
17
|
+
*/
|
|
18
|
+
getCurrentIndex?: () => number;
|
|
19
|
+
/**
|
|
20
|
+
* Milliseconds of inactivity after which the typed buffer resets.
|
|
21
|
+
* @default 750
|
|
22
|
+
*/
|
|
23
|
+
resetMs?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Whether an index should be skipped (e.g. disabled items).
|
|
26
|
+
*/
|
|
27
|
+
isDisabled?: (index: number) => boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface UseTypeaheadReturn {
|
|
30
|
+
/**
|
|
31
|
+
* Keydown handler. Call it (or let it fall through) from the collection's
|
|
32
|
+
* own key handler. Returns `true` when it handled a printable character (so
|
|
33
|
+
* the caller can `preventDefault`/stop further handling), `false` otherwise.
|
|
34
|
+
*/
|
|
35
|
+
onKeyDown: (e: React.KeyboardEvent | KeyboardEvent) => boolean;
|
|
36
|
+
/** Clear the pending buffer (e.g. on close). */
|
|
37
|
+
reset: () => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* First-character ("type-ahead") search for a collection.
|
|
41
|
+
*
|
|
42
|
+
* Buffers printable keystrokes (resetting after `resetMs` of inactivity),
|
|
43
|
+
* matches items whose label starts with the buffer, and cycles from the
|
|
44
|
+
* current index — so pressing "s" repeatedly walks through the "s" items
|
|
45
|
+
* instead of sticking on the first. Space is reserved for activation and is
|
|
46
|
+
* only treated as typeahead while a buffer is already active.
|
|
47
|
+
*
|
|
48
|
+
* Additive and collection-agnostic: it does not move focus itself; the caller
|
|
49
|
+
* wires `onMatch` to its own focus/selection (e.g. `useListFocus.focusItem`).
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```
|
|
53
|
+
* const {listRef, handleKeyDown, focusItem, getItems} = useListFocus(...);
|
|
54
|
+
* const typeahead = useTypeahead({
|
|
55
|
+
* getItemLabels: () => getItems().map(el => el.textContent),
|
|
56
|
+
* onMatch: focusItem,
|
|
57
|
+
* getCurrentIndex: () => getItems().findIndex(el => el === document.activeElement),
|
|
58
|
+
* });
|
|
59
|
+
* const onKeyDown = (e) => { if (!typeahead.onKeyDown(e)) handleKeyDown(e); };
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function useTypeahead(options: UseTypeaheadOptions): UseTypeaheadReturn;
|
|
63
|
+
//# sourceMappingURL=useTypeahead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTypeahead.d.ts","sourceRoot":"","sources":["../../src/hooks/useTypeahead.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,aAAa,EAAE,MAAM,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAE9D;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,aAAa,KAAK,OAAO,CAAC;IAC/D,gDAAgD;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAkF7E"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTypeahead.ts
|
|
7
|
+
* @input Uses React useCallback/useRef
|
|
8
|
+
* @output Exports useTypeahead hook for first-character (type-to-focus) search
|
|
9
|
+
* @position Core hook; adds APG typeahead to menus, listboxes, and other
|
|
10
|
+
* collections. Composes with useListFocus/useGridFocus (or any collection)
|
|
11
|
+
* via a caller-supplied getItemLabels + onMatch.
|
|
12
|
+
*
|
|
13
|
+
* SYNC: When modified, update:
|
|
14
|
+
* - /packages/core/src/hooks/index.ts
|
|
15
|
+
*/
|
|
16
|
+
import { useCallback, useRef } from 'react';
|
|
17
|
+
/**
|
|
18
|
+
* Whether a key event represents a single printable character (a type-to-focus
|
|
19
|
+
* candidate) rather than a control/navigation key or a shortcut chord.
|
|
20
|
+
*/
|
|
21
|
+
function isPrintableCharacter(e) {
|
|
22
|
+
return e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey &&
|
|
23
|
+
// A lone space is used for activation in menus, not typeahead-from-empty.
|
|
24
|
+
e.key !== ' ';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* First-character ("type-ahead") search for a collection.
|
|
29
|
+
*
|
|
30
|
+
* Buffers printable keystrokes (resetting after `resetMs` of inactivity),
|
|
31
|
+
* matches items whose label starts with the buffer, and cycles from the
|
|
32
|
+
* current index — so pressing "s" repeatedly walks through the "s" items
|
|
33
|
+
* instead of sticking on the first. Space is reserved for activation and is
|
|
34
|
+
* only treated as typeahead while a buffer is already active.
|
|
35
|
+
*
|
|
36
|
+
* Additive and collection-agnostic: it does not move focus itself; the caller
|
|
37
|
+
* wires `onMatch` to its own focus/selection (e.g. `useListFocus.focusItem`).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```
|
|
41
|
+
* const {listRef, handleKeyDown, focusItem, getItems} = useListFocus(...);
|
|
42
|
+
* const typeahead = useTypeahead({
|
|
43
|
+
* getItemLabels: () => getItems().map(el => el.textContent),
|
|
44
|
+
* onMatch: focusItem,
|
|
45
|
+
* getCurrentIndex: () => getItems().findIndex(el => el === document.activeElement),
|
|
46
|
+
* });
|
|
47
|
+
* const onKeyDown = (e) => { if (!typeahead.onKeyDown(e)) handleKeyDown(e); };
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function useTypeahead(options) {
|
|
51
|
+
const {
|
|
52
|
+
getItemLabels,
|
|
53
|
+
onMatch,
|
|
54
|
+
getCurrentIndex,
|
|
55
|
+
resetMs = 750,
|
|
56
|
+
isDisabled
|
|
57
|
+
} = options;
|
|
58
|
+
const bufferRef = useRef('');
|
|
59
|
+
const timeoutRef = useRef(undefined);
|
|
60
|
+
const reset = useCallback(() => {
|
|
61
|
+
bufferRef.current = '';
|
|
62
|
+
if (timeoutRef.current) {
|
|
63
|
+
clearTimeout(timeoutRef.current);
|
|
64
|
+
timeoutRef.current = undefined;
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
const scheduleReset = useCallback(() => {
|
|
68
|
+
if (timeoutRef.current) {
|
|
69
|
+
clearTimeout(timeoutRef.current);
|
|
70
|
+
}
|
|
71
|
+
timeoutRef.current = setTimeout(() => {
|
|
72
|
+
bufferRef.current = '';
|
|
73
|
+
timeoutRef.current = undefined;
|
|
74
|
+
}, resetMs);
|
|
75
|
+
}, [resetMs]);
|
|
76
|
+
const onKeyDown = useCallback(e => {
|
|
77
|
+
// A bare Space with no active buffer is not typeahead (menus activate on
|
|
78
|
+
// Space); once the user is mid-typing, Space extends the query.
|
|
79
|
+
const isSpaceMidType = e.key === ' ' && bufferRef.current.length > 0;
|
|
80
|
+
if (!isPrintableCharacter(e) && !isSpaceMidType) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const labels = getItemLabels();
|
|
84
|
+
if (labels.length === 0) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Pressing the SAME single character repeatedly cycles to the next match
|
|
89
|
+
// rather than filtering deeper (APG behavior).
|
|
90
|
+
const char = e.key.toLowerCase();
|
|
91
|
+
const isRepeatSameChar = bufferRef.current.length > 0 && bufferRef.current.split('').every(c => c === char);
|
|
92
|
+
const nextBuffer = isRepeatSameChar ? char : bufferRef.current + char;
|
|
93
|
+
bufferRef.current = nextBuffer;
|
|
94
|
+
scheduleReset();
|
|
95
|
+
const start = getCurrentIndex?.() ?? -1;
|
|
96
|
+
const count = labels.length;
|
|
97
|
+
// When repeating a single char, start the search AFTER the current item
|
|
98
|
+
// so we advance; otherwise include the current item.
|
|
99
|
+
const offset = isRepeatSameChar ? 1 : 0;
|
|
100
|
+
for (let i = 0; i < count; i++) {
|
|
101
|
+
const index = (start + offset + i + count) % count;
|
|
102
|
+
if (isDisabled?.(index)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const label = labels[index];
|
|
106
|
+
if (label != null && label.trim().toLowerCase().startsWith(nextBuffer)) {
|
|
107
|
+
onMatch(index);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}, [getItemLabels, onMatch, getCurrentIndex, scheduleReset, isDisabled]);
|
|
113
|
+
return {
|
|
114
|
+
onKeyDown,
|
|
115
|
+
reset
|
|
116
|
+
};
|
|
117
|
+
}
|
package/dist/index.d.ts
CHANGED