@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241
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/dist/Avatar/Avatar.d.ts +6 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +46 -12
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
- package/dist/Breadcrumbs/index.d.ts +1 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +2 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +8 -8
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +10 -3
- package/dist/CheckboxInput/CheckboxInput.js +4 -4
- package/dist/Citation/Citation.d.ts +22 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -6
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +13 -7
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +3 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +42 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +14 -5
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +3 -1
- package/dist/DateInput/DateInput.d.ts +1 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +31 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -19
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/inputStyles.stylex.d.ts +1 -0
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +1 -0
- package/dist/FieldStatus/FieldStatus.d.ts +4 -1
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/FileInput/FileInput.d.ts +1 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +91 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +15 -9
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +65 -40
- package/dist/NumberInput/NumberInput.d.ts +1 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +20 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.js +4 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +49 -19
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +58 -16
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +5 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +13 -10
- package/dist/SideNav/SideNavItem.d.ts +1 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +12 -10
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +87 -34
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +11 -2
- package/dist/Switch/Switch.d.ts +8 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +160 -91
- package/dist/TabList/TabList.d.ts +2 -11
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +5 -9
- package/dist/TabList/TabListContext.d.ts +0 -5
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +0 -5
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +1 -0
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -19
- package/dist/TextInput/TextInput.d.ts +1 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +18 -17
- package/dist/TimeInput/TimeInput.d.ts +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Token/Token.d.ts +30 -3
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +17 -2
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -10
- package/dist/Typeahead/Typeahead.js +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +20 -10
- package/dist/astryx.css +38 -14
- package/dist/astryx.umd.js +52 -52
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +76 -0
- package/locales/pseudo.json +57 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +18 -4
- package/src/Avatar/Avatar.test.tsx +106 -0
- package/src/Avatar/Avatar.tsx +62 -10
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.tsx +4 -4
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/Citation/Citation.doc.mjs +4 -3
- package/src/Citation/Citation.test.tsx +97 -0
- package/src/Citation/Citation.tsx +43 -11
- package/src/CodeBlock/CodeBlock.tsx +35 -8
- package/src/Collapsible/Collapsible.doc.mjs +1 -0
- package/src/Collapsible/Collapsible.test.tsx +7 -0
- package/src/Collapsible/Collapsible.tsx +16 -6
- package/src/CommandPalette/CommandPalette.test.tsx +95 -1
- package/src/CommandPalette/CommandPalette.tsx +41 -2
- package/src/ContextMenu/ContextMenu.test.tsx +42 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +15 -5
- package/src/DateInput/DateInput.test.tsx +210 -3
- package/src/DateInput/DateInput.tsx +41 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
- package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
- package/src/DateRangeInput/DateRangeInput.tsx +29 -27
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.tsx +10 -4
- package/src/FileInput/FileInput.doc.mjs +13 -7
- package/src/FileInput/FileInput.test.tsx +84 -16
- package/src/FileInput/FileInput.tsx +96 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
- package/src/MultiSelector/MultiSelector.test.tsx +306 -3
- package/src/MultiSelector/MultiSelector.tsx +88 -57
- package/src/NumberInput/NumberInput.doc.mjs +11 -5
- package/src/NumberInput/NumberInput.test.tsx +48 -0
- package/src/NumberInput/NumberInput.tsx +25 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +4 -4
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
- package/src/Selector/Selector.doc.mjs +8 -0
- package/src/Selector/Selector.test.tsx +328 -3
- package/src/Selector/Selector.tsx +70 -19
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Slider/Slider.doc.mjs +6 -0
- package/src/Slider/Slider.test.tsx +240 -12
- package/src/Slider/Slider.tsx +96 -23
- package/src/Spinner/Spinner.test.tsx +10 -8
- package/src/Spinner/Spinner.tsx +15 -5
- package/src/Switch/Switch.doc.mjs +142 -42
- package/src/Switch/Switch.test.tsx +22 -0
- package/src/Switch/Switch.tsx +90 -33
- package/src/TabList/TabList.doc.mjs +0 -6
- package/src/TabList/TabList.test.tsx +3 -15
- package/src/TabList/TabList.tsx +6 -17
- package/src/TabList/TabListContext.ts +0 -6
- package/src/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
- package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
- package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +11 -5
- package/src/TextArea/TextArea.tsx +17 -26
- package/src/TextInput/TextInput.doc.mjs +11 -5
- package/src/TextInput/TextInput.test.tsx +130 -3
- package/src/TextInput/TextInput.tsx +18 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +11 -5
- package/src/TimeInput/TimeInput.tsx +14 -25
- package/src/Timestamp/Timestamp.doc.mjs +32 -6
- package/src/Timestamp/Timestamp.test.tsx +371 -0
- package/src/Timestamp/Timestamp.tsx +96 -93
- package/src/Timestamp/formatInstant.ts +218 -0
- package/src/Timestamp/index.ts +4 -0
- package/src/Timestamp/tooltipEntries.test.ts +552 -0
- package/src/Timestamp/tooltipEntries.ts +210 -0
- package/src/Token/Token.test.tsx +21 -15
- package/src/Token/Token.tsx +31 -14
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
- package/src/Tokenizer/Tokenizer.tsx +4 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +29 -14
- package/src/Typeahead/Typeahead.doc.mjs +6 -0
- package/src/Typeahead/Typeahead.test.tsx +27 -0
- package/src/Typeahead/Typeahead.tsx +1 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -545,6 +545,21 @@ function DefaultOption({option}: {option: SelectorOptionData}) {
|
|
|
545
545
|
);
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
// Case-insensitive substring match for a single option. The one predicate used
|
|
549
|
+
// by both the flat filter (count + keyboard nav) and the grouped renderer, so
|
|
550
|
+
// what is shown while searching stays in lockstep with the announced count.
|
|
551
|
+
function optionMatchesQuery(
|
|
552
|
+
option: SelectorOptionData,
|
|
553
|
+
query: string,
|
|
554
|
+
): boolean {
|
|
555
|
+
if (!query) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
return (option.label ?? option.value)
|
|
559
|
+
.toLowerCase()
|
|
560
|
+
.includes(query.toLowerCase());
|
|
561
|
+
}
|
|
562
|
+
|
|
548
563
|
// Case-insensitive substring filter over the selectable options. Shared by the
|
|
549
564
|
// `filteredItems` memo (rendering) and the search-change handler, which needs
|
|
550
565
|
// the count for the *next* query synchronously to announce it exactly once per
|
|
@@ -556,10 +571,7 @@ function filterOptionsByQuery(
|
|
|
556
571
|
if (!query) {
|
|
557
572
|
return items;
|
|
558
573
|
}
|
|
559
|
-
|
|
560
|
-
return items.filter(item =>
|
|
561
|
-
(item.label ?? item.value).toLowerCase().includes(q),
|
|
562
|
-
);
|
|
574
|
+
return items.filter(item => optionMatchesQuery(item, query));
|
|
563
575
|
}
|
|
564
576
|
|
|
565
577
|
/**
|
|
@@ -947,16 +959,15 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
947
959
|
|
|
948
960
|
// Render all options (handling sections/dividers)
|
|
949
961
|
const renderOptions = useCallback(() => {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
return filteredItems.map((item, index) => renderItem(item, index));
|
|
962
|
+
const isSearching = hasSearch && Boolean(searchQuery);
|
|
963
|
+
|
|
964
|
+
// Nothing matched across every group/option: show the empty state.
|
|
965
|
+
if (isSearching && filteredItems.length === 0) {
|
|
966
|
+
return [
|
|
967
|
+
<div key="empty" {...stylex.props(styles.emptyState)}>
|
|
968
|
+
No results found
|
|
969
|
+
</div>,
|
|
970
|
+
];
|
|
960
971
|
}
|
|
961
972
|
|
|
962
973
|
let flatIndex = 0;
|
|
@@ -966,13 +977,27 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
966
977
|
const option = options[i];
|
|
967
978
|
|
|
968
979
|
if (isDivider(option)) {
|
|
980
|
+
// While searching, a standalone divider between groups would orphan
|
|
981
|
+
// itself once its neighbors are filtered out, so skip it.
|
|
982
|
+
if (isSearching) {
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
969
985
|
elements.push(<Divider key={`divider-${i}`} xstyle={styles.divider} />);
|
|
970
986
|
} else if (isSection(option)) {
|
|
971
987
|
const sectionItems: ReactNode[] = [];
|
|
972
988
|
for (const opt of option.options) {
|
|
973
|
-
|
|
989
|
+
const normalized = normalizeOption(opt);
|
|
990
|
+
if (isSearching && !optionMatchesQuery(normalized, searchQuery)) {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
sectionItems.push(renderItem(normalized, flatIndex));
|
|
974
994
|
flatIndex++;
|
|
975
995
|
}
|
|
996
|
+
// Hide a group entirely (header + wrapper) when none of its items
|
|
997
|
+
// match the query, so no header is left standing over nothing.
|
|
998
|
+
if (sectionItems.length === 0) {
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
976
1001
|
if (option.title) {
|
|
977
1002
|
elements.push(
|
|
978
1003
|
<Divider
|
|
@@ -988,7 +1013,11 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
988
1013
|
</div>,
|
|
989
1014
|
);
|
|
990
1015
|
} else if (isOptionData(option)) {
|
|
991
|
-
|
|
1016
|
+
const normalized = normalizeOption(option);
|
|
1017
|
+
if (isSearching && !optionMatchesQuery(normalized, searchQuery)) {
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
elements.push(renderItem(normalized, flatIndex));
|
|
992
1021
|
flatIndex++;
|
|
993
1022
|
}
|
|
994
1023
|
}
|
|
@@ -1017,7 +1046,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1017
1046
|
isDisabled && inputWrapperStyles.disabled,
|
|
1018
1047
|
!selectedItem && styles.triggerPlaceholder,
|
|
1019
1048
|
status && inputStatusBorderStyles[status.type],
|
|
1020
|
-
status && inputStatusHoverShadowStyles[status.type],
|
|
1049
|
+
status && !isDisabled && inputStatusHoverShadowStyles[status.type],
|
|
1021
1050
|
inputGroup && groupStyles.inGroup,
|
|
1022
1051
|
xstyle,
|
|
1023
1052
|
),
|
|
@@ -1080,7 +1109,17 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1080
1109
|
onClick={handleClear}
|
|
1081
1110
|
aria-label={t('@astryx.selector.clearLabel', {label})}
|
|
1082
1111
|
{...stylex.props(styles.clearButton)}>
|
|
1083
|
-
<Icon
|
|
1112
|
+
<Icon
|
|
1113
|
+
icon="close"
|
|
1114
|
+
size="sm"
|
|
1115
|
+
color="secondary"
|
|
1116
|
+
// Stable theme target on the clear glyph itself, so a theme can
|
|
1117
|
+
// restyle just this icon (color, size, hover) via `defineTheme`.
|
|
1118
|
+
// Same-element rules in @layer astryx-theme win over the icon's
|
|
1119
|
+
// own base color/size, which a button-level target could not
|
|
1120
|
+
// reach.
|
|
1121
|
+
{...themeProps('selector-clear-icon')}
|
|
1122
|
+
/>
|
|
1084
1123
|
</button>
|
|
1085
1124
|
)}
|
|
1086
1125
|
<span
|
|
@@ -1096,7 +1135,19 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1096
1135
|
color={STATUS_ICON_COLOR_MAP[status.type]}
|
|
1097
1136
|
/>
|
|
1098
1137
|
) : (
|
|
1099
|
-
<Icon
|
|
1138
|
+
<Icon
|
|
1139
|
+
icon="chevronDown"
|
|
1140
|
+
size="sm"
|
|
1141
|
+
color="inherit"
|
|
1142
|
+
// Stable theme target on the chevron glyph itself, so a theme can
|
|
1143
|
+
// restyle just this icon (color, size, hover) — and its
|
|
1144
|
+
// open/closed state — via `defineTheme`. Same-element rules in
|
|
1145
|
+
// @layer astryx-theme win over the icon's own base color/size,
|
|
1146
|
+
// which a button-level target could not reach.
|
|
1147
|
+
{...themeProps('selector-indicator-icon', {
|
|
1148
|
+
state: popover.isOpen ? 'expanded' : 'collapsed',
|
|
1149
|
+
})}
|
|
1150
|
+
/>
|
|
1100
1151
|
)}
|
|
1101
1152
|
</span>
|
|
1102
1153
|
</div>
|
package/src/SideNav/SideNav.tsx
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import type {BaseProps} from '../BaseProps';
|
|
32
32
|
import * as stylex from '@stylexjs/stylex';
|
|
33
33
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
34
|
-
import {
|
|
34
|
+
import {spacingVars} from '../theme/tokens.stylex';
|
|
35
35
|
import {mergeProps, mergeRefs} from '../utils';
|
|
36
36
|
import {
|
|
37
37
|
SideNavCollapseContext,
|
|
@@ -123,9 +123,6 @@ const styles = stylex.create({
|
|
|
123
123
|
paddingInline: spacingVars['--spacing-2'],
|
|
124
124
|
paddingBlockStart: spacingVars['--spacing-1'],
|
|
125
125
|
paddingBlockEnd: spacingVars['--spacing-2'],
|
|
126
|
-
borderBlockStartWidth: borderVars['--border-width'],
|
|
127
|
-
borderBlockStartStyle: 'solid',
|
|
128
|
-
borderBlockStartColor: colorVars['--color-border'],
|
|
129
126
|
},
|
|
130
127
|
footerRow: {
|
|
131
128
|
display: 'flex',
|
|
@@ -145,7 +142,6 @@ const styles = stylex.create({
|
|
|
145
142
|
alignItems: 'center',
|
|
146
143
|
},
|
|
147
144
|
stickyBottomCollapsed: {
|
|
148
|
-
borderBlockStart: 'none',
|
|
149
145
|
paddingBlockStart: 0,
|
|
150
146
|
},
|
|
151
147
|
// Drawer footer — pushed to bottom of the scrollable content area
|
|
@@ -155,9 +151,6 @@ const styles = stylex.create({
|
|
|
155
151
|
marginBlockStart: 'auto',
|
|
156
152
|
gap: spacingVars['--spacing-2'],
|
|
157
153
|
paddingBlockStart: spacingVars['--spacing-2'],
|
|
158
|
-
borderBlockStartWidth: borderVars['--border-width'],
|
|
159
|
-
borderBlockStartStyle: 'solid',
|
|
160
|
-
borderBlockStartColor: colorVars['--color-border'],
|
|
161
154
|
},
|
|
162
155
|
drawerFooterIcons: {
|
|
163
156
|
display: 'flex',
|
|
@@ -23,7 +23,7 @@ import {durationVars, easeVars} from '../theme/tokens.stylex';
|
|
|
23
23
|
import {getIcon} from '../Icon/globalIconRegistry';
|
|
24
24
|
import {Button} from '../Button';
|
|
25
25
|
import type {BaseProps} from '../BaseProps';
|
|
26
|
-
import {composeEventHandlers} from '../utils';
|
|
26
|
+
import {composeEventHandlers, rtlStyles} from '../utils';
|
|
27
27
|
import {
|
|
28
28
|
useSideNavCollapse,
|
|
29
29
|
type SideNavCollapseState,
|
|
@@ -131,12 +131,14 @@ export function SideNavCollapseButton({
|
|
|
131
131
|
onClick={composeEventHandlers(onClickProp, toggle)}
|
|
132
132
|
icon={
|
|
133
133
|
children ?? (
|
|
134
|
-
<span
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
<span {...stylex.props(rtlStyles.mirror)}>
|
|
135
|
+
<span
|
|
136
|
+
{...stylex.props(
|
|
137
|
+
styles.chevron,
|
|
138
|
+
isCollapsed && styles.chevronCollapsed,
|
|
139
|
+
)}>
|
|
140
|
+
{getIcon('chevronLeft')}
|
|
141
|
+
</span>
|
|
140
142
|
</span>
|
|
141
143
|
)
|
|
142
144
|
}
|
|
@@ -356,6 +356,8 @@ export function SideNavItem({
|
|
|
356
356
|
size = 'md',
|
|
357
357
|
'data-testid': testId,
|
|
358
358
|
ref,
|
|
359
|
+
xstyle,
|
|
360
|
+
...rest
|
|
359
361
|
}: SideNavItemProps) {
|
|
360
362
|
const t = useTranslator();
|
|
361
363
|
const {isCollapsed} = useSideNavCollapse();
|
|
@@ -502,7 +504,7 @@ export function SideNavItem({
|
|
|
502
504
|
// Items with children: popover trigger + popover
|
|
503
505
|
if (hasChildren) {
|
|
504
506
|
return (
|
|
505
|
-
<div {...stylex.props(styles.root)}>
|
|
507
|
+
<div {...stylex.props(styles.root, xstyle)}>
|
|
506
508
|
<button
|
|
507
509
|
ref={mergeRefs(ref, popover.triggerRef)}
|
|
508
510
|
type="button"
|
|
@@ -554,7 +556,7 @@ export function SideNavItem({
|
|
|
554
556
|
);
|
|
555
557
|
|
|
556
558
|
return (
|
|
557
|
-
<div ref={itemRef} {...stylex.props(styles.root)}>
|
|
559
|
+
<div ref={itemRef} {...stylex.props(styles.root, xstyle)}>
|
|
558
560
|
{collapsedElement}
|
|
559
561
|
<Tooltip content={label} placement="end" anchorRef={itemRef} />
|
|
560
562
|
</div>
|
|
@@ -670,7 +672,7 @@ export function SideNavItem({
|
|
|
670
672
|
}
|
|
671
673
|
|
|
672
674
|
const item = (
|
|
673
|
-
<div ref={itemRef} {...stylex.props(styles.root)}>
|
|
675
|
+
<div ref={itemRef} {...stylex.props(styles.root, xstyle)}>
|
|
674
676
|
{itemElement}
|
|
675
677
|
{hasChildren && !isCollapsed && (
|
|
676
678
|
<div
|
|
@@ -141,6 +141,12 @@ export const docs = {
|
|
|
141
141
|
type: 'string',
|
|
142
142
|
description: 'Tooltip text for an info icon displayed next to the label.',
|
|
143
143
|
},
|
|
144
|
+
{
|
|
145
|
+
name: 'width',
|
|
146
|
+
type: 'SizeValue',
|
|
147
|
+
description:
|
|
148
|
+
'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
|
|
149
|
+
},
|
|
144
150
|
{
|
|
145
151
|
name: 'xstyle',
|
|
146
152
|
type: 'StyleXStyles',
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* SYNC: When Slider.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import {useState} from 'react';
|
|
12
13
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
13
14
|
import {render, screen, act, fireEvent, waitFor} from '@testing-library/react';
|
|
14
15
|
import userEvent from '@testing-library/user-event';
|
|
@@ -50,23 +51,17 @@ beforeEach(() => {
|
|
|
50
51
|
describe('Slider', () => {
|
|
51
52
|
// --- Aria labels ---
|
|
52
53
|
|
|
53
|
-
it('single thumb
|
|
54
|
+
it('single thumb takes its accessible name from the label', () => {
|
|
54
55
|
render(<Slider label="Volume" value={50} />);
|
|
55
56
|
const slider = screen.getByRole('slider');
|
|
56
|
-
expect(slider).
|
|
57
|
+
expect(slider).toHaveAccessibleName('Volume');
|
|
57
58
|
});
|
|
58
59
|
|
|
59
|
-
it('range thumbs have
|
|
60
|
+
it('range thumbs have individual names composing with the group label', () => {
|
|
60
61
|
render(<Slider label="Price range" value={[20, 80] as [number, number]} />);
|
|
61
62
|
const sliders = screen.getAllByRole('slider');
|
|
62
|
-
expect(sliders[0]).
|
|
63
|
-
|
|
64
|
-
'Price range, minimum value',
|
|
65
|
-
);
|
|
66
|
-
expect(sliders[1]).toHaveAttribute(
|
|
67
|
-
'aria-label',
|
|
68
|
-
'Price range, maximum value',
|
|
69
|
-
);
|
|
63
|
+
expect(sliders[0]).toHaveAccessibleName('Minimum value');
|
|
64
|
+
expect(sliders[1]).toHaveAccessibleName('Maximum value');
|
|
70
65
|
});
|
|
71
66
|
|
|
72
67
|
it('sets aria-valuetext with formatValue', () => {
|
|
@@ -94,7 +89,9 @@ describe('Slider', () => {
|
|
|
94
89
|
render(<Slider label="Volume" value={value} min={0} max={100} />);
|
|
95
90
|
const slider = screen.getByRole('slider');
|
|
96
91
|
expect(slider).toHaveAttribute('aria-valuenow', String(expectedValue));
|
|
97
|
-
|
|
92
|
+
// Thumb positions via the logical `inset-inline-start` so it mirrors
|
|
93
|
+
// under RTL (see RTL Phase 4). In LTR this resolves to the left edge.
|
|
94
|
+
expect(slider).toHaveStyle({insetInlineStart: expectedPosition});
|
|
98
95
|
},
|
|
99
96
|
);
|
|
100
97
|
|
|
@@ -110,6 +107,102 @@ describe('Slider', () => {
|
|
|
110
107
|
const sliders = screen.getAllByRole('slider');
|
|
111
108
|
expect(sliders[0]).toHaveAttribute('aria-valuenow', '25');
|
|
112
109
|
expect(sliders[1]).toHaveAttribute('aria-valuenow', '75');
|
|
110
|
+
// Per the APG multi-thumb pattern, each thumb's bounds are constrained by
|
|
111
|
+
// its sibling: the lower thumb can't exceed the upper thumb's value and
|
|
112
|
+
// the upper thumb can't go below the lower thumb's value.
|
|
113
|
+
expect(sliders[0]).toHaveAttribute('aria-valuemin', '0');
|
|
114
|
+
expect(sliders[0]).toHaveAttribute('aria-valuemax', '75');
|
|
115
|
+
expect(sliders[1]).toHaveAttribute('aria-valuemin', '25');
|
|
116
|
+
expect(sliders[1]).toHaveAttribute('aria-valuemax', '100');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('range thumb bounds update after moving a thumb', async () => {
|
|
120
|
+
const user = userEvent.setup();
|
|
121
|
+
function ControlledRange() {
|
|
122
|
+
const [value, setValue] = useState<[number, number]>([25, 75]);
|
|
123
|
+
return (
|
|
124
|
+
<Slider
|
|
125
|
+
label="Range"
|
|
126
|
+
value={value}
|
|
127
|
+
onChange={setValue}
|
|
128
|
+
min={0}
|
|
129
|
+
max={100}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
render(<ControlledRange />);
|
|
134
|
+
const sliders = screen.getAllByRole('slider');
|
|
135
|
+
act(() => {
|
|
136
|
+
sliders[0].focus();
|
|
137
|
+
});
|
|
138
|
+
await user.keyboard('{ArrowRight}');
|
|
139
|
+
expect(sliders[0]).toHaveAttribute('aria-valuenow', '26');
|
|
140
|
+
// The upper thumb's floor tracks the lower thumb's new value.
|
|
141
|
+
expect(sliders[1]).toHaveAttribute('aria-valuemin', '26');
|
|
142
|
+
expect(sliders[0]).toHaveAttribute('aria-valuemax', '75');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('range thumb bounds include the minStepsBetweenThumbs gap', () => {
|
|
146
|
+
render(
|
|
147
|
+
<Slider
|
|
148
|
+
label="Range"
|
|
149
|
+
value={[20, 80] as [number, number]}
|
|
150
|
+
min={0}
|
|
151
|
+
max={100}
|
|
152
|
+
step={5}
|
|
153
|
+
minStepsBetweenThumbs={2}
|
|
154
|
+
/>,
|
|
155
|
+
);
|
|
156
|
+
const sliders = screen.getAllByRole('slider');
|
|
157
|
+
// minGap = 2 steps * 5 = 10
|
|
158
|
+
expect(sliders[0]).toHaveAttribute('aria-valuemax', '70');
|
|
159
|
+
expect(sliders[1]).toHaveAttribute('aria-valuemin', '30');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// --- Label association ---
|
|
163
|
+
|
|
164
|
+
// A <label htmlFor> must point at an existing form-associated element;
|
|
165
|
+
// div[role="slider"] is not labelable, so the Slider label must render as a
|
|
166
|
+
// group label instead (WCAG 1.3.1).
|
|
167
|
+
const LABELABLE_TAGS = new Set([
|
|
168
|
+
'BUTTON',
|
|
169
|
+
'INPUT',
|
|
170
|
+
'METER',
|
|
171
|
+
'OUTPUT',
|
|
172
|
+
'PROGRESS',
|
|
173
|
+
'SELECT',
|
|
174
|
+
'TEXTAREA',
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
function expectNoOrphanedLabels(container: HTMLElement) {
|
|
178
|
+
for (const labelEl of container.querySelectorAll('label[for]')) {
|
|
179
|
+
const target = document.getElementById(labelEl.getAttribute('for')!);
|
|
180
|
+
expect(target).not.toBeNull();
|
|
181
|
+
expect(LABELABLE_TAGS.has(target!.tagName)).toBe(true);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
it('single mode renders the label as a group label naming the thumb', () => {
|
|
186
|
+
const {container} = render(<Slider label="Volume" value={50} />);
|
|
187
|
+
expectNoOrphanedLabels(container);
|
|
188
|
+
const slider = screen.getByRole('slider');
|
|
189
|
+
const labelledBy = slider.getAttribute('aria-labelledby');
|
|
190
|
+
expect(labelledBy).toBeTruthy();
|
|
191
|
+
expect(document.getElementById(labelledBy!)).toHaveTextContent('Volume');
|
|
192
|
+
expect(slider).toHaveAccessibleName('Volume');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('range mode labels the slider group via aria-labelledby', () => {
|
|
196
|
+
const {container} = render(
|
|
197
|
+
<Slider label="Price range" value={[20, 80] as [number, number]} />,
|
|
198
|
+
);
|
|
199
|
+
expectNoOrphanedLabels(container);
|
|
200
|
+
const group = screen.getByRole('group', {name: 'Price range'});
|
|
201
|
+
const labelledBy = group.getAttribute('aria-labelledby');
|
|
202
|
+
expect(labelledBy).toBeTruthy();
|
|
203
|
+
expect(document.getElementById(labelledBy!)).toHaveTextContent(
|
|
204
|
+
'Price range',
|
|
205
|
+
);
|
|
113
206
|
});
|
|
114
207
|
|
|
115
208
|
it('sets aria-orientation for vertical', () => {
|
|
@@ -301,6 +394,58 @@ describe('Slider', () => {
|
|
|
301
394
|
expect(handleChangeEnd).toHaveBeenCalledWith(55);
|
|
302
395
|
});
|
|
303
396
|
|
|
397
|
+
// --- Fractional step precision ---
|
|
398
|
+
|
|
399
|
+
it('emits exact decimal values for fractional steps on keyboard', async () => {
|
|
400
|
+
const user = userEvent.setup();
|
|
401
|
+
const handleChange = vi.fn();
|
|
402
|
+
const handleChangeEnd = vi.fn();
|
|
403
|
+
render(
|
|
404
|
+
<Slider
|
|
405
|
+
label="Opacity"
|
|
406
|
+
value={0.2}
|
|
407
|
+
min={0}
|
|
408
|
+
max={1}
|
|
409
|
+
step={0.1}
|
|
410
|
+
onChange={handleChange}
|
|
411
|
+
onChangeEnd={handleChangeEnd}
|
|
412
|
+
/>,
|
|
413
|
+
);
|
|
414
|
+
const slider = screen.getByRole('slider');
|
|
415
|
+
act(() => {
|
|
416
|
+
slider.focus();
|
|
417
|
+
});
|
|
418
|
+
await user.keyboard('{ArrowRight}');
|
|
419
|
+
// 0.2 + 0.1 must not surface binary float error (0.30000000000000004)
|
|
420
|
+
expect(handleChange).toHaveBeenCalledWith(0.3);
|
|
421
|
+
expect(handleChangeEnd).toHaveBeenCalledWith(0.3);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('emits exact decimal values for fractional steps in range mode', async () => {
|
|
425
|
+
const user = userEvent.setup();
|
|
426
|
+
const handleChange = vi.fn();
|
|
427
|
+
const handleChangeEnd = vi.fn();
|
|
428
|
+
render(
|
|
429
|
+
<Slider
|
|
430
|
+
label="Range"
|
|
431
|
+
value={[0.2, 0.6] as [number, number]}
|
|
432
|
+
min={0}
|
|
433
|
+
max={1}
|
|
434
|
+
step={0.1}
|
|
435
|
+
onChange={handleChange}
|
|
436
|
+
onChangeEnd={handleChangeEnd}
|
|
437
|
+
/>,
|
|
438
|
+
);
|
|
439
|
+
const sliders = screen.getAllByRole('slider');
|
|
440
|
+
act(() => {
|
|
441
|
+
sliders[1].focus();
|
|
442
|
+
});
|
|
443
|
+
await user.keyboard('{ArrowRight}');
|
|
444
|
+
// 0.6 + 0.1 snaps to 7 * 0.1, which is 0.7000000000000001 without rounding
|
|
445
|
+
expect(handleChange).toHaveBeenCalledWith([0.2, 0.7]);
|
|
446
|
+
expect(handleChangeEnd).toHaveBeenCalledWith([0.2, 0.7]);
|
|
447
|
+
});
|
|
448
|
+
|
|
304
449
|
it('fires onChangeEnd on keyboard Home/End with correct value', async () => {
|
|
305
450
|
const user = userEvent.setup();
|
|
306
451
|
const handleChangeEnd = vi.fn();
|
|
@@ -416,6 +561,89 @@ describe('Slider', () => {
|
|
|
416
561
|
expect(document.activeElement).toBe(slider);
|
|
417
562
|
});
|
|
418
563
|
|
|
564
|
+
// --- RTL pointer value mapping (RTL Phase 4) ---
|
|
565
|
+
// Contract: the inline-start edge (value = min) is the RIGHT edge under RTL,
|
|
566
|
+
// so the same physical click maps to the mirrored value relative to LTR.
|
|
567
|
+
// `valueDisplay="none"` keeps the thumb a direct child of the track element
|
|
568
|
+
// (no Tooltip wrapper) so the mocked rect lands on trackRef.
|
|
569
|
+
|
|
570
|
+
it('maps a track click to the LTR value in the default direction', () => {
|
|
571
|
+
const handleChange = vi.fn();
|
|
572
|
+
render(
|
|
573
|
+
<Slider
|
|
574
|
+
label="Volume"
|
|
575
|
+
value={0}
|
|
576
|
+
min={0}
|
|
577
|
+
max={100}
|
|
578
|
+
valueDisplay="none"
|
|
579
|
+
onChange={handleChange}
|
|
580
|
+
/>,
|
|
581
|
+
);
|
|
582
|
+
const track = screen.getByRole('slider').parentElement!;
|
|
583
|
+
track.getBoundingClientRect = () =>
|
|
584
|
+
({
|
|
585
|
+
left: 0,
|
|
586
|
+
top: 0,
|
|
587
|
+
right: 200,
|
|
588
|
+
bottom: 20,
|
|
589
|
+
width: 200,
|
|
590
|
+
height: 20,
|
|
591
|
+
x: 0,
|
|
592
|
+
y: 0,
|
|
593
|
+
toJSON: () => {},
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Click at 25% of the track from the left (x=50 of 200) → value 25 in LTR.
|
|
597
|
+
fireEvent.pointerDown(track, {clientX: 50, clientY: 10, pointerId: 1});
|
|
598
|
+
expect(handleChange).toHaveBeenLastCalledWith(25);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('mirrors a track click to the RTL value when the track is rtl', () => {
|
|
602
|
+
const handleChange = vi.fn();
|
|
603
|
+
render(
|
|
604
|
+
<Slider
|
|
605
|
+
label="Volume"
|
|
606
|
+
value={0}
|
|
607
|
+
min={0}
|
|
608
|
+
max={100}
|
|
609
|
+
valueDisplay="none"
|
|
610
|
+
onChange={handleChange}
|
|
611
|
+
/>,
|
|
612
|
+
);
|
|
613
|
+
const track = screen.getByRole('slider').parentElement!;
|
|
614
|
+
track.getBoundingClientRect = () =>
|
|
615
|
+
({
|
|
616
|
+
left: 0,
|
|
617
|
+
top: 0,
|
|
618
|
+
right: 200,
|
|
619
|
+
bottom: 20,
|
|
620
|
+
width: 200,
|
|
621
|
+
height: 20,
|
|
622
|
+
x: 0,
|
|
623
|
+
y: 0,
|
|
624
|
+
toJSON: () => {},
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Force the track's computed direction to rtl (isRtlElement reads this).
|
|
628
|
+
const realGetComputedStyle = window.getComputedStyle;
|
|
629
|
+
const gcsSpy = vi
|
|
630
|
+
.spyOn(window, 'getComputedStyle')
|
|
631
|
+
.mockImplementation((el: Element, pseudo?: string | null) => {
|
|
632
|
+
if (el === track) {
|
|
633
|
+
return {direction: 'rtl'} as CSSStyleDeclaration;
|
|
634
|
+
}
|
|
635
|
+
return realGetComputedStyle(el, pseudo ?? undefined);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Same physical click at 25% from the left (x=50). Under RTL the inline
|
|
639
|
+
// start is the right edge, so fraction = (right - x)/width = 150/200 = 0.75
|
|
640
|
+
// → value 75 (the mirror of the LTR value 25).
|
|
641
|
+
fireEvent.pointerDown(track, {clientX: 50, clientY: 10, pointerId: 1});
|
|
642
|
+
expect(handleChange).toHaveBeenLastCalledWith(75);
|
|
643
|
+
|
|
644
|
+
gcsSpy.mockRestore();
|
|
645
|
+
});
|
|
646
|
+
|
|
419
647
|
// --- Mark label click snapping ---
|
|
420
648
|
|
|
421
649
|
it('clicking a mark label snaps to that mark value, not pointer position', () => {
|