@astryxdesign/core 0.4.7 → 0.5.0-canary.055e75f
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 +161 -1
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +45 -20
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +3 -2
- package/dist/AspectRatio/AspectRatio.d.ts +24 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +23 -4
- package/dist/Avatar/Avatar.d.ts +25 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +58 -19
- package/dist/Avatar/index.d.ts +2 -2
- package/dist/Avatar/index.d.ts.map +1 -1
- package/dist/Avatar/index.js +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +8 -4
- package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
- package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +10 -6
- package/dist/Banner/Banner.d.ts +40 -15
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +51 -18
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +17 -4
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -6
- package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +26 -7
- package/dist/Calendar/Calendar.d.ts +3 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +14 -15
- package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
- package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
- package/dist/Calendar/getInitialFocusDate.js +62 -0
- package/dist/Calendar/styles.d.ts +5 -14
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +15 -15
- package/dist/Card/Card.d.ts +6 -9
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +6 -6
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +3 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +3 -3
- package/dist/Chat/ChatMessageList.d.ts +22 -3
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +6 -3
- package/dist/Chat/ChatTokenizedText.d.ts +2 -0
- package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
- package/dist/Chat/ChatTokenizedText.js +11 -6
- package/dist/Chat/useChatComposerTokens.js +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +10 -3
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +15 -4
- package/dist/CodeBlock/highlightRanges.js +1 -1
- package/dist/CodeBlock/highlightStyles.js +13 -13
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +3 -2
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +3 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/DateInput/DateInput.d.ts +43 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +22 -2
- package/dist/DateInput/NativeDateField.d.ts +10 -0
- package/dist/DateInput/NativeDateField.d.ts.map +1 -0
- package/dist/DateInput/NativeDateField.js +494 -0
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +82 -23
- package/dist/DateInput/index.d.ts +1 -1
- package/dist/DateInput/index.d.ts.map +1 -1
- package/dist/DateInput/nativeDateSegments.d.ts +22 -0
- package/dist/DateInput/nativeDateSegments.d.ts.map +1 -0
- package/dist/DateInput/nativeDateSegments.js +111 -0
- package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +406 -12
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/Dialog/Dialog.d.ts +21 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +110 -58
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +9 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +3 -2
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +6 -2
- package/dist/HoverCard/HoverCard.d.ts +6 -0
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +104 -3
- package/dist/HoverCard/useHoverCard.d.ts +19 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +74 -26
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckboxIndicator.js +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -2
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +2 -19
- package/dist/Layer/LayerDepthContext.d.ts +52 -0
- package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
- package/dist/Layer/LayerDepthContext.js +66 -0
- package/dist/Layer/index.d.ts +3 -0
- package/dist/Layer/index.d.ts.map +1 -1
- package/dist/Layer/index.js +4 -0
- package/dist/Layer/layerStack.d.ts +98 -0
- package/dist/Layer/layerStack.d.ts.map +1 -0
- package/dist/Layer/layerStack.js +306 -0
- package/dist/Layer/useLayerDismissal.d.ts +93 -0
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
- package/dist/Layer/useLayerDismissal.js +91 -0
- package/dist/Lightbox/Lightbox.d.ts +1 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +103 -81
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +9 -4
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +38 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +182 -32
- package/dist/MobileNav/MobileNav.d.ts +6 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +61 -36
- package/dist/MultiSelector/MultiSelector.d.ts +32 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +63 -11
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/NavIcon/NavIcon.d.ts.map +1 -1
- package/dist/NavIcon/NavIcon.js +5 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +3 -2
- package/dist/NumberInput/NumberInput.d.ts +6 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +54 -104
- package/dist/NumberInput/numberInputCommit.d.ts +34 -0
- package/dist/NumberInput/numberInputCommit.d.ts.map +1 -0
- package/dist/NumberInput/numberInputCommit.js +71 -0
- package/dist/NumberInput/numberParser.d.ts +64 -0
- package/dist/NumberInput/numberParser.d.ts.map +1 -0
- package/dist/NumberInput/numberParser.js +350 -0
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +3 -2
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +3 -2
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +20 -17
- package/dist/PowerSearch/PowerSearch.d.ts +8 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +19 -6
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +110 -27
- package/dist/PowerSearch/types.d.ts +2 -0
- package/dist/PowerSearch/types.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchSource.js +14 -5
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +18 -5
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +3 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -3
- package/dist/Selector/Selector.d.ts +17 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +81 -14
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +4 -3
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +8 -7
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +4 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +37 -5
- package/dist/Spinner/Spinner.d.ts +3 -3
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +130 -78
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +6 -1
- package/dist/Stepper/Step.d.ts +140 -0
- package/dist/Stepper/Step.d.ts.map +1 -0
- package/dist/Stepper/Step.js +1045 -0
- package/dist/Stepper/StepStatus.d.ts +24 -0
- package/dist/Stepper/StepStatus.d.ts.map +1 -0
- package/dist/Stepper/StepStatus.js +1 -0
- package/dist/Stepper/Stepper.d.ts +93 -0
- package/dist/Stepper/Stepper.d.ts.map +1 -0
- package/dist/Stepper/Stepper.js +184 -0
- package/dist/Stepper/StepperContext.d.ts +41 -0
- package/dist/Stepper/StepperContext.d.ts.map +1 -0
- package/dist/Stepper/StepperContext.js +34 -0
- package/dist/Stepper/index.d.ts +8 -0
- package/dist/Stepper/index.d.ts.map +1 -0
- package/dist/Stepper/index.js +7 -0
- package/dist/Stepper/stepper.stylex.d.ts +15 -0
- package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
- package/dist/Stepper/stepper.stylex.js +20 -0
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +8 -3
- package/dist/TabList/Tab.d.ts +15 -2
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +32 -7
- package/dist/TabList/TabList.d.ts +46 -7
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +353 -23
- package/dist/TabList/TabListContext.d.ts +11 -1
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +11 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +3 -2
- package/dist/TabList/index.d.ts +2 -2
- package/dist/TabList/index.d.ts.map +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +10 -2
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +3 -6
- package/dist/Table/TableRow.d.ts +16 -5
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +16 -5
- package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.js +73 -4
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
- package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
- package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +19 -5
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
- package/dist/Table/types.d.ts +22 -4
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -0
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +6 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +9 -3
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +3 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +5 -3
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +27 -8
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +2 -2
- package/dist/Tokenizer/Tokenizer.d.ts +3 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -0
- package/dist/Tooltip/Tooltip.d.ts +5 -0
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +5 -0
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +39 -24
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +5 -4
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -3
- package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +68 -29
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +3 -2
- package/dist/astryx.css +98 -7
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFocusTrap.d.ts +7 -4
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +60 -95
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +3 -18
- package/dist/hooks/useMergedRefs.d.ts +18 -0
- package/dist/hooks/useMergedRefs.d.ts.map +1 -0
- package/dist/hooks/useMergedRefs.js +25 -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/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +24 -4
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +5 -2
- package/dist/utils/groupItems.d.ts +5 -1
- package/dist/utils/groupItems.d.ts.map +1 -1
- package/dist/utils/groupItems.js +9 -9
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/isApplePlatform.d.ts +18 -0
- package/dist/utils/isApplePlatform.d.ts.map +1 -0
- package/dist/utils/isApplePlatform.js +37 -0
- package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
- package/dist/utils/sharedResizeObserver.js +10 -1
- package/dist/utils/timeParser.d.ts +1 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +26 -12
- package/locales/en.json +154 -10
- package/locales/pseudo.json +108 -0
- package/package.json +8 -3
- package/src/AlertDialog/AlertDialog.doc.mjs +114 -30
- package/src/AlertDialog/AlertDialog.test.tsx +211 -2
- package/src/AlertDialog/AlertDialog.tsx +62 -20
- package/src/AppShell/AppShell.tsx +3 -2
- package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
- package/src/AspectRatio/AspectRatio.test.tsx +57 -10
- package/src/AspectRatio/AspectRatio.tsx +40 -1
- package/src/Avatar/Avatar.doc.mjs +14 -3
- package/src/Avatar/Avatar.test.tsx +97 -0
- package/src/Avatar/Avatar.tsx +125 -52
- package/src/Avatar/index.ts +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
- package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
- package/src/AvatarGroup/AvatarGroup.tsx +15 -6
- package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +10 -4
- package/src/Banner/Banner.doc.mjs +13 -11
- package/src/Banner/Banner.test.tsx +102 -15
- package/src/Banner/Banner.tsx +72 -25
- package/src/BottomSheet/BottomSheet.test.tsx +123 -0
- package/src/BottomSheet/BottomSheet.tsx +17 -4
- package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
- package/src/Button/Button.doc.mjs +24 -1
- package/src/Button/Button.test.tsx +32 -4
- package/src/Button/Button.tsx +12 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
- package/src/ButtonGroup/ButtonGroup.test.tsx +404 -1
- package/src/ButtonGroup/ButtonGroup.tsx +23 -5
- package/src/Calendar/Calendar.doc.mjs +4 -3
- package/src/Calendar/Calendar.test.tsx +103 -0
- package/src/Calendar/Calendar.tsx +19 -16
- package/src/Calendar/getInitialFocusDate.test.ts +94 -0
- package/src/Calendar/getInitialFocusDate.ts +93 -0
- package/src/Calendar/styles.ts +49 -31
- package/src/Card/Card.doc.mjs +10 -7
- package/src/Card/Card.test.tsx +44 -19
- package/src/Card/Card.tsx +12 -32
- package/src/Carousel/Carousel.tsx +3 -2
- package/src/Chat/ChatComposerInput.tsx +5 -1
- package/src/Chat/ChatLayout.tsx +3 -2
- package/src/Chat/ChatMessageList.doc.mjs +9 -1
- package/src/Chat/ChatMessageList.test.tsx +46 -0
- package/src/Chat/ChatMessageList.tsx +28 -4
- package/src/Chat/ChatTokenizedText.tsx +17 -10
- package/src/Chat/useChatComposerTokens.ts +1 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +2 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
- package/src/CheckboxInput/CheckboxInput.tsx +8 -2
- package/src/ClickableCard/ClickableCard.tsx +3 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +16 -4
- package/src/CodeBlock/CodeBlock.test.tsx +37 -0
- package/src/CodeBlock/CodeBlock.tsx +20 -4
- package/src/CodeBlock/highlightRanges.ts +1 -1
- package/src/CodeBlock/highlightStyles.ts +13 -13
- package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
- package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
- package/src/ContextMenu/ContextMenu.tsx +4 -3
- package/src/DateInput/DateInput.doc.mjs +16 -0
- package/src/DateInput/DateInput.tsx +55 -2
- package/src/DateInput/DateInputTouch.test.tsx +146 -32
- package/src/DateInput/NativeDateField.test.tsx +635 -0
- package/src/DateInput/NativeDateField.tsx +641 -0
- package/src/DateInput/TouchDateField.tsx +108 -24
- package/src/DateInput/index.ts +1 -0
- package/src/DateInput/nativeDateSegments.ts +127 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
- package/src/DateTimeInput/DateTimeInput.tsx +556 -24
- package/src/DateTimeInput/index.ts +1 -0
- package/src/Dialog/Dialog.doc.mjs +8 -3
- package/src/Dialog/Dialog.test.tsx +429 -2
- package/src/Dialog/Dialog.tsx +133 -58
- package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
- package/src/Field/Field.doc.mjs +13 -2
- package/src/Field/FieldLabel.test.tsx +29 -0
- package/src/Field/FieldLabel.tsx +7 -0
- package/src/FileInput/FileInput.tsx +3 -2
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -2
- package/src/HoverCard/HoverCard.test.tsx +154 -4
- package/src/HoverCard/HoverCard.tsx +143 -3
- package/src/HoverCard/useHoverCard.tsx +104 -33
- package/src/Indicator/CheckboxIndicator.tsx +14 -8
- package/src/Indicator/Indicator.doc.mjs +2 -0
- package/src/Item/Item.tsx +5 -4
- package/src/Kbd/Kbd.tsx +2 -19
- package/src/Layer/LayerDepthContext.tsx +62 -0
- package/src/Layer/index.ts +9 -0
- package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
- package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
- package/src/Layer/layerStack.ts +362 -0
- package/src/Layer/useLayer.test.tsx +62 -14
- package/src/Layer/useLayerDismissal.test.tsx +469 -0
- package/src/Layer/useLayerDismissal.ts +173 -0
- package/src/Layout/Layout.doc.mjs +2 -2
- package/src/Lightbox/Lightbox.doc.mjs +12 -0
- package/src/Lightbox/Lightbox.tsx +124 -102
- package/src/Link/Link.doc.mjs +39 -0
- package/src/Markdown/Markdown.doc.mjs +5 -0
- package/src/Markdown/Markdown.tsx +9 -4
- package/src/Markdown/incremental.test.ts +16 -2
- package/src/Markdown/index.ts +1 -0
- package/src/Markdown/parser.perf.test.ts +96 -1
- package/src/Markdown/parser.test.ts +178 -2
- package/src/Markdown/parser.ts +253 -32
- package/src/MobileNav/MobileNav.tsx +62 -36
- package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +49 -1
- package/src/MultiSelector/MultiSelector.test.tsx +277 -0
- package/src/MultiSelector/MultiSelector.tsx +142 -11
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +8 -2
- package/src/NavIcon/NavIcon.test.tsx +9 -0
- package/src/NavIcon/NavIcon.tsx +5 -1
- package/src/NavMenu/NavHeadingMenu.tsx +3 -2
- package/src/NumberInput/NumberInput.doc.mjs +31 -7
- package/src/NumberInput/NumberInput.test.tsx +436 -8
- package/src/NumberInput/NumberInput.tsx +62 -114
- package/src/NumberInput/numberInputCommit.test.ts +72 -0
- package/src/NumberInput/numberInputCommit.ts +103 -0
- package/src/NumberInput/numberParser.docblock.test.ts +234 -0
- package/src/NumberInput/numberParser.test.ts +222 -0
- package/src/NumberInput/numberParser.ts +403 -0
- package/src/Outline/Outline.tsx +3 -2
- package/src/OverflowList/OverflowList.tsx +3 -2
- package/src/Overlay/Overlay.tsx +3 -2
- package/src/Pagination/Pagination.test.tsx +3 -2
- package/src/Popover/usePopover.tsx +31 -28
- package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
- package/src/PowerSearch/PowerSearch.test.tsx +240 -1
- package/src/PowerSearch/PowerSearch.tsx +34 -5
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +288 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +111 -1
- package/src/PowerSearch/PowerSearchValueEditor.tsx +124 -10
- package/src/PowerSearch/types.ts +2 -0
- package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
- package/src/PowerSearch/usePowerSearchSource.ts +13 -3
- package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
- package/src/ProgressBar/ProgressBar.test.tsx +24 -0
- package/src/ProgressBar/ProgressBar.tsx +28 -8
- package/src/Resizable/ResizeHandle.tsx +3 -2
- package/src/SegmentedControl/SegmentedControl.tsx +3 -2
- package/src/SelectableCard/SelectableCard.tsx +3 -2
- package/src/Selector/Selector.doc.mjs +26 -1
- package/src/Selector/Selector.test.tsx +311 -1
- package/src/Selector/Selector.tsx +139 -16
- package/src/SideNav/SideNav.doc.mjs +6 -6
- package/src/SideNav/SideNav.tsx +4 -2
- package/src/SideNav/SideNavHeading.tsx +12 -12
- package/src/SideNav/SideNavItem.tsx +4 -2
- package/src/Slider/Slider.test.tsx +106 -1
- package/src/Slider/Slider.tsx +55 -3
- package/src/Spinner/Spinner.test.tsx +147 -1
- package/src/Spinner/Spinner.tsx +120 -101
- package/src/StatusDot/StatusDot.doc.mjs +8 -2
- package/src/StatusDot/StatusDot.test.tsx +9 -0
- package/src/StatusDot/StatusDot.tsx +7 -1
- package/src/Stepper/Step.doc.mjs +98 -0
- package/src/Stepper/Step.tsx +1638 -0
- package/src/Stepper/StepStatus.ts +26 -0
- package/src/Stepper/Stepper.doc.mjs +370 -0
- package/src/Stepper/Stepper.test.tsx +1024 -0
- package/src/Stepper/Stepper.tsx +258 -0
- package/src/Stepper/StepperContext.ts +71 -0
- package/src/Stepper/index.ts +18 -0
- package/src/Stepper/stepper.stylex.ts +19 -0
- package/src/Switch/Switch.doc.mjs +2 -0
- package/src/Switch/Switch.test.tsx +10 -0
- package/src/Switch/Switch.tsx +6 -2
- package/src/TabList/Tab.doc.mjs +13 -1
- package/src/TabList/Tab.tsx +67 -7
- package/src/TabList/TabList.doc.mjs +22 -6
- package/src/TabList/TabList.test.tsx +559 -0
- package/src/TabList/TabList.tsx +490 -22
- package/src/TabList/TabListContext.ts +13 -2
- package/src/TabList/TabMenu.tsx +3 -2
- package/src/TabList/index.ts +6 -2
- package/src/Table/BaseTable.tsx +12 -4
- package/src/Table/Table.doc.mjs +9 -2
- package/src/Table/Table.test.tsx +132 -14
- package/src/Table/Table.tsx +3 -8
- package/src/Table/TableBody.doc.mjs +40 -0
- package/src/Table/TableFooter.doc.mjs +40 -0
- package/src/Table/TableHeader.doc.mjs +40 -0
- package/src/Table/TableRow.tsx +16 -5
- package/src/Table/plugins/filtering/useTableFiltering.test.tsx +44 -6
- package/src/Table/plugins/filtering/useTableFiltering.tsx +100 -4
- package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
- package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
- package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
- package/src/Table/types.ts +22 -4
- package/src/Table/useBaseTablePlugins.ts +5 -0
- package/src/Table/useTableGroupedRows.doc.mjs +5 -4
- package/src/Table/useTablePagination.doc.mjs +7 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableSelection.doc.mjs +31 -0
- package/src/Text/Text.test.tsx +23 -1
- package/src/Text/Text.tsx +6 -2
- package/src/TextArea/TextArea.doc.mjs +8 -2
- package/src/TextArea/TextArea.test.tsx +11 -0
- package/src/TextArea/TextArea.tsx +15 -8
- package/src/TextInput/TextInput.doc.mjs +10 -0
- package/src/TextInput/TextInput.tsx +3 -2
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.tsx +2 -2
- package/src/Timestamp/Timestamp.test.tsx +18 -0
- package/src/Timestamp/Timestamp.tsx +4 -2
- package/src/Toast/Toast.doc.mjs +6 -0
- package/src/Toast/ToastViewport.tsx +17 -1
- package/src/Token/TokenLink.tsx +2 -2
- package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
- package/src/Tokenizer/Tokenizer.tsx +4 -0
- package/src/Toolbar/Toolbar.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.test.tsx +32 -0
- package/src/Tooltip/Tooltip.tsx +5 -0
- package/src/Tooltip/useTooltip.tsx +47 -24
- package/src/TopNav/TopNavHeading.tsx +5 -4
- package/src/TopNav/TopNavMegaMenu.tsx +3 -2
- package/src/TopNav/TopNavMenu.tsx +4 -3
- package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
- package/src/Typeahead/BaseTypeahead.tsx +85 -33
- package/src/Typeahead/Typeahead.doc.mjs +5 -0
- package/src/Typeahead/Typeahead.test.tsx +49 -0
- package/src/Typeahead/Typeahead.tsx +3 -2
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useContainerReveal.doc.mjs +2 -2
- package/src/hooks/useFocusTrap.test.tsx +14 -0
- package/src/hooks/useFocusTrap.ts +62 -111
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
- package/src/hooks/useHotkeys.ts +3 -18
- package/src/hooks/useMergedRefs.doc.mjs +79 -0
- package/src/hooks/useMergedRefs.test.tsx +62 -0
- package/src/hooks/useMergedRefs.ts +36 -0
- package/src/index.ts +1 -0
- package/src/raw-import.d.ts +14 -0
- package/src/theme/MediaTheme.doc.mjs +5 -5
- package/src/theme/derivedVarRegistry.test.ts +19 -3
- package/src/theme/derivedVarRegistry.ts +23 -4
- package/src/theme/generateThemeRules.test.ts +37 -0
- package/src/theme/syntax/tokens.ts +5 -2
- package/src/utils/groupItems.ts +12 -6
- package/src/utils/index.ts +1 -1
- package/src/utils/isApplePlatform.test.ts +61 -0
- package/src/utils/isApplePlatform.ts +37 -0
- package/src/utils/sharedResizeObserver.ts +10 -2
- package/src/utils/timeParser.test.ts +6 -1
- package/src/utils/timeParser.ts +35 -13
- package/src/__tests__/inputWidthContract.test.tsx +0 -69
|
@@ -16,6 +16,8 @@ export const docs = {
|
|
|
16
16
|
theming: {
|
|
17
17
|
targets: [
|
|
18
18
|
{className: 'astryx-tab-list', visualProps: ['size']},
|
|
19
|
+
{className: 'astryx-tab-strip'},
|
|
20
|
+
{className: 'astryx-tab-scroll-button'},
|
|
19
21
|
{className: 'astryx-tab', states: ['selected']},
|
|
20
22
|
{className: 'astryx-tab-indicator', states: ['selected']},
|
|
21
23
|
{className: 'astryx-tab-menu'},
|
|
@@ -26,7 +28,7 @@ export const docs = {
|
|
|
26
28
|
{name: '--_tab-indicator-bottom', description: 'Vertical offset of the selected-tab indicator from the tab bottom edge. A host that draws its own bottom divider (Toolbar) sets this so the indicator sits on the divider instead of above it.', default: '-1px', private: true},
|
|
27
29
|
],
|
|
28
30
|
},
|
|
29
|
-
description: '
|
|
31
|
+
description: 'Tab strip that provides TabListContext (value, onChange, size) to Tab and TabMenu children; a nav landmark, or the WAI-ARIA tabs pattern where role="tablist" asks for it.',
|
|
30
32
|
props: [
|
|
31
33
|
{
|
|
32
34
|
name: 'value',
|
|
@@ -58,10 +60,21 @@ export const docs = {
|
|
|
58
60
|
description: 'Whether to show a bottom border divider under the tab list.',
|
|
59
61
|
default: 'false',
|
|
60
62
|
},
|
|
63
|
+
{
|
|
64
|
+
name: 'role',
|
|
65
|
+
type: 'AriaRole',
|
|
66
|
+
description: "ARIA role for the strip. 'tablist' asks for the WAI-ARIA tabs pattern: role=\"tablist\" / role=\"tab\" and aria-selected, with each tab pointing at the panel it controls via its panelId; only tabs may live in a tablist strip, and an href on a tab is ignored there. Left unset, the strip is a nav landmark marking the current tab with aria-current. Any other value is passed through to the element unchanged.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'overflow',
|
|
70
|
+
type: "'auto' | 'scroll' | 'visible'",
|
|
71
|
+
description: "What happens when the tabs are wider than the strip. 'auto' lets the component choose, which today always scrolls. 'scroll' scrolls the tabs horizontally, with edge fades and arrow affordances for pointers that can hover. 'visible' turns overflow handling off and lets the tabs spill out of the strip. The selected tab is always scrolled back into view.",
|
|
72
|
+
default: "'auto'",
|
|
73
|
+
},
|
|
61
74
|
{
|
|
62
75
|
name: 'children',
|
|
63
76
|
type: 'ReactNode',
|
|
64
|
-
description: 'Tab and TabMenu items to render inside the
|
|
77
|
+
description: 'Tab and TabMenu items to render inside the strip.',
|
|
65
78
|
slotElements: [
|
|
66
79
|
{
|
|
67
80
|
__element: 'Tab',
|
|
@@ -88,8 +101,9 @@ export const docs = {
|
|
|
88
101
|
'TabList provides tab-style navigation for organizing content into categorized sections. Use it to let users switch between related views without leaving the page, with overflow items handled by a built-in "more" menu.',
|
|
89
102
|
bestPractices: [
|
|
90
103
|
{ guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
|
|
91
|
-
{ guidance: true, description: 'Use TabMenu
|
|
104
|
+
{ guidance: true, description: 'Leave overflow handling on: a strip narrower than its tabs scrolls, and the selected tab is kept in view. Use TabMenu when you want a curated group of extra options rather than a scrolling strip.' },
|
|
92
105
|
{ guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
|
|
106
|
+
{ guidance: true, description: 'Reach for role="tablist" when the strip switches panels in place, and give each tab a panelId pointing at the panel it opens: that link is how a screen reader gets from a tab to its content. Leave it off for navigation between views.' },
|
|
93
107
|
{ guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
|
|
94
108
|
{ guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
|
|
95
109
|
{ guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
|
|
@@ -109,8 +123,9 @@ export const docsZh = {
|
|
|
109
123
|
'TabList provides tab-style navigation for organizing content into categorized sections. Use it to let users switch between related views without leaving the page, with overflow items handled by a built-in "more" menu.',
|
|
110
124
|
bestPractices: [
|
|
111
125
|
{ guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
|
|
112
|
-
{ guidance: true, description: 'Use TabMenu
|
|
126
|
+
{ guidance: true, description: 'Leave overflow handling on: a strip narrower than its tabs scrolls, and the selected tab is kept in view. Use TabMenu when you want a curated group of extra options rather than a scrolling strip.' },
|
|
113
127
|
{ guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
|
|
128
|
+
{ guidance: true, description: 'Reach for role="tablist" when the strip switches panels in place, and give each tab a panelId pointing at the panel it opens: that link is how a screen reader gets from a tab to its content. Leave it off for navigation between views.' },
|
|
114
129
|
{ guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
|
|
115
130
|
{ guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
|
|
116
131
|
{ guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
|
|
@@ -125,14 +140,15 @@ export const docsZh = {
|
|
|
125
140
|
|
|
126
141
|
/** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
|
|
127
142
|
export const docsDense = {
|
|
128
|
-
description: 'Tab
|
|
143
|
+
description: 'Tab strip w/ overflow scrolling; nav landmark by default, WAI-ARIA tabs pattern under role="tablist".',
|
|
129
144
|
usage: {
|
|
130
145
|
description:
|
|
131
146
|
'TabList provides tab-style navigation for organizing content into categorized sections. Use it to let users switch between related views without leaving the page, with overflow items handled by a built-in "more" menu.',
|
|
132
147
|
bestPractices: [
|
|
133
148
|
{ guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
|
|
134
|
-
{ guidance: true, description: 'Use TabMenu
|
|
149
|
+
{ guidance: true, description: 'Leave overflow handling on: a strip narrower than its tabs scrolls, and the selected tab is kept in view. Use TabMenu when you want a curated group of extra options rather than a scrolling strip.' },
|
|
135
150
|
{ guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
|
|
151
|
+
{ guidance: true, description: 'Reach for role="tablist" when the strip switches panels in place, and give each tab a panelId pointing at the panel it opens: that link is how a screen reader gets from a tab to its content. Leave it off for navigation between views.' },
|
|
136
152
|
{ guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
|
|
137
153
|
{ guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
|
|
138
154
|
{ guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
|
|
@@ -13,6 +13,7 @@ import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
|
|
|
13
13
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {TabList} from './TabList';
|
|
16
|
+
import type {TabListProps} from './TabList';
|
|
16
17
|
import {Tab} from './Tab';
|
|
17
18
|
import {TabMenu} from './TabMenu';
|
|
18
19
|
import {LinkProvider} from '../Link/LinkProvider';
|
|
@@ -960,3 +961,561 @@ describe('TabMenu keyboard navigation (roving tabindex)', () => {
|
|
|
960
961
|
expect(hidePopover).toHaveBeenCalled();
|
|
961
962
|
});
|
|
962
963
|
});
|
|
964
|
+
|
|
965
|
+
describe('TabList overflow (scroll)', () => {
|
|
966
|
+
const STRIP = '.astryx-tab-strip';
|
|
967
|
+
const ARROW = '.astryx-tab-scroll-button';
|
|
968
|
+
|
|
969
|
+
/** jsdom has no layout, so the scroll box is described by hand. */
|
|
970
|
+
function fakeScrollBox(
|
|
971
|
+
strip: HTMLElement,
|
|
972
|
+
{
|
|
973
|
+
scrollWidth,
|
|
974
|
+
clientWidth,
|
|
975
|
+
scrollLeft = 0,
|
|
976
|
+
}: {scrollWidth: number; clientWidth: number; scrollLeft?: number},
|
|
977
|
+
) {
|
|
978
|
+
Object.defineProperty(strip, 'scrollWidth', {
|
|
979
|
+
value: scrollWidth,
|
|
980
|
+
configurable: true,
|
|
981
|
+
});
|
|
982
|
+
Object.defineProperty(strip, 'clientWidth', {
|
|
983
|
+
value: clientWidth,
|
|
984
|
+
configurable: true,
|
|
985
|
+
});
|
|
986
|
+
Object.defineProperty(strip, 'scrollLeft', {
|
|
987
|
+
value: scrollLeft,
|
|
988
|
+
writable: true,
|
|
989
|
+
configurable: true,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Places the strip at 0..width and a tab at the given offsets, so the
|
|
995
|
+
* keep-selected-visible arithmetic has real numbers to work with.
|
|
996
|
+
*/
|
|
997
|
+
function fakeGeometry(
|
|
998
|
+
strip: HTMLElement,
|
|
999
|
+
tabValue: string,
|
|
1000
|
+
{
|
|
1001
|
+
stripWidth,
|
|
1002
|
+
tabLeft,
|
|
1003
|
+
tabRight,
|
|
1004
|
+
}: {stripWidth: number; tabLeft: number; tabRight: number},
|
|
1005
|
+
) {
|
|
1006
|
+
strip.getBoundingClientRect = () =>
|
|
1007
|
+
({left: 0, right: stripWidth, width: stripWidth}) as DOMRect;
|
|
1008
|
+
const tab = strip.querySelector<HTMLElement>(
|
|
1009
|
+
`[data-tab-value="${tabValue}"]`,
|
|
1010
|
+
);
|
|
1011
|
+
if (tab) {
|
|
1012
|
+
tab.getBoundingClientRect = () =>
|
|
1013
|
+
({
|
|
1014
|
+
left: tabLeft,
|
|
1015
|
+
right: tabRight,
|
|
1016
|
+
width: tabRight - tabLeft,
|
|
1017
|
+
}) as DOMRect;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function renderStrip(props: Partial<TabListProps> = {}) {
|
|
1022
|
+
const utils = render(
|
|
1023
|
+
<TabList value="a" onChange={() => {}} {...props}>
|
|
1024
|
+
<Tab value="a" label="Alpha" />
|
|
1025
|
+
<Tab value="b" label="Beta" />
|
|
1026
|
+
<Tab value="c" label="Gamma" />
|
|
1027
|
+
</TabList>,
|
|
1028
|
+
);
|
|
1029
|
+
const strip = utils.container.querySelector<HTMLElement>(STRIP);
|
|
1030
|
+
if (!strip) {
|
|
1031
|
+
throw new Error('no tab strip');
|
|
1032
|
+
}
|
|
1033
|
+
return {...utils, strip};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
it('renders the tabs inside a scroll strip by default', () => {
|
|
1037
|
+
const {strip} = renderStrip();
|
|
1038
|
+
expect(strip.querySelectorAll('[data-tab-value]')).toHaveLength(3);
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
it('offers an end arrow while there are tabs past the end, and pressing it scrolls', () => {
|
|
1042
|
+
const {container, strip} = renderStrip();
|
|
1043
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300});
|
|
1044
|
+
const scrollBy = vi.fn();
|
|
1045
|
+
strip.scrollBy = scrollBy;
|
|
1046
|
+
fireEvent.scroll(strip);
|
|
1047
|
+
|
|
1048
|
+
const arrows = container.querySelectorAll(ARROW);
|
|
1049
|
+
expect(arrows).toHaveLength(1);
|
|
1050
|
+
|
|
1051
|
+
fireEvent.click(arrows[0]);
|
|
1052
|
+
expect(scrollBy).toHaveBeenCalledWith({left: 240});
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('offers both arrows once the strip is scrolled away from the start', () => {
|
|
1056
|
+
const {container, strip} = renderStrip();
|
|
1057
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300, scrollLeft: 150});
|
|
1058
|
+
strip.scrollBy = vi.fn();
|
|
1059
|
+
fireEvent.scroll(strip);
|
|
1060
|
+
|
|
1061
|
+
expect(container.querySelectorAll(ARROW)).toHaveLength(2);
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
it('keeps the arrows out of the tab order and out of the accessibility tree', () => {
|
|
1065
|
+
const {container, strip} = renderStrip();
|
|
1066
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300});
|
|
1067
|
+
strip.scrollBy = vi.fn();
|
|
1068
|
+
fireEvent.scroll(strip);
|
|
1069
|
+
|
|
1070
|
+
const arrow = container.querySelector<HTMLElement>(ARROW);
|
|
1071
|
+
expect(arrow?.getAttribute('aria-hidden')).toBe('true');
|
|
1072
|
+
expect(arrow?.tabIndex).toBe(-1);
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it('scrolls a selected tab that starts out of view back into view on mount', () => {
|
|
1076
|
+
const scrollBy = vi.fn();
|
|
1077
|
+
// The strip mounts before the effect runs, so the geometry has to be in
|
|
1078
|
+
// place on the prototype rather than on an element we can reach first.
|
|
1079
|
+
const originalScrollBy = Element.prototype.scrollBy;
|
|
1080
|
+
const originalRect = Element.prototype.getBoundingClientRect;
|
|
1081
|
+
Element.prototype.scrollBy = scrollBy;
|
|
1082
|
+
Element.prototype.getBoundingClientRect = function (this: Element) {
|
|
1083
|
+
return (
|
|
1084
|
+
this.classList.contains('astryx-tab-strip')
|
|
1085
|
+
? {left: 0, right: 300, width: 300}
|
|
1086
|
+
: {left: 420, right: 500, width: 80}
|
|
1087
|
+
) as DOMRect;
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
render(
|
|
1091
|
+
<TabList value="c" onChange={() => {}}>
|
|
1092
|
+
<Tab value="a" label="Alpha" />
|
|
1093
|
+
<Tab value="b" label="Beta" />
|
|
1094
|
+
<Tab value="c" label="Gamma" />
|
|
1095
|
+
</TabList>,
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
expect(scrollBy).toHaveBeenCalledWith(
|
|
1099
|
+
expect.objectContaining({behavior: 'instant'}),
|
|
1100
|
+
);
|
|
1101
|
+
expect(scrollBy.mock.calls[0][0].left).toBeGreaterThan(0);
|
|
1102
|
+
Element.prototype.scrollBy = originalScrollBy;
|
|
1103
|
+
Element.prototype.getBoundingClientRect = originalRect;
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
it('keeps the selected tab in view when the host changes value without focus', () => {
|
|
1107
|
+
const {strip, rerender} = renderStrip({value: 'a'});
|
|
1108
|
+
const scrollBy = vi.fn();
|
|
1109
|
+
strip.scrollBy = scrollBy;
|
|
1110
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: 420, tabRight: 500});
|
|
1111
|
+
|
|
1112
|
+
rerender(
|
|
1113
|
+
<TabList value="c" onChange={() => {}}>
|
|
1114
|
+
<Tab value="a" label="Alpha" />
|
|
1115
|
+
<Tab value="b" label="Beta" />
|
|
1116
|
+
<Tab value="c" label="Gamma" />
|
|
1117
|
+
</TabList>,
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
expect(scrollBy).toHaveBeenCalledTimes(1);
|
|
1121
|
+
expect(scrollBy.mock.calls[0][0].left).toBeGreaterThan(0);
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it('leaves a tab that is already in view alone', () => {
|
|
1125
|
+
const {strip, rerender} = renderStrip({value: 'a'});
|
|
1126
|
+
const scrollBy = vi.fn();
|
|
1127
|
+
strip.scrollBy = scrollBy;
|
|
1128
|
+
fakeGeometry(strip, 'b', {stripWidth: 300, tabLeft: 80, tabRight: 140});
|
|
1129
|
+
|
|
1130
|
+
rerender(
|
|
1131
|
+
<TabList value="b" onChange={() => {}}>
|
|
1132
|
+
<Tab value="a" label="Alpha" />
|
|
1133
|
+
<Tab value="b" label="Beta" />
|
|
1134
|
+
<Tab value="c" label="Gamma" />
|
|
1135
|
+
</TabList>,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
expect(scrollBy).not.toHaveBeenCalled();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('reveals on a selection change, not on an unrelated overflow change', () => {
|
|
1142
|
+
const {strip, rerender} = renderStrip({value: 'c', overflow: 'visible'});
|
|
1143
|
+
const scrollBy = vi.fn();
|
|
1144
|
+
strip.scrollBy = scrollBy;
|
|
1145
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: 420, tabRight: 500});
|
|
1146
|
+
|
|
1147
|
+
// Turning scrolling on re-measures the strip, and that one reveal is the
|
|
1148
|
+
// resize path's. The selection has not moved, so nothing may add a second.
|
|
1149
|
+
rerender(
|
|
1150
|
+
<TabList value="c" onChange={() => {}} overflow="scroll">
|
|
1151
|
+
<Tab value="a" label="Alpha" />
|
|
1152
|
+
<Tab value="b" label="Beta" />
|
|
1153
|
+
<Tab value="c" label="Gamma" />
|
|
1154
|
+
</TabList>,
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
expect(scrollBy).toHaveBeenCalledTimes(1);
|
|
1158
|
+
|
|
1159
|
+
fakeGeometry(strip, 'a', {stripWidth: 300, tabLeft: -200, tabRight: -120});
|
|
1160
|
+
rerender(
|
|
1161
|
+
<TabList value="a" onChange={() => {}} overflow="scroll">
|
|
1162
|
+
<Tab value="a" label="Alpha" />
|
|
1163
|
+
<Tab value="b" label="Beta" />
|
|
1164
|
+
<Tab value="c" label="Gamma" />
|
|
1165
|
+
</TabList>,
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
expect(scrollBy).toHaveBeenCalledTimes(2);
|
|
1169
|
+
expect(scrollBy.mock.calls[1][0].left).toBeLessThan(0);
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
it('finishes the job on focus: a tab only half in view is scrolled clear', () => {
|
|
1173
|
+
const {strip} = renderStrip({value: 'a'});
|
|
1174
|
+
const scrollBy = vi.fn();
|
|
1175
|
+
strip.scrollBy = scrollBy;
|
|
1176
|
+
// The browser scrolls a focused tab into view only when it is entirely
|
|
1177
|
+
// outside the box, so this one — straddling the end edge — stays cut off.
|
|
1178
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: 260, tabRight: 340});
|
|
1179
|
+
|
|
1180
|
+
const tab = strip.querySelector<HTMLElement>('[data-tab-value="c"]');
|
|
1181
|
+
fireEvent.focus(tab!, {bubbles: true});
|
|
1182
|
+
|
|
1183
|
+
expect(scrollBy).toHaveBeenCalledTimes(1);
|
|
1184
|
+
expect(scrollBy.mock.calls[0][0].left).toBeCloseTo(40);
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
it('reveals a half-visible overflow menu trigger too, not just tabs', () => {
|
|
1188
|
+
const {container} = render(
|
|
1189
|
+
<TabList value="a" onChange={() => {}}>
|
|
1190
|
+
<Tab value="a" label="Alpha" />
|
|
1191
|
+
<Tab value="b" label="Beta" />
|
|
1192
|
+
<TabMenu label="More" options={[{value: 'c', label: 'Gamma'}]} />
|
|
1193
|
+
</TabList>,
|
|
1194
|
+
);
|
|
1195
|
+
const strip = container.querySelector<HTMLElement>(STRIP);
|
|
1196
|
+
if (!strip) {
|
|
1197
|
+
throw new Error('no tab strip');
|
|
1198
|
+
}
|
|
1199
|
+
const scrollBy = vi.fn();
|
|
1200
|
+
strip.scrollBy = scrollBy;
|
|
1201
|
+
strip.getBoundingClientRect = () =>
|
|
1202
|
+
({left: 0, right: 300, width: 300}) as DOMRect;
|
|
1203
|
+
const trigger = strip.querySelector<HTMLElement>('[data-tab-menu]');
|
|
1204
|
+
if (!trigger) {
|
|
1205
|
+
throw new Error('no menu trigger');
|
|
1206
|
+
}
|
|
1207
|
+
trigger.getBoundingClientRect = () =>
|
|
1208
|
+
({left: 260, right: 340, width: 80}) as DOMRect;
|
|
1209
|
+
|
|
1210
|
+
fireEvent.focus(trigger, {bubbles: true});
|
|
1211
|
+
|
|
1212
|
+
expect(scrollBy).toHaveBeenCalledTimes(1);
|
|
1213
|
+
expect(scrollBy.mock.calls[0][0].left).toBeCloseTo(40);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
it('fades out at the strip’s own edge, not at the bleed edge', () => {
|
|
1217
|
+
// The scroll box is a ring-bleed wider than the TabList on each side while
|
|
1218
|
+
// it holds focus, so a fade that ran to the box edge would paint tabs
|
|
1219
|
+
// outside the component and past the arrow that caps that edge.
|
|
1220
|
+
const bleed = 'var(--_tab-strip-bleed)';
|
|
1221
|
+
const {strip} = renderStrip();
|
|
1222
|
+
strip.scrollBy = vi.fn();
|
|
1223
|
+
|
|
1224
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300});
|
|
1225
|
+
fireEvent.scroll(strip);
|
|
1226
|
+
expect(getComputedStyle(strip).maskImage).toContain(`transparent ${bleed}`);
|
|
1227
|
+
|
|
1228
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300, scrollLeft: 150});
|
|
1229
|
+
fireEvent.scroll(strip);
|
|
1230
|
+
expect(getComputedStyle(strip).maskImage).toContain(
|
|
1231
|
+
`transparent calc(100% - ${bleed})`,
|
|
1232
|
+
);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it('is no wider than the TabList until the strip holds focus', () => {
|
|
1236
|
+
// The bleed is real geometry: while the strip carries it, its border box
|
|
1237
|
+
// sticks out of the TabList's and every ancestor that scrolls counts it.
|
|
1238
|
+
// jsdom has no layout, so the box is read off the declarations: padding
|
|
1239
|
+
// and its cancelling margin are both the bleed, and the bleed is zero
|
|
1240
|
+
// until a ring needs keeping whole. `:has(:focus-visible)` flips it --
|
|
1241
|
+
// covered in a browser, since jsdom does not recompute style on a focus
|
|
1242
|
+
// change.
|
|
1243
|
+
const {strip} = renderStrip();
|
|
1244
|
+
const cs = getComputedStyle(strip);
|
|
1245
|
+
|
|
1246
|
+
expect(cs.getPropertyValue('--_tab-strip-bleed')).toBe('0px');
|
|
1247
|
+
expect(cs.paddingInline).toBe('var(--_tab-strip-bleed)');
|
|
1248
|
+
expect(cs.marginInline).toBe('calc(-1 * (var(--_tab-strip-bleed)))');
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
it('does not hand focus to an arrow, which is hidden from assistive tech', () => {
|
|
1252
|
+
const {container, strip} = renderStrip();
|
|
1253
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300});
|
|
1254
|
+
strip.scrollBy = vi.fn();
|
|
1255
|
+
fireEvent.scroll(strip);
|
|
1256
|
+
|
|
1257
|
+
const arrow = container.querySelector<HTMLElement>(ARROW);
|
|
1258
|
+
const prevented = !fireEvent.mouseDown(arrow!);
|
|
1259
|
+
expect(prevented).toBe(true);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it('overflow="visible" scrolls nothing and offers no arrows', () => {
|
|
1263
|
+
const {container, strip, rerender} = renderStrip({
|
|
1264
|
+
value: 'a',
|
|
1265
|
+
overflow: 'visible',
|
|
1266
|
+
});
|
|
1267
|
+
const scrollBy = vi.fn();
|
|
1268
|
+
strip.scrollBy = scrollBy;
|
|
1269
|
+
fakeScrollBox(strip, {scrollWidth: 600, clientWidth: 300});
|
|
1270
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: 420, tabRight: 500});
|
|
1271
|
+
fireEvent.scroll(strip);
|
|
1272
|
+
|
|
1273
|
+
rerender(
|
|
1274
|
+
<TabList value="c" onChange={() => {}} overflow="visible">
|
|
1275
|
+
<Tab value="a" label="Alpha" />
|
|
1276
|
+
<Tab value="b" label="Beta" />
|
|
1277
|
+
<Tab value="c" label="Gamma" />
|
|
1278
|
+
</TabList>,
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
expect(container.querySelectorAll(ARROW)).toHaveLength(0);
|
|
1282
|
+
expect(scrollBy).not.toHaveBeenCalled();
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it('shows the reading start of a stop too wide to fit', () => {
|
|
1286
|
+
const {strip} = renderStrip({value: 'a'});
|
|
1287
|
+
const scrollBy = vi.fn();
|
|
1288
|
+
strip.scrollBy = scrollBy;
|
|
1289
|
+
// Wider than the strip, so one end stays hidden either way. Aligning the
|
|
1290
|
+
// end would land a long label on its last words.
|
|
1291
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: 420, tabRight: 800});
|
|
1292
|
+
|
|
1293
|
+
const tab = strip.querySelector<HTMLElement>('[data-tab-value="c"]');
|
|
1294
|
+
fireEvent.focus(tab!, {bubbles: true});
|
|
1295
|
+
|
|
1296
|
+
expect(scrollBy.mock.calls[0][0].left).toBeCloseTo(420);
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
it('shows the reading start of an over-wide stop in RTL too', () => {
|
|
1300
|
+
const {strip} = renderStrip({value: 'a'});
|
|
1301
|
+
const scrollBy = vi.fn();
|
|
1302
|
+
strip.scrollBy = scrollBy;
|
|
1303
|
+
strip.style.direction = 'rtl';
|
|
1304
|
+
fakeGeometry(strip, 'c', {stripWidth: 300, tabLeft: -500, tabRight: -120});
|
|
1305
|
+
|
|
1306
|
+
const tab = strip.querySelector<HTMLElement>('[data-tab-value="c"]');
|
|
1307
|
+
fireEvent.focus(tab!, {bubbles: true});
|
|
1308
|
+
|
|
1309
|
+
// RTL reads from the right edge, so that is the end to bring flush.
|
|
1310
|
+
expect(scrollBy.mock.calls[0][0].left).toBeCloseTo(-420);
|
|
1311
|
+
});
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
describe('TabList ARIA pattern — role="tablist"', () => {
|
|
1315
|
+
function warnSpy() {
|
|
1316
|
+
return vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
it('speaks the tabs pattern: a labelled tablist of tabs with aria-selected', () => {
|
|
1320
|
+
render(
|
|
1321
|
+
<TabList value="b" onChange={() => {}} role="tablist" aria-label="Views">
|
|
1322
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1323
|
+
<Tab value="b" label="Beta" panelId="panel-b" />
|
|
1324
|
+
</TabList>,
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
const tablist = screen.getByRole('tablist', {name: 'Views'});
|
|
1328
|
+
const tabs = screen.getAllByRole('tab');
|
|
1329
|
+
expect(tabs).toHaveLength(2);
|
|
1330
|
+
expect(tabs.every(tab => tablist.contains(tab))).toBe(true);
|
|
1331
|
+
expect(tabs[0]).toHaveAttribute('aria-selected', 'false');
|
|
1332
|
+
expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
|
|
1333
|
+
expect(tabs[1]).toHaveAttribute('aria-controls', 'panel-b');
|
|
1334
|
+
expect(tabs[1]).not.toHaveAttribute('aria-current');
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
it('lets the consumer name the tablist from another element', () => {
|
|
1338
|
+
render(
|
|
1339
|
+
<>
|
|
1340
|
+
<h2 id="views-heading">Project views</h2>
|
|
1341
|
+
<TabList
|
|
1342
|
+
value="a"
|
|
1343
|
+
onChange={() => {}}
|
|
1344
|
+
role="tablist"
|
|
1345
|
+
aria-labelledby="views-heading">
|
|
1346
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1347
|
+
</TabList>
|
|
1348
|
+
</>,
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
expect(screen.getByRole('tablist', {name: 'Project views'})).not.toBeNull();
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
it('renders no navigation landmark around the tabs', () => {
|
|
1355
|
+
const {container} = render(
|
|
1356
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1357
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1358
|
+
</TabList>,
|
|
1359
|
+
);
|
|
1360
|
+
|
|
1361
|
+
expect(container.querySelector('nav')).toBeNull();
|
|
1362
|
+
expect(screen.queryByRole('navigation')).toBeNull();
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
it('omits aria-controls when a tab has no panel, and says so', () => {
|
|
1366
|
+
const warn = warnSpy();
|
|
1367
|
+
render(
|
|
1368
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1369
|
+
<Tab value="a" label="Alpha" />
|
|
1370
|
+
</TabList>,
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
expect(screen.getByRole('tab')).not.toHaveAttribute('aria-controls');
|
|
1374
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1375
|
+
expect.stringContaining(
|
|
1376
|
+
'Tab: a tab in a role="tablist" TabList controls nothing',
|
|
1377
|
+
),
|
|
1378
|
+
);
|
|
1379
|
+
warn.mockRestore();
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
it('leaves a hand-wired aria-controls alone, and does not ask for a panelId it already has', () => {
|
|
1383
|
+
const warn = warnSpy();
|
|
1384
|
+
render(
|
|
1385
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1386
|
+
<Tab value="a" label="Alpha" aria-controls="panel-written-by-hand" />
|
|
1387
|
+
</TabList>,
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
expect(screen.getByRole('tab')).toHaveAttribute(
|
|
1391
|
+
'aria-controls',
|
|
1392
|
+
'panel-written-by-hand',
|
|
1393
|
+
);
|
|
1394
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1395
|
+
warn.mockRestore();
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
it('ignores href: the tab is a button, and the caller is told', () => {
|
|
1399
|
+
const warn = warnSpy();
|
|
1400
|
+
render(
|
|
1401
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1402
|
+
<Tab value="a" label="Alpha" panelId="panel-a" href="/alpha" />
|
|
1403
|
+
</TabList>,
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
const tab = screen.getByRole('tab');
|
|
1407
|
+
expect(tab.tagName).toBe('BUTTON');
|
|
1408
|
+
expect(tab).not.toHaveAttribute('href');
|
|
1409
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1410
|
+
expect.stringContaining('Tab: href is ignored in a role="tablist"'),
|
|
1411
|
+
);
|
|
1412
|
+
warn.mockRestore();
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
it('warns about anything in the strip that is not a tab, however it got there', () => {
|
|
1416
|
+
const warn = warnSpy();
|
|
1417
|
+
const showMenu = true;
|
|
1418
|
+
render(
|
|
1419
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1420
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1421
|
+
{showMenu ? (
|
|
1422
|
+
<div>
|
|
1423
|
+
<TabMenu label="More" options={[{value: 'b', label: 'Beta'}]} />
|
|
1424
|
+
</div>
|
|
1425
|
+
) : null}
|
|
1426
|
+
</TabList>,
|
|
1427
|
+
);
|
|
1428
|
+
|
|
1429
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1430
|
+
expect.stringContaining('TabList: role="tablist" owns only tabs'),
|
|
1431
|
+
);
|
|
1432
|
+
warn.mockRestore();
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
it('says nothing when the strip holds only tabs with panels', () => {
|
|
1436
|
+
const warn = warnSpy();
|
|
1437
|
+
render(
|
|
1438
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1439
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1440
|
+
<Tab value="b" label="Beta" panelId="panel-b" />
|
|
1441
|
+
</TabList>,
|
|
1442
|
+
);
|
|
1443
|
+
|
|
1444
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1445
|
+
warn.mockRestore();
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
it('leaves ArrowDown and ArrowUp to the page', async () => {
|
|
1449
|
+
// A tablist reports itself as horizontal, so the vertical arrows are the
|
|
1450
|
+
// page's to scroll with.
|
|
1451
|
+
const user = userEvent.setup();
|
|
1452
|
+
render(
|
|
1453
|
+
<TabList value="a" onChange={() => {}} role="tablist">
|
|
1454
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1455
|
+
<Tab value="b" label="Beta" panelId="panel-b" />
|
|
1456
|
+
</TabList>,
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
const alpha = screen.getByRole('tab', {name: 'Alpha'});
|
|
1460
|
+
alpha.focus();
|
|
1461
|
+
await user.keyboard('{ArrowDown}');
|
|
1462
|
+
expect(alpha).toHaveFocus();
|
|
1463
|
+
});
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
describe('TabList ARIA pattern — no role', () => {
|
|
1467
|
+
it('is the navigation landmark it has always been', () => {
|
|
1468
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1469
|
+
render(
|
|
1470
|
+
<TabList value="a" onChange={() => {}} aria-label="Views">
|
|
1471
|
+
<Tab value="a" label="Alpha" />
|
|
1472
|
+
<Tab value="b" label="Beta" />
|
|
1473
|
+
</TabList>,
|
|
1474
|
+
);
|
|
1475
|
+
|
|
1476
|
+
expect(screen.getByRole('navigation', {name: 'Views'})).toBeInTheDocument();
|
|
1477
|
+
expect(screen.queryByRole('tablist')).toBeNull();
|
|
1478
|
+
expect(screen.getByRole('button', {name: 'Alpha'})).toHaveAttribute(
|
|
1479
|
+
'aria-current',
|
|
1480
|
+
'true',
|
|
1481
|
+
);
|
|
1482
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1483
|
+
warn.mockRestore();
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
it('drops a panelId and says so: there is no panel relationship to state', () => {
|
|
1487
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1488
|
+
render(
|
|
1489
|
+
<TabList value="a" onChange={() => {}}>
|
|
1490
|
+
<Tab value="a" label="Alpha" panelId="panel-a" />
|
|
1491
|
+
</TabList>,
|
|
1492
|
+
);
|
|
1493
|
+
|
|
1494
|
+
expect(screen.getByRole('button', {name: 'Alpha'})).not.toHaveAttribute(
|
|
1495
|
+
'aria-controls',
|
|
1496
|
+
);
|
|
1497
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1498
|
+
expect.stringContaining(
|
|
1499
|
+
'Tab: panelId does nothing outside a role="tablist" TabList',
|
|
1500
|
+
),
|
|
1501
|
+
);
|
|
1502
|
+
warn.mockRestore();
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
describe('TabList ARIA pattern — any other role', () => {
|
|
1507
|
+
it('passes the role through and leaves the tabs on the navigation pattern', () => {
|
|
1508
|
+
const {container} = render(
|
|
1509
|
+
<TabList value="a" onChange={() => {}} role="toolbar">
|
|
1510
|
+
<Tab value="a" label="Alpha" />
|
|
1511
|
+
</TabList>,
|
|
1512
|
+
);
|
|
1513
|
+
|
|
1514
|
+
expect(container.querySelector('nav')).toHaveAttribute('role', 'toolbar');
|
|
1515
|
+
expect(screen.queryByRole('tablist')).toBeNull();
|
|
1516
|
+
expect(screen.getByRole('button', {name: 'Alpha'})).toHaveAttribute(
|
|
1517
|
+
'aria-current',
|
|
1518
|
+
'true',
|
|
1519
|
+
);
|
|
1520
|
+
});
|
|
1521
|
+
});
|