@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
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
11
|
-
import {render, screen, waitFor, fireEvent} from '@testing-library/react';
|
|
11
|
+
import {render, screen, waitFor, fireEvent, act} from '@testing-library/react';
|
|
12
12
|
import {CommandPalette} from './CommandPalette';
|
|
13
13
|
import {createStaticSource} from '@astryxdesign/core/Typeahead';
|
|
14
14
|
import type {SearchSource, SearchableItem} from '@astryxdesign/core/Typeahead';
|
|
15
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
15
16
|
|
|
16
17
|
const simpleSource = createStaticSource([
|
|
17
18
|
{id: 'home', label: 'Home'},
|
|
@@ -35,6 +36,9 @@ beforeEach(() => {
|
|
|
35
36
|
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
36
37
|
this.removeAttribute('open');
|
|
37
38
|
});
|
|
39
|
+
// The live regions are module-level singletons; reset them so
|
|
40
|
+
// announcements from one test don't leak into the next.
|
|
41
|
+
__resetLiveRegionsForTest();
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
describe('CommandPalette', () => {
|
|
@@ -269,4 +273,94 @@ describe('CommandPalette', () => {
|
|
|
269
273
|
expect(screen.getByText('No results')).toBeInTheDocument(),
|
|
270
274
|
);
|
|
271
275
|
});
|
|
276
|
+
|
|
277
|
+
describe('screen reader announcements', () => {
|
|
278
|
+
const politeRegion = () =>
|
|
279
|
+
document.querySelector('[data-astryx-live-region="polite"]');
|
|
280
|
+
|
|
281
|
+
it('announces the result count politely after typing a query', async () => {
|
|
282
|
+
render(
|
|
283
|
+
<CommandPalette
|
|
284
|
+
isOpen={true}
|
|
285
|
+
onOpenChange={() => {}}
|
|
286
|
+
searchSource={simpleSource}
|
|
287
|
+
/>,
|
|
288
|
+
);
|
|
289
|
+
await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
|
|
290
|
+
// "e" matches both Home and Settings.
|
|
291
|
+
fireEvent.change(screen.getByRole('combobox'), {target: {value: 'e'}});
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(politeRegion()).toHaveTextContent('2 results');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('announces the singular form when one item matches', async () => {
|
|
298
|
+
render(
|
|
299
|
+
<CommandPalette
|
|
300
|
+
isOpen={true}
|
|
301
|
+
onOpenChange={() => {}}
|
|
302
|
+
searchSource={simpleSource}
|
|
303
|
+
/>,
|
|
304
|
+
);
|
|
305
|
+
await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
|
|
306
|
+
// "set" matches only Settings. Anchored so it cannot pass on "1 results".
|
|
307
|
+
fireEvent.change(screen.getByRole('combobox'), {target: {value: 'set'}});
|
|
308
|
+
await waitFor(() => {
|
|
309
|
+
expect(politeRegion()).toHaveTextContent(/^1 result$/);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('announces the empty state with the query when nothing matches', async () => {
|
|
314
|
+
render(
|
|
315
|
+
<CommandPalette
|
|
316
|
+
isOpen={true}
|
|
317
|
+
onOpenChange={() => {}}
|
|
318
|
+
searchSource={simpleSource}
|
|
319
|
+
/>,
|
|
320
|
+
);
|
|
321
|
+
await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
|
|
322
|
+
fireEvent.change(screen.getByRole('combobox'), {target: {value: 'zzz'}});
|
|
323
|
+
await waitFor(() => {
|
|
324
|
+
expect(politeRegion()).toHaveTextContent('No results for zzz');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('announces loading politely while a search is in flight', async () => {
|
|
329
|
+
// A source whose searches never resolve, so the palette stays busy and
|
|
330
|
+
// only the loading announcement can reach the live region.
|
|
331
|
+
const source: SearchSource = {
|
|
332
|
+
bootstrap: () => [],
|
|
333
|
+
async search(): Promise<SearchableItem[]> {
|
|
334
|
+
return new Promise<SearchableItem[]>(() => {});
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
render(
|
|
338
|
+
<CommandPalette
|
|
339
|
+
isOpen={true}
|
|
340
|
+
onOpenChange={() => {}}
|
|
341
|
+
searchSource={source}
|
|
342
|
+
/>,
|
|
343
|
+
);
|
|
344
|
+
fireEvent.change(screen.getByRole('combobox'), {target: {value: 'z'}});
|
|
345
|
+
await waitFor(() => {
|
|
346
|
+
expect(politeRegion()).toHaveTextContent('Loading');
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('does not announce on initial open (bootstrap)', async () => {
|
|
351
|
+
render(
|
|
352
|
+
<CommandPalette
|
|
353
|
+
isOpen={true}
|
|
354
|
+
onOpenChange={() => {}}
|
|
355
|
+
searchSource={simpleSource}
|
|
356
|
+
/>,
|
|
357
|
+
);
|
|
358
|
+
await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
|
|
359
|
+
// Flush effects and any pending live-region rAF writes.
|
|
360
|
+
await act(async () => {
|
|
361
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
362
|
+
});
|
|
363
|
+
expect(politeRegion()?.textContent ?? '').toBe('');
|
|
364
|
+
});
|
|
365
|
+
});
|
|
272
366
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'use client';
|
|
4
4
|
/**
|
|
5
5
|
* @file CommandPalette.tsx
|
|
6
|
-
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox
|
|
6
|
+
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox, useAnnounce
|
|
7
7
|
* @output Exports CommandPalette root component and props
|
|
8
8
|
* @position Core root component; dialog shell with searchSource-driven items
|
|
9
9
|
*
|
|
@@ -36,6 +36,7 @@ import {CommandPaletteInput} from './CommandPaletteInput';
|
|
|
36
36
|
import {CommandPaletteFooter} from './CommandPaletteFooter';
|
|
37
37
|
import {CommandPaletteEmpty} from './CommandPaletteEmpty';
|
|
38
38
|
import type {BaseProps} from '../BaseProps';
|
|
39
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
39
40
|
import {useTranslator} from '../i18n';
|
|
40
41
|
|
|
41
42
|
export interface CommandPaletteProps<
|
|
@@ -291,6 +292,13 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
|
|
|
291
292
|
const isBusy = isPending;
|
|
292
293
|
const searchVersionRef = useRef(0);
|
|
293
294
|
|
|
295
|
+
// Announce search status to screen readers through the shared polite live
|
|
296
|
+
// region (comboboxes-7 announce path, mirroring Selector / BaseTypeahead).
|
|
297
|
+
// The busy spinner and the empty states are otherwise purely visual, so a
|
|
298
|
+
// screen-reader user typing a query would hear nothing when loading starts
|
|
299
|
+
// or results disappear.
|
|
300
|
+
const announce = useAnnounce();
|
|
301
|
+
|
|
294
302
|
const value = controlledValue ?? internalValue;
|
|
295
303
|
|
|
296
304
|
const setValue = useCallback(
|
|
@@ -318,8 +326,12 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
|
|
|
318
326
|
setInternalValue('');
|
|
319
327
|
}
|
|
320
328
|
searchSource.cancel?.();
|
|
329
|
+
// Clear any lingering result / loading announcement when the palette
|
|
330
|
+
// closes so stale status text does not linger in the a11y tree
|
|
331
|
+
// (matching Selector's onHide).
|
|
332
|
+
announce('');
|
|
321
333
|
onOpenChange(false);
|
|
322
|
-
}, [onOpenChange, searchSource, controlledValue]);
|
|
334
|
+
}, [onOpenChange, searchSource, controlledValue, announce]);
|
|
323
335
|
|
|
324
336
|
const selectItem = useCallback(
|
|
325
337
|
(itemValue: string) => {
|
|
@@ -355,6 +367,14 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
|
|
|
355
367
|
startTransition(async () => {
|
|
356
368
|
const isBootstrap = query === '';
|
|
357
369
|
|
|
370
|
+
// Loading started for a user query: tell screen-reader users the
|
|
371
|
+
// spinner appeared. The polite region coalesces rapid updates, so for
|
|
372
|
+
// fast sources the result-count announcement below simply replaces
|
|
373
|
+
// this instead of stacking one "Loading" per keystroke.
|
|
374
|
+
if (!isBootstrap) {
|
|
375
|
+
announce(t('@astryx.commandPalette.loading'));
|
|
376
|
+
}
|
|
377
|
+
|
|
358
378
|
// Client-filter previous results for instant narrowing while fetch is in flight
|
|
359
379
|
if (!isBootstrap && searchResults.length > 0) {
|
|
360
380
|
const lower = query.toLowerCase().trim();
|
|
@@ -377,6 +397,23 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
|
|
|
377
397
|
setOptimisticResults(items);
|
|
378
398
|
setSearchResults(items);
|
|
379
399
|
|
|
400
|
+
// Announce the outcome from the search commit (not a reactive
|
|
401
|
+
// effect), matching Selector / BaseTypeahead: exactly one
|
|
402
|
+
// announcement per committed query, and the version check above
|
|
403
|
+
// already discards stale keystrokes. Bootstrap stays silent — the
|
|
404
|
+
// same role PowerSearch's mount guard plays — so opening the
|
|
405
|
+
// palette announces nothing; clearing the query only clears any
|
|
406
|
+
// lingering status text.
|
|
407
|
+
if (isBootstrap) {
|
|
408
|
+
announce('');
|
|
409
|
+
} else if (items.length === 0) {
|
|
410
|
+
announce(t('@astryx.commandPalette.noResultsFor', {query}));
|
|
411
|
+
} else {
|
|
412
|
+
announce(
|
|
413
|
+
t('@astryx.commandPalette.resultCount', {count: items.length}),
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
380
417
|
// When opening with a preselected value, highlight it once
|
|
381
418
|
// bootstrap results arrive. No value → highlight stays at -1
|
|
382
419
|
// and ArrowDown naturally moves to the first item.
|
|
@@ -396,6 +433,8 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
|
|
|
396
433
|
value,
|
|
397
434
|
combobox,
|
|
398
435
|
setOptimisticResults,
|
|
436
|
+
announce,
|
|
437
|
+
t,
|
|
399
438
|
],
|
|
400
439
|
);
|
|
401
440
|
|
|
@@ -803,4 +803,46 @@ describe('ContextMenu keyboard access for menuitemradio/menuitemcheckbox (#3829)
|
|
|
803
803
|
screen.getByRole('menuitemradio', {name: 'Oldest', hidden: true}),
|
|
804
804
|
).toHaveFocus();
|
|
805
805
|
});
|
|
806
|
+
|
|
807
|
+
it('renders a submenu from a nested items array and keyboard-reaches an item after it', () => {
|
|
808
|
+
// Same inline-flyout scoping the DropdownMenu fix covers: the submenu's
|
|
809
|
+
// items must not pollute the ContextMenu's roving order, and an item after
|
|
810
|
+
// the submenu row must stay keyboard-reachable.
|
|
811
|
+
const onDelete = vi.fn();
|
|
812
|
+
render(
|
|
813
|
+
<ContextMenu
|
|
814
|
+
items={[
|
|
815
|
+
{label: 'Cut', onClick: () => {}},
|
|
816
|
+
{
|
|
817
|
+
label: 'Move to',
|
|
818
|
+
items: [
|
|
819
|
+
{label: 'Folder A', onClick: () => {}},
|
|
820
|
+
{label: 'Folder B', onClick: () => {}},
|
|
821
|
+
],
|
|
822
|
+
},
|
|
823
|
+
{type: 'divider'},
|
|
824
|
+
{label: 'Delete', onClick: onDelete},
|
|
825
|
+
]}>
|
|
826
|
+
<div>Right-click me</div>
|
|
827
|
+
</ContextMenu>,
|
|
828
|
+
);
|
|
829
|
+
fireEvent.contextMenu(screen.getByText('Right-click me'));
|
|
830
|
+
// Two role="menu" exist (the context menu + the inline submenu flyout);
|
|
831
|
+
// the context menu is the first in DOM order.
|
|
832
|
+
const menu = screen.getAllByRole('menu', {hidden: true})[0];
|
|
833
|
+
// The submenu trigger renders as a menuitem with aria-haspopup.
|
|
834
|
+
const submenuTrigger = screen.getByRole('menuitem', {
|
|
835
|
+
name: /Move to/,
|
|
836
|
+
hidden: true,
|
|
837
|
+
});
|
|
838
|
+
expect(submenuTrigger).toHaveAttribute('aria-haspopup', 'menu');
|
|
839
|
+
// Cut → Move to → Delete, one step per press (submenu items not swept in).
|
|
840
|
+
screen.getByRole('menuitem', {name: 'Cut', hidden: true}).focus();
|
|
841
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
842
|
+
expect(submenuTrigger).toHaveFocus();
|
|
843
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
844
|
+
expect(
|
|
845
|
+
screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
|
|
846
|
+
).toHaveFocus();
|
|
847
|
+
});
|
|
806
848
|
});
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
import {
|
|
50
50
|
MENU_ITEM_ROLES,
|
|
51
51
|
MENU_ITEM_SELECTOR,
|
|
52
|
+
MENU_BOUNDARY_SELECTOR,
|
|
52
53
|
} from '../DropdownMenu/menuItemRoles';
|
|
53
54
|
import {useListFocus} from '../hooks/useListFocus';
|
|
54
55
|
import {useTypeahead} from '../hooks/useTypeahead';
|
|
@@ -268,22 +269,18 @@ export function ContextMenu({
|
|
|
268
269
|
handleKeyDown: listNavKeyDown,
|
|
269
270
|
focusFirst,
|
|
270
271
|
focusItem,
|
|
272
|
+
ownsEvent,
|
|
273
|
+
getItems: getMenuItems,
|
|
271
274
|
} = useListFocus<HTMLDivElement>({
|
|
272
275
|
itemSelector: MENU_ITEM_SELECTOR,
|
|
276
|
+
boundarySelector: MENU_BOUNDARY_SELECTOR,
|
|
273
277
|
wrap: false,
|
|
274
278
|
onEscape: closeMenu,
|
|
275
279
|
});
|
|
276
280
|
|
|
277
|
-
// First-character typeahead over the enabled menu items (menus-11).
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
listRef.current
|
|
281
|
-
? Array.from(
|
|
282
|
-
listRef.current.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR),
|
|
283
|
-
)
|
|
284
|
-
: [],
|
|
285
|
-
[listRef],
|
|
286
|
-
);
|
|
281
|
+
// First-character typeahead over the enabled menu items (menus-11). Reuses
|
|
282
|
+
// the hook's scoped item collection so an inline submenu flyout's items
|
|
283
|
+
// aren't swept in.
|
|
287
284
|
const typeahead = useTypeahead({
|
|
288
285
|
getItemLabels: () => getMenuItems().map(el => el.textContent),
|
|
289
286
|
onMatch: focusItem,
|
|
@@ -340,6 +337,11 @@ export function ContextMenu({
|
|
|
340
337
|
|
|
341
338
|
const listKeyDown = useCallback(
|
|
342
339
|
(e: React.KeyboardEvent) => {
|
|
340
|
+
// A submenu flyout renders inline inside this menu; its key events bubble
|
|
341
|
+
// up here. Let that level own them — only handle events from this level.
|
|
342
|
+
if (!ownsEvent(e)) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
343
345
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
344
346
|
e.preventDefault();
|
|
345
347
|
const focused = document.activeElement as HTMLElement | null;
|
|
@@ -357,7 +359,7 @@ export function ContextMenu({
|
|
|
357
359
|
}
|
|
358
360
|
listNavKeyDown(e);
|
|
359
361
|
},
|
|
360
|
-
[listNavKeyDown, typeahead],
|
|
362
|
+
[listNavKeyDown, typeahead, ownsEvent],
|
|
361
363
|
);
|
|
362
364
|
|
|
363
365
|
// Place the zero-size cursor anchor at a point in the trigger's local
|
package/src/ContextMenu/index.ts
CHANGED
|
@@ -35,3 +35,8 @@ export {
|
|
|
35
35
|
DropdownMenuRadioItem as ContextMenuRadioItem,
|
|
36
36
|
type DropdownMenuRadioItemProps as ContextMenuRadioItemProps,
|
|
37
37
|
} from '../DropdownMenu/DropdownMenuRadioItem';
|
|
38
|
+
// Submenus work inside a ContextMenu too (nested flyout of items).
|
|
39
|
+
export {
|
|
40
|
+
DropdownMenuSubMenu as ContextMenuSubMenu,
|
|
41
|
+
type DropdownMenuSubMenuProps as ContextMenuSubMenuProps,
|
|
42
|
+
} from '../DropdownMenu/DropdownMenuSubMenu';
|
|
@@ -117,9 +117,9 @@ export const docs = {
|
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
name: 'statusVariant',
|
|
120
|
-
type: "'attached' | 'detached'",
|
|
120
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
121
121
|
description:
|
|
122
|
-
'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing.',
|
|
122
|
+
'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing; tooltip hides the message box and surfaces it in a tooltip on the status icon.',
|
|
123
123
|
default: "'attached'",
|
|
124
124
|
},
|
|
125
125
|
{
|
|
@@ -149,6 +149,12 @@ export const docs = {
|
|
|
149
149
|
"How the committed date value is displayed. Named values are reused from Timestamp's format vocabulary: 'date' shows 'Mar 21, 2026', 'date_long' shows 'March 21, 2026', 'date_weekday' shows 'Wed, Mar 21, 2026', 'system_date' shows '2026-03-21'. A function receives the ISO value and returns a custom string. Applies only to the committed value, never to text being typed.",
|
|
150
150
|
default: "'date_long'",
|
|
151
151
|
},
|
|
152
|
+
{
|
|
153
|
+
name: 'width',
|
|
154
|
+
type: 'SizeValue',
|
|
155
|
+
description:
|
|
156
|
+
'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.',
|
|
157
|
+
},
|
|
152
158
|
{
|
|
153
159
|
name: 'xstyle',
|
|
154
160
|
type: 'StyleXStyles',
|
|
@@ -159,6 +165,8 @@ export const docs = {
|
|
|
159
165
|
theming: {
|
|
160
166
|
targets: [
|
|
161
167
|
{className: 'astryx-date-input', visualProps: ['size', 'status']},
|
|
168
|
+
{className: 'astryx-date-input-toggle-icon', states: ['state']},
|
|
169
|
+
{className: 'astryx-date-input-clear-icon'},
|
|
162
170
|
],
|
|
163
171
|
},
|
|
164
172
|
usage: {
|
|
@@ -393,9 +401,9 @@ export const docsZh = {
|
|
|
393
401
|
},
|
|
394
402
|
{
|
|
395
403
|
name: 'statusVariant',
|
|
396
|
-
type: "'attached' | 'detached'",
|
|
404
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
397
405
|
description:
|
|
398
|
-
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached
|
|
406
|
+
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
|
|
399
407
|
default: "'attached'",
|
|
400
408
|
},
|
|
401
409
|
{
|
|
@@ -429,6 +437,8 @@ export const docsZh = {
|
|
|
429
437
|
className: 'astryx-date-input',
|
|
430
438
|
visualProps: ['size', 'status'],
|
|
431
439
|
},
|
|
440
|
+
{className: 'astryx-date-input-toggle-icon', states: ['state']},
|
|
441
|
+
{className: 'astryx-date-input-clear-icon'},
|
|
432
442
|
],
|
|
433
443
|
},
|
|
434
444
|
};
|
|
@@ -506,7 +516,7 @@ export const docsDense = {
|
|
|
506
516
|
placeholder: 'placeholder text in input',
|
|
507
517
|
size: 'input control size',
|
|
508
518
|
status: 'error/warning/success status w/ message',
|
|
509
|
-
statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing.',
|
|
519
|
+
statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
|
|
510
520
|
labelTooltip: 'tooltip text via info icon at label end',
|
|
511
521
|
hasClear: 'Shows clear button when date is set. Clears value on click.',
|
|
512
522
|
numberOfMonths: 'months shown simultaneously in calendar popover',
|
|
@@ -20,8 +20,11 @@ import {
|
|
|
20
20
|
import userEvent from '@testing-library/user-event';
|
|
21
21
|
import {getButton, queryButton} from '../__tests__/fastRoleQueries';
|
|
22
22
|
import {DateInput} from './DateInput';
|
|
23
|
+
import {Icon} from '../Icon';
|
|
23
24
|
import {InputGroup} from '../InputGroup';
|
|
24
25
|
import {InputGroupText} from '../InputGroup/InputGroupText';
|
|
26
|
+
import {defineTheme} from '../theme/defineTheme';
|
|
27
|
+
import {generateThemeCSSFlat} from '../theme/generateThemeRules';
|
|
25
28
|
|
|
26
29
|
describe('DateInput', () => {
|
|
27
30
|
it('renders with label', () => {
|
|
@@ -944,11 +947,14 @@ describe('DateInput', () => {
|
|
|
944
947
|
});
|
|
945
948
|
});
|
|
946
949
|
|
|
947
|
-
|
|
948
950
|
describe('DateInput statusVariant forwarding', () => {
|
|
949
951
|
it('defaults to attached (status renders with data-variant="attached")', () => {
|
|
950
952
|
const {container} = render(
|
|
951
|
-
<DateInput
|
|
953
|
+
<DateInput
|
|
954
|
+
label="Date"
|
|
955
|
+
onChange={() => {}}
|
|
956
|
+
status={{type: 'error', message: 'Bad date'}}
|
|
957
|
+
/>,
|
|
952
958
|
);
|
|
953
959
|
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
954
960
|
'data-variant',
|
|
@@ -958,7 +964,12 @@ describe('DateInput statusVariant forwarding', () => {
|
|
|
958
964
|
|
|
959
965
|
it('forwards statusVariant="detached" to the underlying Field status', () => {
|
|
960
966
|
const {container} = render(
|
|
961
|
-
<DateInput
|
|
967
|
+
<DateInput
|
|
968
|
+
label="Date"
|
|
969
|
+
onChange={() => {}}
|
|
970
|
+
status={{type: 'error', message: 'Bad date'}}
|
|
971
|
+
statusVariant="detached"
|
|
972
|
+
/>,
|
|
962
973
|
);
|
|
963
974
|
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
964
975
|
'data-variant',
|
|
@@ -966,3 +977,199 @@ describe('DateInput statusVariant forwarding', () => {
|
|
|
966
977
|
);
|
|
967
978
|
});
|
|
968
979
|
});
|
|
980
|
+
|
|
981
|
+
describe('DateInput clear icon theme target', () => {
|
|
982
|
+
// Resolve the clear glyph span (the astryx-icon element inside the clear
|
|
983
|
+
// button), independent of the theme target class.
|
|
984
|
+
const getClearIcon = (): HTMLElement => {
|
|
985
|
+
const button = getButton('Clear Date');
|
|
986
|
+
const icon = button.querySelector('.astryx-icon');
|
|
987
|
+
if (icon == null) {
|
|
988
|
+
throw new Error('clear icon not found');
|
|
989
|
+
}
|
|
990
|
+
return icon as HTMLElement;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
it('renders the astryx-date-input-clear-icon target on the clear glyph', () => {
|
|
994
|
+
render(
|
|
995
|
+
<DateInput
|
|
996
|
+
label="Date"
|
|
997
|
+
value="2026-01-15"
|
|
998
|
+
onChange={() => {}}
|
|
999
|
+
hasClear
|
|
1000
|
+
/>,
|
|
1001
|
+
);
|
|
1002
|
+
// The stable theme target lands on the icon element itself (not the
|
|
1003
|
+
// button), so a theme can restyle just this glyph (color, size, hover)
|
|
1004
|
+
// via `defineTheme` — a button-level target could not reach the icon's
|
|
1005
|
+
// own color/size.
|
|
1006
|
+
const icon = getClearIcon();
|
|
1007
|
+
expect(icon).toHaveClass('astryx-date-input-clear-icon');
|
|
1008
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
it('keeps the clear button functional alongside the target', () => {
|
|
1012
|
+
const onChange = vi.fn();
|
|
1013
|
+
render(
|
|
1014
|
+
<DateInput
|
|
1015
|
+
label="Date"
|
|
1016
|
+
value="2026-01-15"
|
|
1017
|
+
onChange={onChange}
|
|
1018
|
+
hasClear
|
|
1019
|
+
/>,
|
|
1020
|
+
);
|
|
1021
|
+
const clear = getButton('Clear Date');
|
|
1022
|
+
expect(clear.tagName).toBe('BUTTON');
|
|
1023
|
+
fireEvent.click(clear);
|
|
1024
|
+
expect(onChange).toHaveBeenCalledWith(undefined);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it('renders the default icon (secondary color, sm size) byte-identically', () => {
|
|
1028
|
+
// Pixel-identical default guard: the clear glyph must carry the exact same
|
|
1029
|
+
// StyleX color/size classes as a standalone secondary/sm icon. The added
|
|
1030
|
+
// target class is purely additive — it changes nothing until a theme
|
|
1031
|
+
// targets it.
|
|
1032
|
+
render(
|
|
1033
|
+
<DateInput
|
|
1034
|
+
label="Date"
|
|
1035
|
+
value="2026-01-15"
|
|
1036
|
+
onChange={() => {}}
|
|
1037
|
+
hasClear
|
|
1038
|
+
/>,
|
|
1039
|
+
);
|
|
1040
|
+
const icon = getClearIcon();
|
|
1041
|
+
|
|
1042
|
+
const {container: refContainer} = render(
|
|
1043
|
+
<Icon icon="close" size="sm" color="secondary" />,
|
|
1044
|
+
);
|
|
1045
|
+
const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
|
|
1046
|
+
|
|
1047
|
+
const styleClasses = (el: HTMLElement) =>
|
|
1048
|
+
el.className
|
|
1049
|
+
.split(' ')
|
|
1050
|
+
.filter(c => c !== 'astryx-date-input-clear-icon')
|
|
1051
|
+
.sort();
|
|
1052
|
+
|
|
1053
|
+
expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
it('exposes date-input-clear-icon so a theme reaches the icon color, size, and hover', () => {
|
|
1057
|
+
// jsdom cannot resolve the @layer cascade, so the DOM-class assertion above
|
|
1058
|
+
// (target lands on the icon element) plus this generation assertion (the
|
|
1059
|
+
// theme emits same-element icon rules in @layer astryx-theme) together
|
|
1060
|
+
// prove the seam: a same-element theme rule wins over the icon's own
|
|
1061
|
+
// base-layer color/size.
|
|
1062
|
+
const theme = defineTheme({
|
|
1063
|
+
name: 'date-input-clear-icon-test',
|
|
1064
|
+
components: {
|
|
1065
|
+
'date-input-clear-icon': {
|
|
1066
|
+
base: {
|
|
1067
|
+
width: '12px',
|
|
1068
|
+
height: '12px',
|
|
1069
|
+
fontSize: '12px',
|
|
1070
|
+
color: 'var(--color-icon-secondary)',
|
|
1071
|
+
':hover': {color: 'var(--color-icon-primary)'},
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
});
|
|
1076
|
+
const css = generateThemeCSSFlat(theme);
|
|
1077
|
+
expect(css).toContain('.astryx-date-input-clear-icon {');
|
|
1078
|
+
expect(css).toContain('width: 12px');
|
|
1079
|
+
expect(css).toContain('height: 12px');
|
|
1080
|
+
expect(css).toContain('.astryx-date-input-clear-icon:hover {');
|
|
1081
|
+
expect(css).toContain('color: var(--color-icon-primary)');
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
describe('DateInput calendar-toggle icon theme target', () => {
|
|
1086
|
+
const iconIn = (button: HTMLElement): HTMLElement => {
|
|
1087
|
+
const icon = button.querySelector('.astryx-icon');
|
|
1088
|
+
if (icon == null) {
|
|
1089
|
+
throw new Error('toggle icon not found');
|
|
1090
|
+
}
|
|
1091
|
+
return icon as HTMLElement;
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
it('renders the astryx-date-input-toggle-icon target on the toggle glyph', () => {
|
|
1095
|
+
render(<DateInput label="Date" onChange={() => {}} />);
|
|
1096
|
+
const icon = iconIn(getButton('Open calendar'));
|
|
1097
|
+
// The stable theme target lands on the icon element itself (not the
|
|
1098
|
+
// button), so a theme can restyle just this glyph (color, size, hover) —
|
|
1099
|
+
// and each open/closed state — via `defineTheme`. A button-level target
|
|
1100
|
+
// could not reach the icon's own color/size.
|
|
1101
|
+
expect(icon).toHaveClass('astryx-date-input-toggle-icon');
|
|
1102
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
1103
|
+
// Open/closed state is reflected so a theme can target each state alone.
|
|
1104
|
+
expect(icon).toHaveAttribute('data-state', 'collapsed');
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('reflects the expanded state on the toggle icon when the popover is open', async () => {
|
|
1108
|
+
render(<DateInput label="Date" onChange={() => {}} />);
|
|
1109
|
+
// Capture the toggle before opening — its aria-label changes to the close
|
|
1110
|
+
// label once open, but the element reference (and its icon) is stable.
|
|
1111
|
+
const toggle = getButton('Open calendar');
|
|
1112
|
+
fireEvent.click(toggle);
|
|
1113
|
+
await waitFor(() => {
|
|
1114
|
+
expect(iconIn(toggle)).toHaveAttribute('data-state', 'expanded');
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
it('keeps the calendar-toggle button functional alongside the target', () => {
|
|
1119
|
+
render(<DateInput label="Date" onChange={() => {}} />);
|
|
1120
|
+
const toggle = getButton('Open calendar');
|
|
1121
|
+
expect(toggle.tagName).toBe('BUTTON');
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it('renders the default icon (secondary color, sm size) byte-identically', () => {
|
|
1125
|
+
// Pixel-identical default guard: the toggle glyph must carry the exact same
|
|
1126
|
+
// StyleX color/size classes as a standalone secondary/sm icon. The added
|
|
1127
|
+
// target class + data-state are purely additive — they change nothing until
|
|
1128
|
+
// a theme targets them.
|
|
1129
|
+
render(<DateInput label="Date" onChange={() => {}} />);
|
|
1130
|
+
const icon = iconIn(getButton('Open calendar'));
|
|
1131
|
+
|
|
1132
|
+
const {container: refContainer} = render(
|
|
1133
|
+
<Icon icon="calendar" size="sm" color="secondary" />,
|
|
1134
|
+
);
|
|
1135
|
+
const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
|
|
1136
|
+
|
|
1137
|
+
// Exclude the additive theme-target classes (the stable target + its
|
|
1138
|
+
// reflected state class) so only the StyleX color/size classes remain.
|
|
1139
|
+
const themeTargetClasses = new Set([
|
|
1140
|
+
'astryx-date-input-toggle-icon',
|
|
1141
|
+
'collapsed',
|
|
1142
|
+
'expanded',
|
|
1143
|
+
]);
|
|
1144
|
+
const styleClasses = (el: HTMLElement) =>
|
|
1145
|
+
el.className
|
|
1146
|
+
.split(' ')
|
|
1147
|
+
.filter(c => !themeTargetClasses.has(c))
|
|
1148
|
+
.sort();
|
|
1149
|
+
|
|
1150
|
+
expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('exposes date-input-toggle-icon so a theme reaches the icon size and per-state color', () => {
|
|
1154
|
+
// jsdom cannot resolve the @layer cascade, so the DOM-class assertions
|
|
1155
|
+
// above (target lands on the icon element) plus this generation assertion
|
|
1156
|
+
// (the theme emits same-element icon rules in @layer astryx-theme) together
|
|
1157
|
+
// prove the seam: a same-element theme rule wins over the icon's own
|
|
1158
|
+
// base-layer color/size.
|
|
1159
|
+
const theme = defineTheme({
|
|
1160
|
+
name: 'date-input-toggle-icon-test',
|
|
1161
|
+
components: {
|
|
1162
|
+
'date-input-toggle-icon': {
|
|
1163
|
+
base: {width: '14px', height: '14px', fontSize: '14px'},
|
|
1164
|
+
'state:expanded': {color: 'var(--color-icon-primary)'},
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
const css = generateThemeCSSFlat(theme);
|
|
1169
|
+
expect(css).toContain('.astryx-date-input-toggle-icon {');
|
|
1170
|
+
expect(css).toContain('width: 14px');
|
|
1171
|
+
expect(css).toContain('height: 14px');
|
|
1172
|
+
expect(css).toContain('.astryx-date-input-toggle-icon.expanded');
|
|
1173
|
+
expect(css).toContain('color: var(--color-icon-primary)');
|
|
1174
|
+
});
|
|
1175
|
+
});
|