@astryxdesign/core 0.1.1 → 0.1.2-canary.04e71b2
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 +105 -0
- package/README.md +131 -13
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- 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 +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +53 -17
- 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/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.map +1 -1
- package/dist/Chat/useTriggerMenu.js +4 -1
- 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/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +49 -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 +59 -8
- 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/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- 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 +8 -0
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -3
- package/dist/InputGroup/InputGroup.d.ts +2 -2
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +14 -5
- 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/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 +34 -2
- package/dist/Layer/anchorName.d.ts +20 -0
- package/dist/Layer/anchorName.d.ts.map +1 -0
- package/dist/Layer/anchorName.js +36 -0
- 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 +29 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +55 -11
- 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 +17 -2
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +20 -9
- package/dist/Link/LinkProvider.d.ts +1 -0
- package/dist/Link/LinkProvider.d.ts.map +1 -1
- package/dist/Link/LinkProvider.js +1 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +18 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -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 +25 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +103 -22
- 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 +46 -28
- package/dist/Popover/usePopover.d.ts +25 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +18 -6
- 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 +22 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +84 -23
- 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/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 +38 -11
- 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 +86 -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 +38 -16
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +36 -7
- 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/TableRow.d.ts +6 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +15 -7
- package/dist/Table/index.d.ts +4 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +3 -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/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
- 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 +161 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +1 -1
- package/dist/Text/text.stylex.d.ts +2 -2
- package/dist/Text/text.stylex.js +2 -2
- 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 +53 -10
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- 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 +132 -3
- package/dist/ToggleButton/ToggleButton.d.ts +7 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +34 -57
- package/dist/Tokenizer/Tokenizer.d.ts +12 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +27 -5
- 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 +57 -9
- 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 +9 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +67 -6
- package/dist/Typeahead/Typeahead.d.ts +24 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +29 -5
- 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 +40 -1
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- 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/theme/generateThemeRules.js +1 -1
- package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
- package/dist/theme/syntax/presets.d.ts +1 -1
- package/dist/theme/syntax/presets.d.ts.map +1 -1
- package/dist/theme/syntax/presets.js +1 -1
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.js +1 -1
- package/dist/theme/types.d.ts +1 -1
- 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/package.json +15 -5
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Button/Button.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +76 -21
- 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/ChatComposer.doc.mjs +3 -0
- package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
- 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 +3 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -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 +169 -16
- package/src/CheckboxList/CheckboxList.tsx +56 -3
- package/src/CheckboxList/CheckboxListContext.tsx +7 -0
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +117 -0
- package/src/Citation/Citation.tsx +9 -1
- package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
- package/src/CodeBlock/CodeBlock.test.tsx +115 -0
- package/src/CodeBlock/CodeBlock.tsx +61 -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 +281 -56
- package/src/DateInput/DateInput.test.tsx +188 -2
- package/src/DateInput/DateInput.tsx +89 -10
- 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/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- 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 +14 -4
- package/src/Grid/Grid.tsx +20 -0
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +13 -10
- package/src/Icon/Icon.doc.mjs +8 -0
- package/src/InputGroup/InputGroup.doc.mjs +2 -2
- package/src/InputGroup/InputGroup.test.tsx +98 -60
- package/src/InputGroup/InputGroup.tsx +21 -4
- package/src/InputGroup/InputGroupContext.ts +9 -4
- package/src/InputGroup/index.ts +1 -0
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +37 -12
- package/src/Layer/anchorName.test.ts +146 -0
- package/src/Layer/anchorName.ts +46 -0
- package/src/Layer/layerAnimations.stylex.ts +20 -4
- package/src/Layer/useLayer.doc.mjs +12 -2
- package/src/Layer/useLayer.tsx +100 -15
- package/src/Layout/Layout.doc.mjs +1 -1
- 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 -2
- 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 +52 -2
- package/src/Link/Link.tsx +42 -7
- package/src/Link/LinkProvider.tsx +1 -0
- package/src/List/List.doc.mjs +7 -0
- package/src/Markdown/Markdown.doc.mjs +2 -0
- package/src/Markdown/Markdown.test.tsx +28 -26
- package/src/Markdown/Markdown.tsx +21 -6
- package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +25 -0
- package/src/MultiSelector/MultiSelector.test.tsx +375 -8
- package/src/MultiSelector/MultiSelector.tsx +145 -21
- 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/OverflowList/OverflowList.doc.mjs +17 -0
- package/src/Pagination/Pagination.doc.mjs +3 -3
- package/src/Pagination/Pagination.test.tsx +250 -14
- package/src/Pagination/Pagination.tsx +54 -29
- package/src/Popover/usePopover.doc.mjs +7 -3
- package/src/Popover/usePopover.tsx +49 -6
- 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 +45 -27
- 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/Resizable/Resizable.doc.mjs +1 -1
- package/src/Section/Section.doc.mjs +11 -0
- 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 +15 -0
- package/src/Selector/Selector.doc.mjs +25 -0
- package/src/Selector/Selector.test.tsx +322 -15
- package/src/Selector/Selector.tsx +126 -40
- package/src/Selector/hooks.ts +23 -0
- package/src/SideNav/SideNav.test.tsx +16 -0
- package/src/SideNav/SideNavItem.tsx +2 -4
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Slider/Slider.doc.mjs +19 -0
- package/src/Slider/Slider.test.tsx +158 -18
- package/src/Slider/Slider.tsx +54 -6
- 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 +12 -0
- package/src/TabList/TabList.test.tsx +208 -0
- package/src/TabList/TabList.tsx +102 -12
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +6 -3
- package/src/Table/BaseTable.tsx +72 -41
- package/src/Table/Table.doc.mjs +14 -0
- package/src/Table/Table.test.tsx +39 -30
- package/src/Table/Table.tsx +27 -1
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/TableRow.tsx +37 -7
- package/src/Table/index.ts +6 -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/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +142 -0
- package/src/Table/types.ts +175 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/Table/useTableColumnResize.doc.mjs +96 -0
- package/src/Table/useTableStickyColumns.doc.mjs +59 -0
- package/src/Text/Heading.doc.mjs +15 -2
- package/src/Text/Text.doc.mjs +7 -1
- package/src/Text/text.stylex.ts +2 -2
- 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/TimeInput/TimeInput.doc.mjs +335 -60
- package/src/TimeInput/TimeInput.test.tsx +144 -7
- package/src/TimeInput/TimeInput.tsx +74 -5
- package/src/Timestamp/Timestamp.doc.mjs +9 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +204 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
- package/src/ToggleButton/ToggleButton.test.tsx +39 -18
- package/src/ToggleButton/ToggleButton.tsx +46 -83
- package/src/Tokenizer/Tokenizer.doc.mjs +15 -0
- package/src/Tokenizer/Tokenizer.test.tsx +105 -1
- package/src/Tokenizer/Tokenizer.tsx +41 -2
- package/src/Toolbar/Toolbar.doc.mjs +65 -5
- package/src/Toolbar/Toolbar.test.tsx +109 -6
- package/src/Toolbar/Toolbar.tsx +59 -5
- package/src/Tooltip/Tooltip.test.tsx +81 -0
- package/src/Tooltip/useTooltip.tsx +57 -11
- 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 +86 -9
- package/src/Typeahead/Typeahead.doc.mjs +136 -30
- package/src/Typeahead/Typeahead.test.tsx +331 -1
- package/src/Typeahead/Typeahead.tsx +52 -1
- 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/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/useImageMode.test.ts +133 -0
- 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 +4 -0
- package/src/theme/generateThemeRules.test.ts +7 -8
- package/src/theme/generateThemeRules.ts +1 -1
- package/src/theme/syntax/SyntaxTheme.tsx +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
- package/src/theme/syntax/presets.ts +5 -2
- package/src/theme/syntax/tokens.ts +1 -1
- package/src/theme/types.ts +1 -1
- 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,133 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useImageMode.test.ts
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useImageMode hook
|
|
6
|
+
* @output Unit tests for useImageMode hook
|
|
7
|
+
* @position Testing; validates useImageMode.ts pixel-sampling + CORS behavior
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When useImageMode.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
|
+
import {renderHook, waitFor, act} from '@testing-library/react';
|
|
14
|
+
import {useImageMode} from './useImageMode';
|
|
15
|
+
|
|
16
|
+
// jsdom has no fetch/createImageBitmap/OffscreenCanvas — stub them per test so
|
|
17
|
+
// we can exercise the sampling path and the cross-origin failure path.
|
|
18
|
+
|
|
19
|
+
/** Fill the sampled canvas with a flat RGB so perceptualLightness is deterministic. */
|
|
20
|
+
function stubSamplingPipeline(rgb: [number, number, number]) {
|
|
21
|
+
// fetch resolves to a blob (same-origin / CORS-enabled image)
|
|
22
|
+
vi.stubGlobal(
|
|
23
|
+
'fetch',
|
|
24
|
+
vi.fn(async () => ({blob: async () => ({}) as Blob})),
|
|
25
|
+
);
|
|
26
|
+
vi.stubGlobal(
|
|
27
|
+
'createImageBitmap',
|
|
28
|
+
vi.fn(async () => ({width: 100, height: 100}) as ImageBitmap),
|
|
29
|
+
);
|
|
30
|
+
const [r, g, b] = rgb;
|
|
31
|
+
class FakeOffscreenCanvas {
|
|
32
|
+
constructor(
|
|
33
|
+
public width: number,
|
|
34
|
+
public height: number,
|
|
35
|
+
) {}
|
|
36
|
+
getContext() {
|
|
37
|
+
return {
|
|
38
|
+
drawImage: vi.fn(),
|
|
39
|
+
getImageData: (_x: number, _y: number, w: number, h: number) => {
|
|
40
|
+
const data = new Uint8ClampedArray(w * h * 4);
|
|
41
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
42
|
+
data[i] = r;
|
|
43
|
+
data[i + 1] = g;
|
|
44
|
+
data[i + 2] = b;
|
|
45
|
+
data[i + 3] = 255;
|
|
46
|
+
}
|
|
47
|
+
return {data};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
vi.stubGlobal('OffscreenCanvas', FakeOffscreenCanvas);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Simulate a cross-origin image with no CORS headers: fetch rejects. */
|
|
56
|
+
function stubCorsBlockedFetch() {
|
|
57
|
+
vi.stubGlobal(
|
|
58
|
+
'fetch',
|
|
59
|
+
vi.fn(async () => {
|
|
60
|
+
throw new TypeError(
|
|
61
|
+
'Failed to fetch: No Access-Control-Allow-Origin header',
|
|
62
|
+
);
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
vi.stubGlobal('createImageBitmap', vi.fn());
|
|
66
|
+
vi.stubGlobal('OffscreenCanvas', class {});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('useImageMode', () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
vi.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
vi.unstubAllGlobals();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns the fallback while no src is provided', () => {
|
|
78
|
+
const {result} = renderHook(() => useImageMode(null, {fallback: 'dark'}));
|
|
79
|
+
expect(result.current).toBe('dark');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('samples a dark image and reports "dark"', async () => {
|
|
83
|
+
stubSamplingPipeline([10, 10, 10]);
|
|
84
|
+
const {result} = renderHook(() =>
|
|
85
|
+
useImageMode('data:image/png;base64,AAAA'),
|
|
86
|
+
);
|
|
87
|
+
await waitFor(() => expect(result.current).toBe('dark'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('samples a light image and reports "light"', async () => {
|
|
91
|
+
stubSamplingPipeline([240, 240, 240]);
|
|
92
|
+
const {result} = renderHook(() =>
|
|
93
|
+
useImageMode('data:image/png;base64,AAAA'),
|
|
94
|
+
);
|
|
95
|
+
await waitFor(() => expect(result.current).toBe('light'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── Reproduction for BB-001 (cluster C6) ────────────────────────────────
|
|
99
|
+
// A cross-origin image with no `Access-Control-Allow-Origin` header cannot be
|
|
100
|
+
// fetched with `mode: 'cors'`, so the APCA sampling pipeline never runs and the
|
|
101
|
+
// hook silently stays on its fallback. This is why image-backed Thumbnail
|
|
102
|
+
// overlay/remove-button contrast is unreliable for hosted demo examples that
|
|
103
|
+
// point `src` at a non-CORS CDN URL.
|
|
104
|
+
it('stays on fallback when a cross-origin image cannot be sampled (CORS)', async () => {
|
|
105
|
+
stubCorsBlockedFetch();
|
|
106
|
+
const {result} = renderHook(() =>
|
|
107
|
+
useImageMode(
|
|
108
|
+
'https://lookaside.facebook.com/assets/astryx/moody-scene-vertical-2.png',
|
|
109
|
+
{fallback: null},
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
// Give the rejected fetch + state update a chance to flush.
|
|
113
|
+
await act(async () => {
|
|
114
|
+
await new Promise(r => setTimeout(r, 0));
|
|
115
|
+
});
|
|
116
|
+
expect(result.current).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('requests the image with CORS mode (the constraint that fails for non-CORS hosts)', async () => {
|
|
120
|
+
const fetchSpy = vi.fn(async () => {
|
|
121
|
+
throw new TypeError('CORS');
|
|
122
|
+
});
|
|
123
|
+
vi.stubGlobal('fetch', fetchSpy);
|
|
124
|
+
vi.stubGlobal('createImageBitmap', vi.fn());
|
|
125
|
+
vi.stubGlobal('OffscreenCanvas', class {});
|
|
126
|
+
renderHook(() => useImageMode('https://cross-origin.example/x.png'));
|
|
127
|
+
await waitFor(() => expect(fetchSpy).toHaveBeenCalled());
|
|
128
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
129
|
+
'https://cross-origin.example/x.png',
|
|
130
|
+
{mode: 'cors'},
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useKeyboardHint.test.tsx
|
|
5
|
+
* @input Uses React Testing Library, useKeyboardHint
|
|
6
|
+
* @output Unit tests for keyboard hint rendering
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {render} from '@testing-library/react';
|
|
10
|
+
import {describe, expect, it} from 'vitest';
|
|
11
|
+
import {useKeyboardHint, type KeyboardHintOrientation} from './useKeyboardHint';
|
|
12
|
+
|
|
13
|
+
function TestHint({orientation}: {orientation?: KeyboardHintOrientation}) {
|
|
14
|
+
const {hintElement} = useKeyboardHint({orientation});
|
|
15
|
+
return <div>{hintElement}</div>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getKeyLabels(container: HTMLElement): (string | null)[] {
|
|
19
|
+
return Array.from(container.querySelectorAll('.astryx-kbd')).map(key =>
|
|
20
|
+
key.getAttribute('aria-label'),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getKeyText(container: HTMLElement): string[] {
|
|
25
|
+
return Array.from(container.querySelectorAll('kbd')).map(
|
|
26
|
+
key => key.textContent ?? '',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('useKeyboardHint', () => {
|
|
31
|
+
it('renders horizontal arrow keys with Kbd', () => {
|
|
32
|
+
const {container} = render(<TestHint orientation="horizontal" />);
|
|
33
|
+
|
|
34
|
+
expect(getKeyLabels(container)).toEqual(['Left arrow', 'Right arrow']);
|
|
35
|
+
expect(getKeyText(container)).toEqual(['←', '→']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders vertical arrow keys with Kbd', () => {
|
|
39
|
+
const {container} = render(<TestHint orientation="vertical" />);
|
|
40
|
+
|
|
41
|
+
expect(getKeyLabels(container)).toEqual(['Up arrow', 'Down arrow']);
|
|
42
|
+
expect(getKeyText(container)).toEqual(['↑', '↓']);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders all arrow keys for both-axis navigation', () => {
|
|
46
|
+
const {container} = render(<TestHint orientation="both" />);
|
|
47
|
+
|
|
48
|
+
expect(getKeyLabels(container)).toEqual([
|
|
49
|
+
'Left arrow',
|
|
50
|
+
'Right arrow',
|
|
51
|
+
'Up arrow',
|
|
52
|
+
'Down arrow',
|
|
53
|
+
]);
|
|
54
|
+
expect(getKeyText(container)).toEqual(['←', '→', '↑', '↓']);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('positions the hint farther from the anchor', () => {
|
|
58
|
+
const {container} = render(<TestHint />);
|
|
59
|
+
const hint = container.querySelector('[popover="manual"]') as HTMLElement;
|
|
60
|
+
|
|
61
|
+
expect(hint.style.marginBlockStart).toBe('var(--spacing-2)');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('keeps the hint surface padding after useLayer reset styles', () => {
|
|
65
|
+
const {container} = render(<TestHint />);
|
|
66
|
+
const hint = container.querySelector('[popover="manual"]') as HTMLElement;
|
|
67
|
+
|
|
68
|
+
expect(hint.className).toContain('useKeyboardHint__styles.hint');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useKeyboardHint.tsx
|
|
7
|
+
* @input Uses React, StyleX, Kbd, useLayer
|
|
8
|
+
* @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
|
|
9
|
+
* @position Core hook; shows sighted keyboard users how to navigate composite
|
|
10
|
+
* widgets that use roving tabindex (single Tab stop, arrows inside)
|
|
11
|
+
*
|
|
12
|
+
* SYNC: When modified, update:
|
|
13
|
+
* - /packages/core/src/hooks/index.ts
|
|
14
|
+
* - /packages/core/src/hooks/useKeyboardHint.doc.mjs
|
|
15
|
+
* - /apps/storybook/stories/useKeyboardHint.stories.tsx
|
|
16
|
+
* - /packages/cli/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import React, {useCallback, useEffect, useRef, type ReactNode} from 'react';
|
|
20
|
+
import * as stylex from '@stylexjs/stylex';
|
|
21
|
+
import {
|
|
22
|
+
colorVars,
|
|
23
|
+
radiusVars,
|
|
24
|
+
shadowVars,
|
|
25
|
+
spacingVars,
|
|
26
|
+
typeScaleVars,
|
|
27
|
+
} from '../theme/tokens.stylex';
|
|
28
|
+
import {Kbd} from '../Kbd';
|
|
29
|
+
import {useLayer} from '../Layer/useLayer';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Types
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export type KeyboardHintOrientation = 'horizontal' | 'vertical' | 'both';
|
|
36
|
+
|
|
37
|
+
export interface UseKeyboardHintOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Orientation of the arrow-key navigation. Controls which arrow icons are
|
|
40
|
+
* shown in the hint badge.
|
|
41
|
+
* - `'horizontal'` → ← →
|
|
42
|
+
* - `'vertical'` → ↑ ↓
|
|
43
|
+
* - `'both'` → ← → ↑ ↓
|
|
44
|
+
* @default 'horizontal'
|
|
45
|
+
*/
|
|
46
|
+
orientation?: KeyboardHintOrientation;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Milliseconds before the hint auto-dismisses after appearing.
|
|
50
|
+
* @default 3000
|
|
51
|
+
*/
|
|
52
|
+
dismissAfterMs?: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether the hint is enabled. Set to false to disable for a specific
|
|
56
|
+
* instance (e.g. when the widget is read-only or the user has dismissed
|
|
57
|
+
* globally).
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
isEnabled?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UseKeyboardHintReturn {
|
|
64
|
+
/**
|
|
65
|
+
* The popover hint element to render inside your component tree (portals to
|
|
66
|
+
* top layer via `popover="manual"`). Render unconditionally — it manages its
|
|
67
|
+
* own visibility.
|
|
68
|
+
*/
|
|
69
|
+
hintElement: ReactNode;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Attach to the composite container's `onFocus`. Shows the hint on the first
|
|
73
|
+
* keyboard-focus (`:focus-visible`) entry from outside.
|
|
74
|
+
*/
|
|
75
|
+
onFocus: (e: React.FocusEvent) => void;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Attach to the composite container's `onBlur`. Hides the hint when focus
|
|
79
|
+
* leaves the composite entirely.
|
|
80
|
+
*/
|
|
81
|
+
onBlur: (e: React.FocusEvent) => void;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Attach to the composite container's `onKeyDown`. Dismisses the hint on the
|
|
85
|
+
* first arrow press (the user discovered the interaction).
|
|
86
|
+
*/
|
|
87
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Styles
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']);
|
|
95
|
+
|
|
96
|
+
const ARROW_HINT_KEYS: Record<
|
|
97
|
+
KeyboardHintOrientation,
|
|
98
|
+
ReadonlyArray<string>
|
|
99
|
+
> = {
|
|
100
|
+
horizontal: ['left', 'right'],
|
|
101
|
+
vertical: ['up', 'down'],
|
|
102
|
+
both: ['left', 'right', 'up', 'down'],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const styles = stylex.create({
|
|
106
|
+
hint: {
|
|
107
|
+
// Top layer + anchor positioned
|
|
108
|
+
position: 'fixed',
|
|
109
|
+
inset: 'auto',
|
|
110
|
+
margin: 0,
|
|
111
|
+
border: 'none',
|
|
112
|
+
|
|
113
|
+
// Surface
|
|
114
|
+
backgroundColor: colorVars['--color-background-popover'],
|
|
115
|
+
borderRadius: radiusVars['--radius-element'],
|
|
116
|
+
boxShadow: shadowVars['--shadow-low'],
|
|
117
|
+
paddingBlockStart: spacingVars['--spacing-1'],
|
|
118
|
+
paddingBlockEnd: spacingVars['--spacing-1'],
|
|
119
|
+
paddingInlineStart: spacingVars['--spacing-2'],
|
|
120
|
+
paddingInlineEnd: spacingVars['--spacing-2'],
|
|
121
|
+
|
|
122
|
+
// Typography
|
|
123
|
+
fontSize: typeScaleVars['--text-supporting-size'],
|
|
124
|
+
lineHeight: typeScaleVars['--text-supporting-leading'],
|
|
125
|
+
color: colorVars['--color-text-secondary'],
|
|
126
|
+
whiteSpace: 'nowrap',
|
|
127
|
+
|
|
128
|
+
// Animation
|
|
129
|
+
opacity: {
|
|
130
|
+
default: 0,
|
|
131
|
+
':popover-open': 1,
|
|
132
|
+
},
|
|
133
|
+
transitionProperty: 'opacity, display, overlay',
|
|
134
|
+
transitionDuration: '150ms',
|
|
135
|
+
transitionBehavior: 'allow-discrete',
|
|
136
|
+
|
|
137
|
+
// Don't capture pointer events (hint floats above content)
|
|
138
|
+
pointerEvents: 'none',
|
|
139
|
+
},
|
|
140
|
+
keys: {
|
|
141
|
+
display: 'inline-flex',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
gap: spacingVars['--spacing-1'],
|
|
144
|
+
},
|
|
145
|
+
label: {
|
|
146
|
+
marginInlineStart: spacingVars['--spacing-1'],
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Hook
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Shows an ephemeral visual hint ("← → to navigate") anchored to the focused
|
|
156
|
+
* item when a composite widget first receives keyboard focus. Teaches sighted
|
|
157
|
+
* keyboard users that arrows navigate within the group.
|
|
158
|
+
*
|
|
159
|
+
* The hint renders in the top layer (popover="manual") and is CSS-anchor-
|
|
160
|
+
* positioned to the currently focused element, so it is never clipped by
|
|
161
|
+
* overflow containers. It auto-dismisses on first arrow press, timeout, or
|
|
162
|
+
* blur, and does not re-show for that instance.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```
|
|
166
|
+
* const hint = useKeyboardHint({orientation: 'horizontal'});
|
|
167
|
+
* <div role="toolbar" onFocus={hint.onFocus} onBlur={hint.onBlur} onKeyDown={hint.onKeyDown}>
|
|
168
|
+
* {children}
|
|
169
|
+
* {hint.hintElement}
|
|
170
|
+
* </div>
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function useKeyboardHint(
|
|
174
|
+
options: UseKeyboardHintOptions = {},
|
|
175
|
+
): UseKeyboardHintReturn {
|
|
176
|
+
const {
|
|
177
|
+
orientation = 'horizontal',
|
|
178
|
+
dismissAfterMs = 3000,
|
|
179
|
+
isEnabled = true,
|
|
180
|
+
} = options;
|
|
181
|
+
|
|
182
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
183
|
+
const dismissedRef = useRef(false);
|
|
184
|
+
const isVisibleRef = useRef(false);
|
|
185
|
+
const layerAnchorRef = useRef<(el: HTMLElement | null) => void>(() => {});
|
|
186
|
+
|
|
187
|
+
const clearDismissTimeout = useCallback(() => {
|
|
188
|
+
if (timeoutRef.current) {
|
|
189
|
+
clearTimeout(timeoutRef.current);
|
|
190
|
+
timeoutRef.current = null;
|
|
191
|
+
}
|
|
192
|
+
}, []);
|
|
193
|
+
|
|
194
|
+
const handleLayerShow = useCallback(() => {
|
|
195
|
+
isVisibleRef.current = true;
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const handleLayerHide = useCallback(() => {
|
|
199
|
+
isVisibleRef.current = false;
|
|
200
|
+
clearDismissTimeout();
|
|
201
|
+
layerAnchorRef.current(null);
|
|
202
|
+
}, [clearDismissTimeout]);
|
|
203
|
+
|
|
204
|
+
const layer = useLayer({
|
|
205
|
+
mode: 'context',
|
|
206
|
+
onShow: handleLayerShow,
|
|
207
|
+
onHide: handleLayerHide,
|
|
208
|
+
});
|
|
209
|
+
layerAnchorRef.current = layer.ref;
|
|
210
|
+
|
|
211
|
+
// Hide + mark dismissed (won't re-show for this instance)
|
|
212
|
+
const dismiss = useCallback(() => {
|
|
213
|
+
dismissedRef.current = true;
|
|
214
|
+
clearDismissTimeout();
|
|
215
|
+
layer.hide();
|
|
216
|
+
isVisibleRef.current = false;
|
|
217
|
+
layerAnchorRef.current(null);
|
|
218
|
+
}, [clearDismissTimeout, layer]);
|
|
219
|
+
|
|
220
|
+
// Show the layer anchored to the focused element
|
|
221
|
+
const show = useCallback(
|
|
222
|
+
(anchor: HTMLElement) => {
|
|
223
|
+
if (dismissedRef.current || !isEnabled) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
layerAnchorRef.current(anchor);
|
|
228
|
+
layer.show();
|
|
229
|
+
|
|
230
|
+
clearDismissTimeout();
|
|
231
|
+
timeoutRef.current = setTimeout(() => {
|
|
232
|
+
dismiss();
|
|
233
|
+
}, dismissAfterMs);
|
|
234
|
+
},
|
|
235
|
+
[clearDismissTimeout, dismiss, dismissAfterMs, isEnabled, layer],
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Cleanup on unmount
|
|
239
|
+
useEffect(
|
|
240
|
+
() => () => {
|
|
241
|
+
clearDismissTimeout();
|
|
242
|
+
layerAnchorRef.current(null);
|
|
243
|
+
},
|
|
244
|
+
[clearDismissTimeout],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// --- Handlers ---
|
|
248
|
+
|
|
249
|
+
const onFocus = useCallback(
|
|
250
|
+
(e: React.FocusEvent) => {
|
|
251
|
+
if (dismissedRef.current || !isEnabled) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Only show on keyboard focus (focus-visible)
|
|
255
|
+
const target = e.target as HTMLElement;
|
|
256
|
+
if (!target.matches(':focus-visible')) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Only show when focus enters from outside the container
|
|
260
|
+
const container = e.currentTarget as HTMLElement;
|
|
261
|
+
if (
|
|
262
|
+
e.relatedTarget instanceof Node &&
|
|
263
|
+
container.contains(e.relatedTarget)
|
|
264
|
+
) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
show(target);
|
|
268
|
+
},
|
|
269
|
+
[show, isEnabled],
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const onBlur = useCallback(
|
|
273
|
+
(e: React.FocusEvent) => {
|
|
274
|
+
if (!isVisibleRef.current) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const container = e.currentTarget as HTMLElement;
|
|
278
|
+
// Only dismiss when focus leaves the container entirely
|
|
279
|
+
if (
|
|
280
|
+
e.relatedTarget instanceof Node &&
|
|
281
|
+
container.contains(e.relatedTarget)
|
|
282
|
+
) {
|
|
283
|
+
// Focus moved within — re-anchor to the new target
|
|
284
|
+
if (!dismissedRef.current && e.relatedTarget instanceof HTMLElement) {
|
|
285
|
+
layerAnchorRef.current(e.relatedTarget);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
dismiss();
|
|
290
|
+
},
|
|
291
|
+
[dismiss],
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const onKeyDown = useCallback(
|
|
295
|
+
(e: React.KeyboardEvent) => {
|
|
296
|
+
if (!isVisibleRef.current) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (ARROW_KEYS.has(e.key)) {
|
|
300
|
+
dismiss();
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
[dismiss],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// --- Render the hint element ---
|
|
307
|
+
|
|
308
|
+
const arrowContent = (
|
|
309
|
+
<span {...stylex.props(styles.keys)}>
|
|
310
|
+
{ARROW_HINT_KEYS[orientation].map(key => (
|
|
311
|
+
<Kbd key={key} keys={key} />
|
|
312
|
+
))}
|
|
313
|
+
</span>
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const hintElement = layer.render(
|
|
317
|
+
<span aria-hidden="true">
|
|
318
|
+
{arrowContent}
|
|
319
|
+
<span {...stylex.props(styles.label)}>to navigate</span>
|
|
320
|
+
</span>,
|
|
321
|
+
{
|
|
322
|
+
placement: 'below',
|
|
323
|
+
alignment: 'start',
|
|
324
|
+
xstyle: styles.hint,
|
|
325
|
+
style: {
|
|
326
|
+
marginBlockStart: spacingVars['--spacing-2'],
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return {hintElement, onFocus, onBlur, onKeyDown};
|
|
332
|
+
}
|