@astryxdesign/core 0.4.7 → 0.5.0-canary.009bcb3
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 +49 -15
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +62 -20
- 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 +7 -3
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +21 -23
- 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 +12 -12
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +17 -9
- package/dist/Card/index.d.ts +35 -1
- package/dist/Card/index.d.ts.map +1 -1
- package/dist/Card/index.js +21 -1
- 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/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -3
- 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/DateRangeInput/DateRangeInput.d.ts +4 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- 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/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +4 -13
- 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/Field/InputClearButton.d.ts +9 -3
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +14 -3
- 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 -3
- 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/gestureCounter.d.ts +8 -0
- package/dist/Layer/gestureCounter.d.ts.map +1 -0
- package/dist/Layer/gestureCounter.js +53 -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/useLayer.d.ts +23 -13
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +128 -8
- 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/List/List.d.ts +1 -1
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +15 -4
- package/dist/Markdown/Markdown.d.ts +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +20 -7
- 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/MetadataList/MetadataListItem.d.ts +1 -1
- package/dist/MetadataList/MetadataListItem.d.ts.map +1 -1
- package/dist/MetadataList/MetadataListItem.js +4 -1
- 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 +73 -15
- package/dist/MultiSelector/hooks.d.ts +7 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +3 -2
- 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 +1 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +9 -5
- 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/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +1 -9
- package/dist/Popover/usePopover.d.ts +7 -41
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +40 -21
- 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 +12 -3
- package/dist/Selector/Selector.d.ts +18 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +91 -18
- package/dist/Selector/hooks.d.ts +7 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +3 -2
- 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 +26 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +54 -11
- 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 +43 -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 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -10
- 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 +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +15 -21
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +22 -7
- package/dist/Toast/ToastViewport.d.ts +1 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +71 -18
- package/dist/Token/Token.d.ts +1 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +20 -9
- package/dist/Token/TokenLink.d.ts +1 -1
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +7 -4
- package/dist/Tokenizer/Tokenizer.d.ts +14 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +33 -21
- 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 +1 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +31 -11
- package/dist/TopNav/TopNavMenu.d.ts +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +22 -7
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +7 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +35 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +119 -37
- package/dist/Typeahead/Typeahead.d.ts +8 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +5 -2
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +7 -2
- package/dist/astryx.css +113 -11
- 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/Theme.d.ts.map +1 -1
- package/dist/theme/Theme.js +46 -10
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +24 -4
- package/dist/theme/generateThemeRules.d.ts +21 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +26 -0
- 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/af-ZA.json +6 -6
- package/locales/ar-SA.json +6 -6
- package/locales/ca-ES.json +5 -5
- package/locales/cs-CZ.json +6 -6
- package/locales/da-DK.json +6 -6
- package/locales/de-DE.json +6 -6
- package/locales/el-GR.json +6 -6
- package/locales/en.json +163 -11
- package/locales/es-ES.json +5 -5
- package/locales/fi-FI.json +6 -6
- package/locales/fr-FR.json +6 -6
- package/locales/he-IL.json +6 -6
- package/locales/hu-HU.json +6 -6
- package/locales/it-IT.json +6 -6
- package/locales/ja-JP.json +6 -6
- package/locales/ko-KR.json +6 -6
- package/locales/nl-NL.json +6 -6
- package/locales/no-NO.json +6 -6
- package/locales/pl-PL.json +6 -6
- package/locales/pseudo.json +114 -0
- package/locales/pt-BR.json +6 -6
- package/locales/pt-PT.json +6 -6
- package/locales/ro-RO.json +6 -6
- package/locales/ru-RU.json +6 -6
- package/locales/sr-SP.json +6 -6
- package/locales/sv-SE.json +6 -6
- package/locales/tr-TR.json +6 -6
- package/locales/uk-UA.json +6 -6
- package/locales/vi-VN.json +6 -6
- package/locales/zh-CN.json +6 -6
- package/locales/zh-TW.json +6 -6
- 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 +22 -11
- package/src/Banner/Banner.test.tsx +177 -25
- package/src/Banner/Banner.tsx +93 -27
- 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 +7 -6
- package/src/Calendar/Calendar.test.tsx +143 -0
- package/src/Calendar/Calendar.tsx +30 -26
- 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 +113 -19
- package/src/Card/Card.tsx +67 -89
- package/src/Card/index.ts +36 -1
- 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/ComplexSelector/ComplexSelector.test.tsx +32 -0
- package/src/ComplexSelector/ComplexSelector.tsx +1 -3
- 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/DateRangeInput/DateRangeInput.doc.mjs +2 -2
- package/src/DateRangeInput/DateRangeInput.test.tsx +22 -0
- package/src/DateRangeInput/DateRangeInput.tsx +4 -2
- 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/DropdownMenu.test.tsx +44 -10
- package/src/DropdownMenu/DropdownMenu.tsx +4 -13
- 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/Field/InputClearButton.test.tsx +15 -1
- package/src/Field/InputClearButton.tsx +28 -4
- package/src/FileInput/FileInput.tsx +3 -2
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -3
- 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/gestureCounter.ts +57 -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 +324 -15
- package/src/Layer/useLayer.tsx +164 -8
- 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/Layout/__tests__/edgeCompensation.test.tsx +1 -1
- 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/List/List.test.tsx +38 -0
- package/src/List/List.tsx +8 -1
- package/src/Markdown/Markdown.doc.mjs +5 -0
- package/src/Markdown/Markdown.test.tsx +37 -0
- package/src/Markdown/Markdown.tsx +16 -5
- 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/MetadataList/MetadataList.test.tsx +75 -2
- package/src/MetadataList/MetadataListItem.tsx +5 -2
- 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 +311 -0
- package/src/MultiSelector/MultiSelector.tsx +151 -15
- package/src/MultiSelector/hooks.ts +9 -2
- 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.test.tsx +30 -0
- package/src/NavMenu/NavHeadingMenu.tsx +9 -5
- 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/Popover.test.tsx +14 -0
- package/src/Popover/Popover.tsx +1 -9
- package/src/Popover/usePopover.tsx +57 -32
- 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.doc.mjs +3 -0
- package/src/SelectableCard/SelectableCard.tsx +19 -2
- package/src/Selector/Selector.doc.mjs +26 -1
- package/src/Selector/Selector.test.tsx +346 -1
- package/src/Selector/Selector.tsx +148 -21
- package/src/Selector/hooks.ts +17 -2
- 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 +67 -12
- 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 +138 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +63 -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 +67 -1
- package/src/Text/Text.tsx +6 -3
- package/src/TextArea/TextArea.doc.mjs +12 -2
- package/src/TextArea/TextArea.test.tsx +11 -0
- package/src/TextArea/TextArea.tsx +37 -24
- 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 +58 -0
- package/src/Timestamp/Timestamp.tsx +12 -10
- package/src/Toast/Toast.doc.mjs +26 -12
- package/src/Toast/Toast.tsx +28 -4
- package/src/Toast/ToastViewport.test.tsx +536 -8
- package/src/Toast/ToastViewport.tsx +81 -12
- package/src/Token/Token.test.tsx +95 -0
- package/src/Token/Token.tsx +15 -10
- package/src/Token/TokenLink.tsx +14 -5
- package/src/Tokenizer/Tokenizer.doc.mjs +43 -3
- package/src/Tokenizer/Tokenizer.test.tsx +161 -0
- package/src/Tokenizer/Tokenizer.tsx +53 -24
- 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.test.tsx +104 -0
- package/src/TopNav/TopNavMegaMenu.tsx +47 -5
- package/src/TopNav/TopNavMenu.test.tsx +133 -1
- package/src/TopNav/TopNavMenu.tsx +38 -7
- package/src/TreeList/TreeList.test.tsx +49 -0
- package/src/TreeList/TreeList.tsx +7 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +24 -0
- package/src/Typeahead/BaseTypeahead.tsx +181 -39
- package/src/Typeahead/Typeahead.doc.mjs +11 -0
- package/src/Typeahead/Typeahead.test.tsx +203 -0
- package/src/Typeahead/Typeahead.tsx +12 -2
- package/src/Typeahead/TypeaheadItem.test.tsx +43 -0
- package/src/Typeahead/TypeaheadItem.tsx +8 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAnnounce.doc.mjs +96 -0
- package/src/hooks/useContainerReveal.doc.mjs +2 -2
- package/src/hooks/useDevWarning.doc.mjs +96 -0
- 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/useIndicatorFocusRing.doc.mjs +104 -0
- package/src/hooks/useInputStatusIcon.doc.mjs +142 -0
- package/src/hooks/useInteractiveRole.doc.mjs +119 -0
- package/src/hooks/useLongPress.doc.mjs +123 -0
- 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/hooks/useTypeahead.doc.mjs +153 -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/Theme.test.tsx +104 -2
- package/src/theme/Theme.tsx +49 -14
- package/src/theme/defineTheme.test.ts +23 -0
- package/src/theme/derivedVarRegistry.test.ts +26 -3
- package/src/theme/derivedVarRegistry.ts +23 -4
- package/src/theme/generateThemeRules.test.ts +99 -1
- package/src/theme/generateThemeRules.ts +28 -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
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {StrictMode} from 'react';
|
|
4
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
5
|
+
import {render, screen, within} from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import {Stepper} from './Stepper';
|
|
8
|
+
import {Step} from './Step';
|
|
9
|
+
|
|
10
|
+
describe('Stepper', () => {
|
|
11
|
+
it('renders an ordered list of steps (not a nav landmark)', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Stepper activeStep={0}>
|
|
14
|
+
<Step step={0} label="Step 1" />
|
|
15
|
+
<Step step={1} label="Step 2" />
|
|
16
|
+
<Step step={2} label="Step 3" />
|
|
17
|
+
</Stepper>,
|
|
18
|
+
);
|
|
19
|
+
// A stepper is a sequence/progress list, not a navigation landmark.
|
|
20
|
+
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
|
21
|
+
const list = screen.getByRole('list', {name: 'Progress'});
|
|
22
|
+
expect(list.tagName).toBe('OL');
|
|
23
|
+
expect(screen.getByText('Step 1')).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText('Step 2')).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByText('Step 3')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders step numbers', () => {
|
|
29
|
+
render(
|
|
30
|
+
<Stepper activeStep={0}>
|
|
31
|
+
<Step step={0} label="First" indicator="number" />
|
|
32
|
+
<Step step={1} label="Second" indicator="number" />
|
|
33
|
+
</Stepper>,
|
|
34
|
+
);
|
|
35
|
+
expect(screen.getByText('1')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByText('2')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('marks the active step with aria-current', () => {
|
|
40
|
+
render(
|
|
41
|
+
<Stepper activeStep={1}>
|
|
42
|
+
<Step step={0} label="Step 1" data-testid="step-0" />
|
|
43
|
+
<Step step={1} label="Step 2" data-testid="step-1" />
|
|
44
|
+
<Step step={2} label="Step 3" data-testid="step-2" />
|
|
45
|
+
</Stepper>,
|
|
46
|
+
);
|
|
47
|
+
expect(screen.getByTestId('step-0')).not.toHaveAttribute('aria-current');
|
|
48
|
+
expect(screen.getByTestId('step-1')).toHaveAttribute(
|
|
49
|
+
'aria-current',
|
|
50
|
+
'step',
|
|
51
|
+
);
|
|
52
|
+
expect(screen.getByTestId('step-2')).not.toHaveAttribute('aria-current');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders descriptions when provided', () => {
|
|
56
|
+
render(
|
|
57
|
+
<Stepper activeStep={0}>
|
|
58
|
+
<Step step={0} label="Account" description="Create your account" />
|
|
59
|
+
<Step step={1} label="Profile" />
|
|
60
|
+
</Stepper>,
|
|
61
|
+
);
|
|
62
|
+
expect(screen.getByText('Create your account')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('supports custom accessible label', () => {
|
|
66
|
+
render(
|
|
67
|
+
<Stepper activeStep={0} label="Checkout progress">
|
|
68
|
+
<Step step={0} label="Cart" />
|
|
69
|
+
<Step step={1} label="Payment" />
|
|
70
|
+
</Stepper>,
|
|
71
|
+
);
|
|
72
|
+
expect(
|
|
73
|
+
screen.getByRole('list', {name: 'Checkout progress'}),
|
|
74
|
+
).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('supports vertical orientation', () => {
|
|
78
|
+
render(
|
|
79
|
+
<Stepper activeStep={0} orientation="vertical">
|
|
80
|
+
<Step step={0} label="Step 1" />
|
|
81
|
+
<Step step={1} label="Step 2" />
|
|
82
|
+
</Stepper>,
|
|
83
|
+
);
|
|
84
|
+
const list = screen.getByRole('list');
|
|
85
|
+
expect(list).toHaveAttribute('data-orientation', 'vertical');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('calls onStepClick when a completed step is clicked', async () => {
|
|
89
|
+
const user = userEvent.setup();
|
|
90
|
+
const handleClick = vi.fn();
|
|
91
|
+
render(
|
|
92
|
+
<Stepper activeStep={2} onStepClick={handleClick}>
|
|
93
|
+
<Step step={0} label="Step 1" />
|
|
94
|
+
<Step step={1} label="Step 2" />
|
|
95
|
+
<Step step={2} label="Step 3" />
|
|
96
|
+
</Stepper>,
|
|
97
|
+
);
|
|
98
|
+
await user.click(
|
|
99
|
+
screen.getByRole('button', {name: 'Go to step 1: Step 1, completed'}),
|
|
100
|
+
);
|
|
101
|
+
expect(handleClick).toHaveBeenCalledWith(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('calls onStepClick when the active step is clicked', async () => {
|
|
105
|
+
const user = userEvent.setup();
|
|
106
|
+
const handleClick = vi.fn();
|
|
107
|
+
render(
|
|
108
|
+
<Stepper activeStep={1} onStepClick={handleClick}>
|
|
109
|
+
<Step step={0} label="Step 1" />
|
|
110
|
+
<Step step={1} label="Step 2" />
|
|
111
|
+
<Step step={2} label="Step 3" />
|
|
112
|
+
</Stepper>,
|
|
113
|
+
);
|
|
114
|
+
await user.click(
|
|
115
|
+
screen.getByRole('button', {name: 'Go to step 2: Step 2'}),
|
|
116
|
+
);
|
|
117
|
+
expect(handleClick).toHaveBeenCalledWith(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('renders buttons for upcoming steps in non-linear mode', () => {
|
|
121
|
+
render(
|
|
122
|
+
<Stepper activeStep={0} onStepClick={() => {}}>
|
|
123
|
+
<Step step={0} label="Step 1" />
|
|
124
|
+
<Step step={1} label="Step 2" />
|
|
125
|
+
</Stepper>,
|
|
126
|
+
);
|
|
127
|
+
expect(
|
|
128
|
+
screen.getByRole('button', {name: 'Go to step 1: Step 1'}),
|
|
129
|
+
).toBeInTheDocument();
|
|
130
|
+
expect(
|
|
131
|
+
screen.getByRole('button', {name: 'Go to step 2: Step 2'}),
|
|
132
|
+
).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('calls onStepClick when an upcoming step is clicked', async () => {
|
|
136
|
+
const user = userEvent.setup();
|
|
137
|
+
const handleClick = vi.fn();
|
|
138
|
+
render(
|
|
139
|
+
<Stepper activeStep={0} onStepClick={handleClick}>
|
|
140
|
+
<Step step={0} label="Step 1" />
|
|
141
|
+
<Step step={1} label="Step 2" />
|
|
142
|
+
<Step step={2} label="Step 3" />
|
|
143
|
+
</Stepper>,
|
|
144
|
+
);
|
|
145
|
+
await user.click(
|
|
146
|
+
screen.getByRole('button', {name: 'Go to step 3: Step 3'}),
|
|
147
|
+
);
|
|
148
|
+
expect(handleClick).toHaveBeenCalledWith(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('does not render buttons for disabled steps', () => {
|
|
152
|
+
render(
|
|
153
|
+
<Stepper activeStep={2} onStepClick={() => {}}>
|
|
154
|
+
<Step step={0} label="Step 1" isDisabled />
|
|
155
|
+
<Step step={1} label="Step 2" />
|
|
156
|
+
<Step step={2} label="Step 3" />
|
|
157
|
+
</Stepper>,
|
|
158
|
+
);
|
|
159
|
+
expect(
|
|
160
|
+
screen.queryByRole('button', {name: /Go to step 1: Step 1/}),
|
|
161
|
+
).not.toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('does not render buttons when onStepClick is not provided', () => {
|
|
165
|
+
render(
|
|
166
|
+
<Stepper activeStep={2}>
|
|
167
|
+
<Step step={0} label="Step 1" />
|
|
168
|
+
<Step step={1} label="Step 2" />
|
|
169
|
+
<Step step={2} label="Step 3" />
|
|
170
|
+
</Stepper>,
|
|
171
|
+
);
|
|
172
|
+
expect(screen.queryAllByRole('button')).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('applies a semantic color status (color only) via data attribute', () => {
|
|
176
|
+
render(
|
|
177
|
+
<Stepper activeStep={1}>
|
|
178
|
+
<Step step={0} label="Step 1" data-testid="step-0" />
|
|
179
|
+
<Step step={1} label="Step 2" status="error" data-testid="step-1" />
|
|
180
|
+
</Stepper>,
|
|
181
|
+
);
|
|
182
|
+
expect(screen.getByTestId('step-1')).toHaveAttribute(
|
|
183
|
+
'data-status',
|
|
184
|
+
'error',
|
|
185
|
+
);
|
|
186
|
+
// status is color-only — it must not change progress semantics.
|
|
187
|
+
expect(screen.getByTestId('step-1')).toHaveAttribute(
|
|
188
|
+
'aria-current',
|
|
189
|
+
'step',
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('reflects each global semantic status on the data attribute', () => {
|
|
194
|
+
render(
|
|
195
|
+
<Stepper activeStep={0}>
|
|
196
|
+
<Step step={0} label="A" status="accent" data-testid="s-accent" />
|
|
197
|
+
<Step step={1} label="B" status="success" data-testid="s-success" />
|
|
198
|
+
<Step step={2} label="C" status="warning" data-testid="s-warning" />
|
|
199
|
+
<Step step={3} label="D" status="error" data-testid="s-error" />
|
|
200
|
+
</Stepper>,
|
|
201
|
+
);
|
|
202
|
+
expect(screen.getByTestId('s-accent')).toHaveAttribute(
|
|
203
|
+
'data-status',
|
|
204
|
+
'accent',
|
|
205
|
+
);
|
|
206
|
+
expect(screen.getByTestId('s-success')).toHaveAttribute(
|
|
207
|
+
'data-status',
|
|
208
|
+
'success',
|
|
209
|
+
);
|
|
210
|
+
expect(screen.getByTestId('s-warning')).toHaveAttribute(
|
|
211
|
+
'data-status',
|
|
212
|
+
'warning',
|
|
213
|
+
);
|
|
214
|
+
expect(screen.getByTestId('s-error')).toHaveAttribute(
|
|
215
|
+
'data-status',
|
|
216
|
+
'error',
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('keeps the progress bar progress-colored regardless of status, recoloring only the indicator', () => {
|
|
221
|
+
// Baseline: a completed step with no status.
|
|
222
|
+
const baseline = render(
|
|
223
|
+
<Stepper activeStep={1}>
|
|
224
|
+
<Step step={0} label="A" data-testid="base" />
|
|
225
|
+
</Stepper>,
|
|
226
|
+
);
|
|
227
|
+
const baseStep = baseline.getByTestId('base');
|
|
228
|
+
const baseBar = baseStep.querySelector('.astryx-step-bar') as HTMLElement;
|
|
229
|
+
const baseIndicator = baseStep.querySelector('svg')
|
|
230
|
+
?.parentElement as HTMLElement;
|
|
231
|
+
|
|
232
|
+
// Same completed step, now with a semantic status.
|
|
233
|
+
const themed = render(
|
|
234
|
+
<Stepper activeStep={1}>
|
|
235
|
+
<Step step={0} label="A" status="error" data-testid="themed" />
|
|
236
|
+
</Stepper>,
|
|
237
|
+
);
|
|
238
|
+
const themedStep = themed.getByTestId('themed');
|
|
239
|
+
const themedBar = themedStep.querySelector(
|
|
240
|
+
'.astryx-step-bar',
|
|
241
|
+
) as HTMLElement;
|
|
242
|
+
const themedIndicator = themedStep.querySelector('svg')
|
|
243
|
+
?.parentElement as HTMLElement;
|
|
244
|
+
|
|
245
|
+
// Bar coloring must be identical — status must NOT recolor the bar
|
|
246
|
+
// (always --color-accent when filled / --color-border when incomplete).
|
|
247
|
+
expect(themedBar.className).toBe(baseBar.className);
|
|
248
|
+
|
|
249
|
+
// The indicator, however, must pick up the status color.
|
|
250
|
+
expect(themedIndicator.className).not.toBe(baseIndicator.className);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('keeps an incomplete step bar border-colored regardless of status', () => {
|
|
254
|
+
// Baseline: a not-started step with no status.
|
|
255
|
+
const baseline = render(
|
|
256
|
+
<Stepper activeStep={0}>
|
|
257
|
+
<Step step={0} label="A" data-testid="base-active" />
|
|
258
|
+
<Step step={1} label="B" data-testid="base" />
|
|
259
|
+
</Stepper>,
|
|
260
|
+
);
|
|
261
|
+
const baseBar = baseline
|
|
262
|
+
.getByTestId('base')
|
|
263
|
+
.querySelector('.astryx-step-bar') as HTMLElement;
|
|
264
|
+
|
|
265
|
+
// Same not-started step, now with a semantic status.
|
|
266
|
+
const themed = render(
|
|
267
|
+
<Stepper activeStep={0}>
|
|
268
|
+
<Step step={0} label="A" data-testid="themed-active" />
|
|
269
|
+
<Step step={1} label="B" status="warning" data-testid="themed" />
|
|
270
|
+
</Stepper>,
|
|
271
|
+
);
|
|
272
|
+
const themedBar = themed
|
|
273
|
+
.getByTestId('themed')
|
|
274
|
+
.querySelector('.astryx-step-bar') as HTMLElement;
|
|
275
|
+
|
|
276
|
+
// Incomplete bar stays border-colored — status must not recolor it.
|
|
277
|
+
expect(themedBar.className).toBe(baseBar.className);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('does not set a status data attribute when status is unset', () => {
|
|
281
|
+
render(
|
|
282
|
+
<Stepper activeStep={0}>
|
|
283
|
+
<Step step={0} label="Step 1" data-testid="step-0" />
|
|
284
|
+
</Stepper>,
|
|
285
|
+
);
|
|
286
|
+
expect(screen.getByTestId('step-0')).not.toHaveAttribute('data-status');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('handles zero active step correctly', () => {
|
|
290
|
+
render(
|
|
291
|
+
<Stepper activeStep={0}>
|
|
292
|
+
<Step step={0} label="Step 1" data-testid="step-0" />
|
|
293
|
+
<Step step={1} label="Step 2" data-testid="step-1" />
|
|
294
|
+
</Stepper>,
|
|
295
|
+
);
|
|
296
|
+
expect(screen.getByTestId('step-0')).toHaveAttribute(
|
|
297
|
+
'aria-current',
|
|
298
|
+
'step',
|
|
299
|
+
);
|
|
300
|
+
expect(screen.getByTestId('step-1')).not.toHaveAttribute('aria-current');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('renders each step as a list item', () => {
|
|
304
|
+
render(
|
|
305
|
+
<Stepper activeStep={0}>
|
|
306
|
+
<Step step={0} label="Step 1" />
|
|
307
|
+
<Step step={1} label="Step 2" />
|
|
308
|
+
<Step step={2} label="Step 3" />
|
|
309
|
+
</Stepper>,
|
|
310
|
+
);
|
|
311
|
+
const items = screen.getAllByRole('listitem');
|
|
312
|
+
expect(items).toHaveLength(3);
|
|
313
|
+
expect(items[0].tagName).toBe('LI');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('accepts a custom ReactNode as indicator', () => {
|
|
317
|
+
render(
|
|
318
|
+
<Stepper activeStep={0}>
|
|
319
|
+
<Step
|
|
320
|
+
step={0}
|
|
321
|
+
label="Step 1"
|
|
322
|
+
indicator={<span data-testid="custom-icon">★</span>}
|
|
323
|
+
/>
|
|
324
|
+
</Stepper>,
|
|
325
|
+
);
|
|
326
|
+
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
|
|
327
|
+
expect(screen.getByText('★')).toBeInTheDocument();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('accepts a ReactNode as indicator', () => {
|
|
331
|
+
render(
|
|
332
|
+
<Stepper activeStep={1}>
|
|
333
|
+
<Step
|
|
334
|
+
step={1}
|
|
335
|
+
label="Payment"
|
|
336
|
+
indicator={<span data-testid="pay-icon">$</span>}
|
|
337
|
+
/>
|
|
338
|
+
</Stepper>,
|
|
339
|
+
);
|
|
340
|
+
expect(screen.getByTestId('pay-icon')).toBeInTheDocument();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('renders a distinct indicator glyph per status on non-current steps', () => {
|
|
344
|
+
// All steps completed (activeStep past them) so none is the current step.
|
|
345
|
+
render(
|
|
346
|
+
<Stepper activeStep={4}>
|
|
347
|
+
<Step step={0} label="A" status="success" data-testid="s-success" />
|
|
348
|
+
<Step step={1} label="B" status="warning" data-testid="s-warning" />
|
|
349
|
+
<Step step={2} label="C" status="error" data-testid="s-error" />
|
|
350
|
+
<Step step={3} label="D" data-testid="s-plain" />
|
|
351
|
+
</Stepper>,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const indicatorClass = (testid: string) =>
|
|
355
|
+
(
|
|
356
|
+
screen.getByTestId(testid).querySelector('svg')
|
|
357
|
+
?.parentElement as HTMLElement
|
|
358
|
+
).className;
|
|
359
|
+
|
|
360
|
+
// Each status renders an svg indicator (no number badge)...
|
|
361
|
+
expect(screen.getByTestId('s-success').querySelector('svg')).toBeTruthy();
|
|
362
|
+
expect(screen.getByTestId('s-warning').querySelector('svg')).toBeTruthy();
|
|
363
|
+
expect(screen.getByTestId('s-error').querySelector('svg')).toBeTruthy();
|
|
364
|
+
|
|
365
|
+
// ...and each status tints the indicator differently from the others and
|
|
366
|
+
// from the plain completed (accent) step.
|
|
367
|
+
const classes = new Set([
|
|
368
|
+
indicatorClass('s-success'),
|
|
369
|
+
indicatorClass('s-warning'),
|
|
370
|
+
indicatorClass('s-error'),
|
|
371
|
+
indicatorClass('s-plain'),
|
|
372
|
+
]);
|
|
373
|
+
expect(classes.size).toBe(4);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('lets the current step keep its ring indicator regardless of status', () => {
|
|
377
|
+
// The current-step ring's painted color is driven by the StyleX
|
|
378
|
+
// `iconInProgress` class (accent), never a status color — the ring replaces
|
|
379
|
+
// any status glyph. The `astryx-step-indicator` theme target reflects
|
|
380
|
+
// `status` as a data attribute so a theme can still reach it, which is
|
|
381
|
+
// orthogonal to the painted color, so assert the StyleX color class here.
|
|
382
|
+
const stylexColorClasses = (el: HTMLElement) =>
|
|
383
|
+
el.className
|
|
384
|
+
.split(/\s+/)
|
|
385
|
+
// Keep StyleX classes (debug `Step__styles.*` names + `x*` atomic
|
|
386
|
+
// hashes); drop the themeProps data reflections (`in-progress`,
|
|
387
|
+
// `success`, `astryx-*`) which are orthogonal to the painted color.
|
|
388
|
+
.filter(c => c.startsWith('Step__styles.') || /^x[a-z0-9]+$/.test(c))
|
|
389
|
+
.sort()
|
|
390
|
+
.join(' ');
|
|
391
|
+
|
|
392
|
+
const plain = render(
|
|
393
|
+
<Stepper activeStep={0}>
|
|
394
|
+
<Step step={0} label="A" data-testid="plain" />
|
|
395
|
+
</Stepper>,
|
|
396
|
+
);
|
|
397
|
+
const plainIndicator = stylexColorClasses(
|
|
398
|
+
plain.getByTestId('plain').querySelector('svg')
|
|
399
|
+
?.parentElement as HTMLElement,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// The same current step, now with status="success": the painted ring must
|
|
403
|
+
// be unchanged (the current-step ring replaces any status glyph).
|
|
404
|
+
const themed = render(
|
|
405
|
+
<Stepper activeStep={0}>
|
|
406
|
+
<Step step={0} label="A" status="success" data-testid="themed" />
|
|
407
|
+
</Stepper>,
|
|
408
|
+
);
|
|
409
|
+
const themedIndicator = stylexColorClasses(
|
|
410
|
+
themed.getByTestId('themed').querySelector('svg')
|
|
411
|
+
?.parentElement as HTMLElement,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
expect(themedIndicator).toBe(plainIndicator);
|
|
415
|
+
// And it is the in-progress (accent) color, not a status color.
|
|
416
|
+
expect(plainIndicator).toContain('Step__styles.iconInProgress');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('replaces the number badge with a status glyph on not-started steps', () => {
|
|
420
|
+
render(
|
|
421
|
+
<Stepper activeStep={0}>
|
|
422
|
+
<Step step={0} label="A" data-testid="current" />
|
|
423
|
+
<Step step={1} label="B" status="error" data-testid="future" />
|
|
424
|
+
</Stepper>,
|
|
425
|
+
);
|
|
426
|
+
const future = screen.getByTestId('future');
|
|
427
|
+
// The not-started step would normally show its number ("2"); with a status
|
|
428
|
+
// glyph it shows an icon instead.
|
|
429
|
+
expect(future.textContent).not.toContain('2');
|
|
430
|
+
expect(future.querySelector('svg')).toBeTruthy();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('exposes progress/status as visually hidden text (indicators are aria-hidden)', () => {
|
|
434
|
+
render(
|
|
435
|
+
<Stepper activeStep={2}>
|
|
436
|
+
<Step step={0} label="Account" data-testid="done" />
|
|
437
|
+
<Step step={1} label="Payment" status="error" data-testid="failed" />
|
|
438
|
+
<Step step={2} label="Review" data-testid="current" />
|
|
439
|
+
<Step step={3} label="Confirm" data-testid="upcoming" />
|
|
440
|
+
</Stepper>,
|
|
441
|
+
);
|
|
442
|
+
// Completed step announces "completed"; error status wins over completion.
|
|
443
|
+
expect(
|
|
444
|
+
within(screen.getByTestId('done')).getByText('completed'),
|
|
445
|
+
).toBeInTheDocument();
|
|
446
|
+
expect(
|
|
447
|
+
within(screen.getByTestId('failed')).getByText('error'),
|
|
448
|
+
).toBeInTheDocument();
|
|
449
|
+
expect(
|
|
450
|
+
within(screen.getByTestId('failed')).queryByText('completed'),
|
|
451
|
+
).not.toBeInTheDocument();
|
|
452
|
+
// Current step is announced via aria-current, not hidden text; upcoming
|
|
453
|
+
// steps stay silent.
|
|
454
|
+
expect(
|
|
455
|
+
within(screen.getByTestId('current')).queryByText('completed'),
|
|
456
|
+
).not.toBeInTheDocument();
|
|
457
|
+
expect(
|
|
458
|
+
within(screen.getByTestId('upcoming')).queryByText(
|
|
459
|
+
/completed|error|warning/,
|
|
460
|
+
),
|
|
461
|
+
).not.toBeInTheDocument();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('exposes warning and success statuses as visually hidden text', () => {
|
|
465
|
+
render(
|
|
466
|
+
<Stepper activeStep={2}>
|
|
467
|
+
<Step step={0} label="Build" status="warning" data-testid="warned" />
|
|
468
|
+
<Step step={1} label="Deploy" status="success" data-testid="passed" />
|
|
469
|
+
</Stepper>,
|
|
470
|
+
);
|
|
471
|
+
expect(
|
|
472
|
+
within(screen.getByTestId('warned')).getByText('warning'),
|
|
473
|
+
).toBeInTheDocument();
|
|
474
|
+
expect(
|
|
475
|
+
within(screen.getByTestId('passed')).getByText('completed'),
|
|
476
|
+
).toBeInTheDocument();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('composes status into the accessible name of clickable steps', () => {
|
|
480
|
+
render(
|
|
481
|
+
<Stepper activeStep={2} onStepClick={() => {}}>
|
|
482
|
+
<Step step={0} label="Account" />
|
|
483
|
+
<Step step={1} label="Payment" status="error" />
|
|
484
|
+
<Step step={2} label="Review" />
|
|
485
|
+
</Stepper>,
|
|
486
|
+
);
|
|
487
|
+
expect(
|
|
488
|
+
screen.getByRole('button', {name: 'Go to step 1: Account, completed'}),
|
|
489
|
+
).toBeInTheDocument();
|
|
490
|
+
expect(
|
|
491
|
+
screen.getByRole('button', {name: 'Go to step 2: Payment, error'}),
|
|
492
|
+
).toBeInTheDocument();
|
|
493
|
+
// The current step gets no status suffix (aria-current covers it).
|
|
494
|
+
expect(
|
|
495
|
+
screen.getByRole('button', {name: 'Go to step 3: Review'}),
|
|
496
|
+
).toBeInTheDocument();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('exposes hidden status text in the on-track layout too', () => {
|
|
500
|
+
render(
|
|
501
|
+
<Stepper activeStep={1} indicatorPosition="on-track">
|
|
502
|
+
<Step step={0} label="Account" data-testid="ot-done" />
|
|
503
|
+
<Step step={1} label="Payment" data-testid="ot-current" />
|
|
504
|
+
</Stepper>,
|
|
505
|
+
);
|
|
506
|
+
expect(
|
|
507
|
+
within(screen.getByTestId('ot-done')).getByText('completed'),
|
|
508
|
+
).toBeInTheDocument();
|
|
509
|
+
expect(
|
|
510
|
+
within(screen.getByTestId('ot-current')).queryByText('completed'),
|
|
511
|
+
).not.toBeInTheDocument();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// A vertical on-track step's content slot renders outside the row that draws
|
|
515
|
+
// the connector, so without its own segment the track breaks open around any
|
|
516
|
+
// step carrying content. jsdom can't measure the line, so assert the wiring:
|
|
517
|
+
// the slot carries a segment, and it hides on the last step like the row's
|
|
518
|
+
// trailing one does.
|
|
519
|
+
it('continues the on-track connector past a step content slot', () => {
|
|
520
|
+
render(
|
|
521
|
+
<Stepper
|
|
522
|
+
activeStep={1}
|
|
523
|
+
orientation="vertical"
|
|
524
|
+
indicatorPosition="on-track">
|
|
525
|
+
<Step step={0} label="Account" data-testid="ot-plain" />
|
|
526
|
+
<Step step={1} label="Payment" data-testid="ot-content">
|
|
527
|
+
<button type="button">Pay now</button>
|
|
528
|
+
</Step>
|
|
529
|
+
</Stepper>,
|
|
530
|
+
);
|
|
531
|
+
expect(
|
|
532
|
+
screen
|
|
533
|
+
.getByTestId('ot-content')
|
|
534
|
+
.querySelector('[class*="otContentSegV"][class*="otSegHiddenIfLast"]'),
|
|
535
|
+
).toBeInTheDocument();
|
|
536
|
+
// A step without content has no slot, so no extra segment.
|
|
537
|
+
expect(
|
|
538
|
+
screen.getByTestId('ot-plain').querySelector('[class*="otContentSegV"]'),
|
|
539
|
+
).not.toBeInTheDocument();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
describe('keyboard interaction', () => {
|
|
543
|
+
it('activates a step with Enter', async () => {
|
|
544
|
+
const user = userEvent.setup();
|
|
545
|
+
const handleClick = vi.fn();
|
|
546
|
+
render(
|
|
547
|
+
<Stepper activeStep={2} onStepClick={handleClick}>
|
|
548
|
+
<Step step={0} label="Step 1" />
|
|
549
|
+
<Step step={1} label="Step 2" />
|
|
550
|
+
<Step step={2} label="Step 3" />
|
|
551
|
+
</Stepper>,
|
|
552
|
+
);
|
|
553
|
+
await user.tab();
|
|
554
|
+
expect(
|
|
555
|
+
screen.getByRole('button', {name: 'Go to step 1: Step 1, completed'}),
|
|
556
|
+
).toHaveFocus();
|
|
557
|
+
await user.keyboard('{Enter}');
|
|
558
|
+
expect(handleClick).toHaveBeenCalledWith(0);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('activates a step with Space', async () => {
|
|
562
|
+
const user = userEvent.setup();
|
|
563
|
+
const handleClick = vi.fn();
|
|
564
|
+
render(
|
|
565
|
+
<Stepper activeStep={2} onStepClick={handleClick}>
|
|
566
|
+
<Step step={0} label="Step 1" />
|
|
567
|
+
<Step step={1} label="Step 2" />
|
|
568
|
+
<Step step={2} label="Step 3" />
|
|
569
|
+
</Stepper>,
|
|
570
|
+
);
|
|
571
|
+
await user.tab();
|
|
572
|
+
await user.keyboard(' ');
|
|
573
|
+
expect(handleClick).toHaveBeenCalledWith(0);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('tabs through steps in document order, skipping disabled ones', async () => {
|
|
577
|
+
const user = userEvent.setup();
|
|
578
|
+
render(
|
|
579
|
+
<Stepper activeStep={3} onStepClick={() => {}}>
|
|
580
|
+
<Step step={0} label="One" />
|
|
581
|
+
<Step step={1} label="Two" isDisabled />
|
|
582
|
+
<Step step={2} label="Three" />
|
|
583
|
+
</Stepper>,
|
|
584
|
+
);
|
|
585
|
+
await user.tab();
|
|
586
|
+
expect(
|
|
587
|
+
screen.getByRole('button', {name: 'Go to step 1: One, completed'}),
|
|
588
|
+
).toHaveFocus();
|
|
589
|
+
// The disabled step renders no button, so Tab lands on step 3 next.
|
|
590
|
+
await user.tab();
|
|
591
|
+
expect(
|
|
592
|
+
screen.getByRole('button', {name: 'Go to step 3: Three, completed'}),
|
|
593
|
+
).toHaveFocus();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('supports keyboard activation in the on-track layout', async () => {
|
|
597
|
+
const user = userEvent.setup();
|
|
598
|
+
const handleClick = vi.fn();
|
|
599
|
+
render(
|
|
600
|
+
<Stepper
|
|
601
|
+
activeStep={2}
|
|
602
|
+
indicatorPosition="on-track"
|
|
603
|
+
onStepClick={handleClick}>
|
|
604
|
+
<Step step={0} label="Cart" />
|
|
605
|
+
<Step step={1} label="Shipping" />
|
|
606
|
+
<Step step={2} label="Payment" />
|
|
607
|
+
</Stepper>,
|
|
608
|
+
);
|
|
609
|
+
await user.tab();
|
|
610
|
+
await user.keyboard('{Enter}');
|
|
611
|
+
expect(handleClick).toHaveBeenCalledWith(0);
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
describe('fragment-grouped steps', () => {
|
|
616
|
+
const onTrackSteps = (
|
|
617
|
+
<>
|
|
618
|
+
<Step step={0} label="Cart" />
|
|
619
|
+
<Step step={1} label="Shipping" />
|
|
620
|
+
<Step step={2} label="Payment" />
|
|
621
|
+
</>
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
it('renders on-track steps identically whether they are flat or grouped in a fragment', () => {
|
|
625
|
+
const flat = render(
|
|
626
|
+
<Stepper activeStep={1} indicatorPosition="on-track">
|
|
627
|
+
<Step step={0} label="Cart" />
|
|
628
|
+
<Step step={1} label="Shipping" />
|
|
629
|
+
<Step step={2} label="Payment" />
|
|
630
|
+
</Stepper>,
|
|
631
|
+
);
|
|
632
|
+
const flatHtml = flat.container.innerHTML;
|
|
633
|
+
flat.unmount();
|
|
634
|
+
|
|
635
|
+
const grouped = render(
|
|
636
|
+
<Stepper activeStep={1} indicatorPosition="on-track">
|
|
637
|
+
{onTrackSteps}
|
|
638
|
+
</Stepper>,
|
|
639
|
+
);
|
|
640
|
+
expect(grouped.container.innerHTML).toBe(flatHtml);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// The connector's end segments are hidden by CSS keyed to the step's own
|
|
644
|
+
// <li> position, which jsdom can't evaluate — assert the wiring instead:
|
|
645
|
+
// every step carries the same hide-if-first/hide-if-last classes, so no
|
|
646
|
+
// step is singled out by counting children.
|
|
647
|
+
it('gives every grouped step the same structural connector classes', () => {
|
|
648
|
+
render(
|
|
649
|
+
<Stepper activeStep={1} indicatorPosition="on-track">
|
|
650
|
+
{onTrackSteps}
|
|
651
|
+
</Stepper>,
|
|
652
|
+
);
|
|
653
|
+
const items = screen.getAllByRole('listitem');
|
|
654
|
+
expect(items).toHaveLength(3);
|
|
655
|
+
for (const item of items) {
|
|
656
|
+
expect(
|
|
657
|
+
item.querySelector('[class*="otSegHiddenIfFirst"]'),
|
|
658
|
+
).toBeInTheDocument();
|
|
659
|
+
expect(
|
|
660
|
+
item.querySelector('[class*="otSegHiddenIfLast"]'),
|
|
661
|
+
).toBeInTheDocument();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('keeps every grouped step keyboard-activatable', async () => {
|
|
666
|
+
const user = userEvent.setup();
|
|
667
|
+
const handleClick = vi.fn();
|
|
668
|
+
render(
|
|
669
|
+
<Stepper
|
|
670
|
+
activeStep={2}
|
|
671
|
+
indicatorPosition="on-track"
|
|
672
|
+
onStepClick={handleClick}>
|
|
673
|
+
{onTrackSteps}
|
|
674
|
+
</Stepper>,
|
|
675
|
+
);
|
|
676
|
+
screen
|
|
677
|
+
.getByRole('button', {name: 'Go to step 2: Shipping, completed'})
|
|
678
|
+
.focus();
|
|
679
|
+
await user.keyboard('{Enter}');
|
|
680
|
+
expect(handleClick).toHaveBeenCalledWith(1);
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// The connector fill animates a ::before scaled along the track axis, which
|
|
685
|
+
// jsdom neither renders nor times — so assert the wiring the animation hangs
|
|
686
|
+
// off: every connector carries the shared track style (the element that owns
|
|
687
|
+
// the fill layer and its transition), and the fill/empty pair it gets is the
|
|
688
|
+
// one for its axis, since the wrong axis would scale the line's thickness
|
|
689
|
+
// away instead of its length.
|
|
690
|
+
describe('connector fill', () => {
|
|
691
|
+
const barEl = (testId: string) =>
|
|
692
|
+
screen
|
|
693
|
+
.getByTestId(testId)
|
|
694
|
+
.querySelector('[class*="connectorTrack"]') as HTMLElement;
|
|
695
|
+
|
|
696
|
+
it('scales the separated bar along the inline axis when horizontal', () => {
|
|
697
|
+
render(
|
|
698
|
+
<Stepper activeStep={1} orientation="horizontal">
|
|
699
|
+
<Step step={0} label="A" data-testid="a" />
|
|
700
|
+
<Step step={1} label="B" data-testid="b" />
|
|
701
|
+
<Step step={2} label="C" data-testid="c" />
|
|
702
|
+
</Stepper>,
|
|
703
|
+
);
|
|
704
|
+
// Reached steps are filled, upcoming ones empty — both on the H pair.
|
|
705
|
+
expect(barEl('a').className).toContain('connectorFillH');
|
|
706
|
+
expect(barEl('b').className).toContain('connectorFillH');
|
|
707
|
+
expect(barEl('c').className).toContain('connectorEmptyH');
|
|
708
|
+
expect(barEl('c').className).not.toContain('connectorEmptyV');
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('scales the separated bar along the block axis when vertical', () => {
|
|
712
|
+
render(
|
|
713
|
+
<Stepper activeStep={1} orientation="vertical">
|
|
714
|
+
<Step step={0} label="A" data-testid="a" />
|
|
715
|
+
<Step step={1} label="B" data-testid="b" />
|
|
716
|
+
<Step step={2} label="C" data-testid="c" />
|
|
717
|
+
</Stepper>,
|
|
718
|
+
);
|
|
719
|
+
expect(barEl('a').className).toContain('connectorFillV');
|
|
720
|
+
expect(barEl('c').className).toContain('connectorEmptyV');
|
|
721
|
+
expect(barEl('c').className).not.toContain('connectorEmptyH');
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('gives every on-track segment the track style and its axis pair', () => {
|
|
725
|
+
render(
|
|
726
|
+
<Stepper activeStep={1} indicatorPosition="on-track">
|
|
727
|
+
<Step step={0} label="A" data-testid="a" />
|
|
728
|
+
<Step step={1} label="B" data-testid="b" />
|
|
729
|
+
<Step step={2} label="C" data-testid="c" />
|
|
730
|
+
</Stepper>,
|
|
731
|
+
);
|
|
732
|
+
// A step draws the segments flanking its own indicator, so each carries
|
|
733
|
+
// the track style; none may fall back to the vertical axis pair here.
|
|
734
|
+
for (const id of ['a', 'b', 'c']) {
|
|
735
|
+
const segs = Array.from(
|
|
736
|
+
screen.getByTestId(id).querySelectorAll('[class*="connectorTrack"]'),
|
|
737
|
+
);
|
|
738
|
+
expect(segs).toHaveLength(2);
|
|
739
|
+
for (const seg of segs) {
|
|
740
|
+
expect(seg.className).toMatch(/connector(Fill|Empty)H/);
|
|
741
|
+
expect(seg.className).not.toMatch(/connector(Fill|Empty)V/);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
// Progress splits at the current step: the segment arriving at it is
|
|
745
|
+
// filled, the one leaving it is not.
|
|
746
|
+
const current = Array.from(
|
|
747
|
+
screen.getByTestId('b').querySelectorAll('[class*="connectorTrack"]'),
|
|
748
|
+
);
|
|
749
|
+
expect(current[0].className).toContain('connectorFillH');
|
|
750
|
+
expect(current[1].className).toContain('connectorEmptyH');
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// One change animates the connector: advancing a single step. jsdom runs no
|
|
755
|
+
// transitions, so what is asserted here is the schedule one would run on.
|
|
756
|
+
// Every segment's slice is expressed in one unit — the node-to-node span —
|
|
757
|
+
// so a slice reads as `{start, length}` in spans, a segment that lands at
|
|
758
|
+
// once reads as a pair of zeros, and a span stitched from several segments
|
|
759
|
+
// is contiguous when its slices abut in track order.
|
|
760
|
+
describe('connector fill motion', () => {
|
|
761
|
+
const separated = (activeStep: number) => (
|
|
762
|
+
<Stepper activeStep={activeStep} orientation="horizontal">
|
|
763
|
+
<Step step={0} label="A" data-testid="a" />
|
|
764
|
+
<Step step={1} label="B" data-testid="b" />
|
|
765
|
+
<Step step={2} label="C" data-testid="c" />
|
|
766
|
+
<Step step={3} label="D" data-testid="d" />
|
|
767
|
+
<Step step={4} label="E" data-testid="e" />
|
|
768
|
+
</Stepper>
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
// StyleX routes dynamic values through hashed custom properties, whose
|
|
772
|
+
// names are not readable by hand. Recover the mapping from the one change
|
|
773
|
+
// that animates — the only one where the two carry different values. The
|
|
774
|
+
// probe mounts alongside whatever the calling test rendered, so it needs a
|
|
775
|
+
// test id of its own to be found.
|
|
776
|
+
const timingVarNames = (() => {
|
|
777
|
+
let cached: {duration: string; delay: string} | null = null;
|
|
778
|
+
return () => {
|
|
779
|
+
if (cached) {
|
|
780
|
+
return cached;
|
|
781
|
+
}
|
|
782
|
+
const steps = (
|
|
783
|
+
<>
|
|
784
|
+
<Step step={0} label="A" />
|
|
785
|
+
<Step step={1} label="B" data-testid="timing-probe" />
|
|
786
|
+
</>
|
|
787
|
+
);
|
|
788
|
+
const probe = render(<Stepper activeStep={0}>{steps}</Stepper>);
|
|
789
|
+
probe.rerender(<Stepper activeStep={1}>{steps}</Stepper>);
|
|
790
|
+
const el = probe
|
|
791
|
+
.getByTestId('timing-probe')
|
|
792
|
+
.querySelector('[class*="connectorTiming"]') as HTMLElement;
|
|
793
|
+
const found: Record<string, string> = {};
|
|
794
|
+
for (const name of Array.from(el.style)) {
|
|
795
|
+
const value = el.style.getPropertyValue(name).trim();
|
|
796
|
+
if (value === 'var(--duration-medium)') {
|
|
797
|
+
found.duration = name;
|
|
798
|
+
} else if (value === '0s') {
|
|
799
|
+
found.delay = name;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
probe.unmount();
|
|
803
|
+
cached = found as {duration: string; delay: string};
|
|
804
|
+
return cached;
|
|
805
|
+
};
|
|
806
|
+
})();
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* A slice as a multiple of one span: `0s` is none of a span, a bare span
|
|
810
|
+
* expression is all of one, and `calc(<span> * n)` is n of them.
|
|
811
|
+
*/
|
|
812
|
+
const spans = (value: string): number => {
|
|
813
|
+
if (value === '0s') {
|
|
814
|
+
return 0;
|
|
815
|
+
}
|
|
816
|
+
const scaled = value.match(/\* ([\d.]+)\)$/);
|
|
817
|
+
return scaled ? Number(scaled[1]) : 1;
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
/** Every connector in track order, as `{start, length}` in spans. */
|
|
821
|
+
const schedule = (el: HTMLElement) =>
|
|
822
|
+
Array.from(
|
|
823
|
+
el.querySelectorAll<HTMLElement>('[class*="connectorTiming"]'),
|
|
824
|
+
).map(seg => {
|
|
825
|
+
const names = timingVarNames();
|
|
826
|
+
return {
|
|
827
|
+
start: spans(seg.style.getPropertyValue(names.delay).trim()),
|
|
828
|
+
length: spans(seg.style.getPropertyValue(names.duration).trim()),
|
|
829
|
+
};
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
/** Nothing in this subtree moves over time. */
|
|
833
|
+
const isInstant = (el: HTMLElement) =>
|
|
834
|
+
schedule(el).every(slice => slice.start === 0 && slice.length === 0);
|
|
835
|
+
|
|
836
|
+
/** The span expression itself, which carries the fill's duration. */
|
|
837
|
+
const spanExpression = (el: HTMLElement) => {
|
|
838
|
+
const raw = (
|
|
839
|
+
el.querySelector('[class*="connectorTiming"]') as HTMLElement
|
|
840
|
+
).style
|
|
841
|
+
.getPropertyValue(timingVarNames().duration)
|
|
842
|
+
.trim();
|
|
843
|
+
return raw.replace(/^calc\((.*) \* [\d.]+\)$/, '$1');
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* The timing functions the fills in this subtree run on. Unlike the
|
|
848
|
+
* duration and the delay this is a static style, so it lives in the
|
|
849
|
+
* injected stylesheet under one of each segment's atomic classes rather
|
|
850
|
+
* than in an inline custom property.
|
|
851
|
+
*/
|
|
852
|
+
const fillEasings = (el: HTMLElement) => {
|
|
853
|
+
const found = new Set<string>();
|
|
854
|
+
for (const seg of Array.from(
|
|
855
|
+
el.querySelectorAll<HTMLElement>('[class*="connectorTrack"]'),
|
|
856
|
+
)) {
|
|
857
|
+
const classes = new Set(seg.className.split(/\s+/));
|
|
858
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
859
|
+
for (const rule of Array.from(sheet.cssRules)) {
|
|
860
|
+
if (!('selectorText' in rule)) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
const {selectorText, style} = rule as CSSStyleRule;
|
|
864
|
+
const owner = selectorText.match(/^\.([\w-]+)/);
|
|
865
|
+
if (
|
|
866
|
+
owner == null ||
|
|
867
|
+
!classes.has(owner[1]) ||
|
|
868
|
+
!selectorText.endsWith('::before')
|
|
869
|
+
) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const value = style
|
|
873
|
+
.getPropertyValue('transition-timing-function')
|
|
874
|
+
.trim();
|
|
875
|
+
if (value !== '') {
|
|
876
|
+
found.add(value);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return [...found];
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
it('animates the one span a single forward step crosses', () => {
|
|
885
|
+
const {rerender, getByTestId} = render(separated(0));
|
|
886
|
+
rerender(separated(1));
|
|
887
|
+
// The bar arriving at the new step fills across the whole span.
|
|
888
|
+
expect(schedule(getByTestId('b'))).toEqual([{start: 0, length: 1}]);
|
|
889
|
+
// Every other bar is untouched, so it is handed no timing that a later
|
|
890
|
+
// change could inherit as a stale delay.
|
|
891
|
+
for (const id of ['a', 'c', 'd', 'e']) {
|
|
892
|
+
expect(isInstant(getByTestId(id))).toBe(true);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('fills that span over the medium duration', () => {
|
|
897
|
+
// A fill crossing a whole segment is a spatial change, not a
|
|
898
|
+
// micro-interaction, so it sits above the fast band.
|
|
899
|
+
const {rerender, getByTestId} = render(separated(0));
|
|
900
|
+
rerender(separated(1));
|
|
901
|
+
expect(spanExpression(getByTestId('b'))).toBe('var(--duration-medium)');
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('fills linearly so a stitched span reads as one front', () => {
|
|
905
|
+
// An on-track span is drawn by two segments, three where a content slot
|
|
906
|
+
// splits it. A curve applied per segment restarts its deceleration at
|
|
907
|
+
// every seam, and the seam becomes the most visible part of the result.
|
|
908
|
+
const {container} = render(separated(0));
|
|
909
|
+
expect(fillEasings(container.firstElementChild as HTMLElement)).toEqual([
|
|
910
|
+
'linear',
|
|
911
|
+
]);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('lands a forward jump of more than one step at once', () => {
|
|
915
|
+
// A jump is a navigation rather than a progression: sweeping a front
|
|
916
|
+
// across the crossed segments makes the user sit out a journey they
|
|
917
|
+
// asked to skip.
|
|
918
|
+
const {rerender, getByTestId} = render(separated(0));
|
|
919
|
+
rerender(separated(3));
|
|
920
|
+
for (const id of ['a', 'b', 'c', 'd', 'e']) {
|
|
921
|
+
expect(isInstant(getByTestId(id))).toBe(true);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('lands a backward change at once, one step or several', () => {
|
|
926
|
+
// Retracting is the same arithmetic as filling, but it ends on a
|
|
927
|
+
// shrinking stub of accent rather than a nearly-full bar, and a remnant
|
|
928
|
+
// still on the track reads as unfinished however briefly it is there.
|
|
929
|
+
for (const to of [2, 0]) {
|
|
930
|
+
const view = render(separated(3));
|
|
931
|
+
view.rerender(separated(to));
|
|
932
|
+
for (const id of ['a', 'b', 'c', 'd', 'e']) {
|
|
933
|
+
expect(isInstant(view.getByTestId(id))).toBe(true);
|
|
934
|
+
}
|
|
935
|
+
view.unmount();
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it('leaves every segment instant on mount', () => {
|
|
940
|
+
// A stepper opening mid-flow has no previous step to have travelled
|
|
941
|
+
// from, so its completed segments must paint filled at once rather than
|
|
942
|
+
// replaying the history that would have produced them.
|
|
943
|
+
const {getByTestId} = render(separated(3));
|
|
944
|
+
for (const id of ['a', 'b', 'c', 'd', 'e']) {
|
|
945
|
+
expect(isInstant(getByTestId(id))).toBe(true);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
const onTrack = (activeStep: number) => (
|
|
950
|
+
<Stepper
|
|
951
|
+
activeStep={activeStep}
|
|
952
|
+
orientation="horizontal"
|
|
953
|
+
indicatorPosition="on-track">
|
|
954
|
+
<Step step={0} label="A" data-testid="a" />
|
|
955
|
+
<Step step={1} label="B" data-testid="b" />
|
|
956
|
+
<Step step={2} label="C" data-testid="c" />
|
|
957
|
+
</Stepper>
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
it('runs the leaving half of an on-track span before the arriving half', () => {
|
|
961
|
+
const {rerender, getByTestId} = render(onTrack(0));
|
|
962
|
+
rerender(onTrack(1));
|
|
963
|
+
// The span is drawn by two steps. Left alone both halves flip together
|
|
964
|
+
// and the gap between two nodes reads as two dashes converging on the
|
|
965
|
+
// space between them; split, the one leaving the near node runs first
|
|
966
|
+
// and the pair reads as one stretch of track filling.
|
|
967
|
+
const [aCap, aRail] = schedule(getByTestId('a'));
|
|
968
|
+
const [bCap, bRail] = schedule(getByTestId('b'));
|
|
969
|
+
expect(aRail).toEqual({start: 0, length: 0.5});
|
|
970
|
+
expect(bCap).toEqual({start: 0.5, length: 0.5});
|
|
971
|
+
// The segments either side of the span are not part of it.
|
|
972
|
+
expect(aCap).toEqual({start: 0, length: 0});
|
|
973
|
+
expect(bRail).toEqual({start: 0, length: 0});
|
|
974
|
+
expect(isInstant(getByTestId('c'))).toBe(true);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
const verticalWithContent = (activeStep: number) => (
|
|
978
|
+
<Stepper
|
|
979
|
+
activeStep={activeStep}
|
|
980
|
+
orientation="vertical"
|
|
981
|
+
indicatorPosition="on-track">
|
|
982
|
+
<Step step={0} label="A" data-testid="a" />
|
|
983
|
+
<Step step={1} label="B" data-testid="b">
|
|
984
|
+
<button type="button">Do it</button>
|
|
985
|
+
</Step>
|
|
986
|
+
<Step step={2} label="C" data-testid="c" />
|
|
987
|
+
</Stepper>
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
it('threads a content slot segment into its span between rail and cap', () => {
|
|
991
|
+
const {rerender, getByTestId} = render(verticalWithContent(1));
|
|
992
|
+
rerender(verticalWithContent(2));
|
|
993
|
+
// A vertical step with content draws a third segment down the side of
|
|
994
|
+
// the slot, so the span leaving it is stitched from three pieces and all
|
|
995
|
+
// three have to fall in track order. The rail and cap are unequal
|
|
996
|
+
// lengths, so the slices are unequal too.
|
|
997
|
+
const [bCap, bRail, bContent] = schedule(getByTestId('b'));
|
|
998
|
+
const [cCap] = schedule(getByTestId('c'));
|
|
999
|
+
expect(bRail).toEqual({start: 0, length: 0.175});
|
|
1000
|
+
expect(bContent).toEqual({start: 0.175, length: 0.525});
|
|
1001
|
+
expect(cCap).toEqual({start: 0.7, length: 0.3});
|
|
1002
|
+
// The span arriving at this step belongs to the previous change.
|
|
1003
|
+
expect(bCap).toEqual({start: 0, length: 0});
|
|
1004
|
+
expect(isInstant(getByTestId('a'))).toBe(true);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
it('tracks the previous step correctly under StrictMode double rendering', () => {
|
|
1008
|
+
// The previous step is derived during render from state, so StrictMode
|
|
1009
|
+
// invoking the render twice must queue the same successor both times
|
|
1010
|
+
// rather than losing a step or advancing two — which would turn a single
|
|
1011
|
+
// forward step into a two-step jump and stop it animating.
|
|
1012
|
+
const {rerender, getByTestId} = render(
|
|
1013
|
+
<StrictMode>{separated(0)}</StrictMode>,
|
|
1014
|
+
);
|
|
1015
|
+
rerender(<StrictMode>{separated(1)}</StrictMode>);
|
|
1016
|
+
expect(schedule(getByTestId('b'))).toEqual([{start: 0, length: 1}]);
|
|
1017
|
+
// A second step measures from step 1, not from the original mount, so
|
|
1018
|
+
// the next span animates and the one behind it stops.
|
|
1019
|
+
rerender(<StrictMode>{separated(2)}</StrictMode>);
|
|
1020
|
+
expect(schedule(getByTestId('c'))).toEqual([{start: 0, length: 1}]);
|
|
1021
|
+
expect(isInstant(getByTestId('b'))).toBe(true);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
});
|