@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
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* @position Test file; validates selection behavior (checkboxes, aria, select-all)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {describe, it, expect} from 'vitest';
|
|
10
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
11
11
|
import {useState} from 'react';
|
|
12
|
-
import {render, screen, within} from '@testing-library/react';
|
|
12
|
+
import {act, render, renderHook, screen, within} from '@testing-library/react';
|
|
13
13
|
import userEvent from '@testing-library/user-event';
|
|
14
14
|
import {Table} from '../../Table';
|
|
15
15
|
import {useTableSelection} from './useTableSelection';
|
|
16
|
-
import type {TableColumn} from '../../types';
|
|
16
|
+
import type {BodyRowRenderProps, TableColumn} from '../../types';
|
|
17
17
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Test Data
|
|
@@ -210,4 +210,62 @@ describe('useTableSelection', () => {
|
|
|
210
210
|
const headers = within(headerRow).getAllByRole('columnheader');
|
|
211
211
|
expect(headers).toHaveLength(3);
|
|
212
212
|
});
|
|
213
|
+
|
|
214
|
+
it('unsubscribes detached row refs instead of accumulating listeners', () => {
|
|
215
|
+
const getIsItemSelected = vi.fn(() => false);
|
|
216
|
+
const {result, rerender} = renderHook(() =>
|
|
217
|
+
useTableSelection<SelectableUser>({
|
|
218
|
+
getIsItemSelected,
|
|
219
|
+
onSelectItem: vi.fn(),
|
|
220
|
+
onSelectAll: vi.fn(),
|
|
221
|
+
getIsAllSelected: () => false,
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const initialProps: BodyRowRenderProps = {
|
|
226
|
+
htmlProps: {},
|
|
227
|
+
xstyle: [],
|
|
228
|
+
children: null,
|
|
229
|
+
};
|
|
230
|
+
const row = document.createElement('tr');
|
|
231
|
+
document.body.append(row);
|
|
232
|
+
|
|
233
|
+
let detach: (() => void) | undefined;
|
|
234
|
+
|
|
235
|
+
// Simulate React replacing the callback ref as this row re-renders.
|
|
236
|
+
// Each detach must unsubscribe the previous ref before the next attaches.
|
|
237
|
+
for (let i = 0; i < 3; i++) {
|
|
238
|
+
act(() => {
|
|
239
|
+
detach?.();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const transformed = result.current.transformBodyRow?.(
|
|
243
|
+
initialProps,
|
|
244
|
+
selectableUsers[0],
|
|
245
|
+
0,
|
|
246
|
+
);
|
|
247
|
+
expect(transformed?.ref).toBeTypeOf('function');
|
|
248
|
+
|
|
249
|
+
act(() => {
|
|
250
|
+
const cleanup = (
|
|
251
|
+
transformed?.ref as React.RefCallback<HTMLTableRowElement>
|
|
252
|
+
)(row);
|
|
253
|
+
expect(cleanup).toBeTypeOf('function');
|
|
254
|
+
detach = cleanup as () => void;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
getIsItemSelected.mockClear();
|
|
259
|
+
rerender();
|
|
260
|
+
expect(getIsItemSelected).toHaveBeenCalledTimes(1);
|
|
261
|
+
|
|
262
|
+
act(() => {
|
|
263
|
+
detach?.();
|
|
264
|
+
});
|
|
265
|
+
getIsItemSelected.mockClear();
|
|
266
|
+
rerender();
|
|
267
|
+
expect(getIsItemSelected).not.toHaveBeenCalled();
|
|
268
|
+
|
|
269
|
+
row.remove();
|
|
270
|
+
});
|
|
213
271
|
});
|
|
@@ -213,7 +213,12 @@ const SHADOW_WIDTH = '6px';
|
|
|
213
213
|
// --color-shadow is only ~10% alpha (too faint here); use a slightly stronger soft tint.
|
|
214
214
|
const SHADOW_TINT = 'light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.32))';
|
|
215
215
|
const shadowStyles = stylex.create({
|
|
216
|
-
// start-pinned: shadow falls
|
|
216
|
+
// start-pinned: shadow falls toward inline-end (over scrolled content).
|
|
217
|
+
// insetInlineEnd anchors the strip to the column's inline-end edge; the strip
|
|
218
|
+
// must then be nudged one full width OUTWARD (past that edge) into the
|
|
219
|
+
// scrolled region. Outward is +X under LTR (inline-end = right) but −X under
|
|
220
|
+
// RTL (inline-end = left), and the fade must run from the pinned edge toward
|
|
221
|
+
// content in each direction — hence both transform and gradient flip on dir.
|
|
217
222
|
start: {
|
|
218
223
|
'::after': {
|
|
219
224
|
content: '""',
|
|
@@ -222,14 +227,24 @@ const shadowStyles = stylex.create({
|
|
|
222
227
|
bottom: 0,
|
|
223
228
|
insetInlineEnd: 0,
|
|
224
229
|
width: SHADOW_WIDTH,
|
|
225
|
-
transform:
|
|
230
|
+
transform: {
|
|
231
|
+
default: 'translateX(100%)',
|
|
232
|
+
':is([dir="rtl"] *)': 'translateX(-100%)',
|
|
233
|
+
},
|
|
226
234
|
pointerEvents: 'none',
|
|
227
235
|
transition: 'opacity 150ms ease',
|
|
228
236
|
opacity: `var(${SHADOW_VAR_START}, 0)`,
|
|
229
|
-
backgroundImage:
|
|
237
|
+
backgroundImage: {
|
|
238
|
+
default: `linear-gradient(to right, ${SHADOW_TINT}, transparent)`,
|
|
239
|
+
':is([dir="rtl"] *)': `linear-gradient(to left, ${SHADOW_TINT}, transparent)`,
|
|
240
|
+
},
|
|
230
241
|
},
|
|
231
242
|
},
|
|
232
|
-
// end-pinned: shadow falls
|
|
243
|
+
// end-pinned: shadow falls toward inline-start (over scrolled content).
|
|
244
|
+
// insetInlineStart anchors the strip to the column's inline-start edge; the
|
|
245
|
+
// strip is nudged one full width OUTWARD into content — −X under LTR
|
|
246
|
+
// (inline-start = left) but +X under RTL (inline-start = right) — with the
|
|
247
|
+
// gradient fading from the pinned edge toward content in each direction.
|
|
233
248
|
end: {
|
|
234
249
|
'::after': {
|
|
235
250
|
content: '""',
|
|
@@ -238,11 +253,17 @@ const shadowStyles = stylex.create({
|
|
|
238
253
|
bottom: 0,
|
|
239
254
|
insetInlineStart: 0,
|
|
240
255
|
width: SHADOW_WIDTH,
|
|
241
|
-
transform:
|
|
256
|
+
transform: {
|
|
257
|
+
default: 'translateX(-100%)',
|
|
258
|
+
':is([dir="rtl"] *)': 'translateX(100%)',
|
|
259
|
+
},
|
|
242
260
|
pointerEvents: 'none',
|
|
243
261
|
transition: 'opacity 150ms ease',
|
|
244
262
|
opacity: `var(${SHADOW_VAR_END}, 0)`,
|
|
245
|
-
backgroundImage:
|
|
263
|
+
backgroundImage: {
|
|
264
|
+
default: `linear-gradient(to left, ${SHADOW_TINT}, transparent)`,
|
|
265
|
+
':is([dir="rtl"] *)': `linear-gradient(to right, ${SHADOW_TINT}, transparent)`,
|
|
266
|
+
},
|
|
246
267
|
},
|
|
247
268
|
},
|
|
248
269
|
});
|
|
@@ -289,16 +310,24 @@ export function useTableStickyColumns<T extends Record<string, unknown>>(
|
|
|
289
310
|
const {hasStart: hs, hasEnd: he} = stateRef.current;
|
|
290
311
|
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
291
312
|
const hasOverflow = maxScroll > 1;
|
|
313
|
+
// RTL-safe scroll position. Spec-compliant browsers report a NEGATIVE
|
|
314
|
+
// scrollLeft under RTL (0 at the inline-start edge, decreasing toward the
|
|
315
|
+
// inline-end edge), so a raw `scrollLeft > 1` start test would never fire
|
|
316
|
+
// and the end test would be wrong-signed. Math.abs collapses both
|
|
317
|
+
// conventions to a distance-from-inline-start, matching the repo's own
|
|
318
|
+
// useScrollOverflow (overflowStart/overflowEnd use Math.abs, tolerance 1).
|
|
319
|
+
// No-op under LTR where scrollLeft is already >= 0.
|
|
320
|
+
const pos = Math.abs(el.scrollLeft);
|
|
292
321
|
if (hs) {
|
|
293
322
|
el.style.setProperty(
|
|
294
323
|
SHADOW_VAR_START,
|
|
295
|
-
hasOverflow &&
|
|
324
|
+
hasOverflow && pos > 1 ? '1' : '0',
|
|
296
325
|
);
|
|
297
326
|
}
|
|
298
327
|
if (he) {
|
|
299
328
|
el.style.setProperty(
|
|
300
329
|
SHADOW_VAR_END,
|
|
301
|
-
hasOverflow &&
|
|
330
|
+
hasOverflow && pos < maxScroll - 1 ? '1' : '0',
|
|
302
331
|
);
|
|
303
332
|
}
|
|
304
333
|
};
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
import * as stylex from '@stylexjs/stylex';
|
|
43
43
|
import {colorVars, radiusVars, spacingVars} from '../../../theme/tokens.stylex';
|
|
44
44
|
import {Icon} from '../../../Icon';
|
|
45
|
-
import {mergeRefs} from '../../../utils';
|
|
45
|
+
import {mergeRefs, rtlStyles} from '../../../utils';
|
|
46
46
|
import type {
|
|
47
47
|
TablePlugin,
|
|
48
48
|
TableColumn,
|
|
@@ -299,12 +299,14 @@ function TreeExpander({
|
|
|
299
299
|
: t('@astryx.tableTree.expandRow')
|
|
300
300
|
}
|
|
301
301
|
aria-expanded={isExpanded}>
|
|
302
|
-
<span
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
<span {...stylex.props(rtlStyles.mirror)}>
|
|
303
|
+
<span
|
|
304
|
+
{...stylex.props(
|
|
305
|
+
treeStyles.chevron,
|
|
306
|
+
isExpanded && treeStyles.chevronExpanded,
|
|
307
|
+
)}>
|
|
308
|
+
<Icon icon="chevronRight" size="xsm" />
|
|
309
|
+
</span>
|
|
308
310
|
</span>
|
|
309
311
|
</button>
|
|
310
312
|
);
|
|
@@ -346,12 +348,14 @@ function TreeExpandAllToggle({
|
|
|
346
348
|
: t('@astryx.tableTree.expandAllRows')
|
|
347
349
|
}
|
|
348
350
|
aria-expanded={allExpanded}>
|
|
349
|
-
<span
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
351
|
+
<span {...stylex.props(rtlStyles.mirror)}>
|
|
352
|
+
<span
|
|
353
|
+
{...stylex.props(
|
|
354
|
+
treeStyles.chevron,
|
|
355
|
+
allExpanded && treeStyles.chevronExpanded,
|
|
356
|
+
)}>
|
|
357
|
+
<Icon icon="chevronRight" size="xsm" />
|
|
358
|
+
</span>
|
|
355
359
|
</span>
|
|
356
360
|
</button>
|
|
357
361
|
);
|
package/src/Table/types.ts
CHANGED
|
@@ -540,6 +540,39 @@ export interface BaseTableProps<
|
|
|
540
540
|
/** Plugins to transform render props at each level */
|
|
541
541
|
plugins?: TablePlugin<T>[];
|
|
542
542
|
|
|
543
|
+
/**
|
|
544
|
+
* ARIA row index (1-based) assigned to the **first** rendered body row.
|
|
545
|
+
*
|
|
546
|
+
* The row ordinal is an accessibility concern independent of any visible
|
|
547
|
+
* index column: `aria-rowindex` on each `<tr>` should reflect the row's
|
|
548
|
+
* position in the **full** dataset, not just the current page. For a
|
|
549
|
+
* paginated / windowed view, pass the offset of the first visible row,
|
|
550
|
+
* e.g. `(page - 1) * pageSize + 1`.
|
|
551
|
+
*
|
|
552
|
+
* Setting this (or {@link rowCount}) opts the table into emitting
|
|
553
|
+
* `aria-rowindex` on body rows and `aria-rowcount` on the `<table>`. Data
|
|
554
|
+
* rows are numbered from this value; the header row keeps native table
|
|
555
|
+
* semantics and is not assigned an ARIA row index (so the visible
|
|
556
|
+
* `useTableRowIndex` numbering and `aria-rowindex` stay in agreement).
|
|
557
|
+
*
|
|
558
|
+
* Applies to data-driven mode only (ignored in children mode).
|
|
559
|
+
*
|
|
560
|
+
* @default 1
|
|
561
|
+
*/
|
|
562
|
+
rowIndexStart?: number;
|
|
563
|
+
/**
|
|
564
|
+
* Total number of body rows across **all** pages/windows, used for
|
|
565
|
+
* `aria-rowcount` on the `<table>`. Provide this for paginated data so
|
|
566
|
+
* assistive tech can announce "row X of Y" against the full dataset.
|
|
567
|
+
*
|
|
568
|
+
* When omitted but {@link rowIndexStart} is set (a windowed view with an
|
|
569
|
+
* unknown total, e.g. cursor pagination), `aria-rowcount` is set to `-1`
|
|
570
|
+
* per the ARIA convention for an unknown row count.
|
|
571
|
+
*
|
|
572
|
+
* Applies to data-driven mode only (ignored in children mode).
|
|
573
|
+
*/
|
|
574
|
+
rowCount?: number;
|
|
575
|
+
|
|
543
576
|
/** Children mode — render `<tr>`/`<td>` directly instead of data-driven */
|
|
544
577
|
children?: ReactNode;
|
|
545
578
|
/**
|
|
@@ -13,7 +13,7 @@ export const docs = {
|
|
|
13
13
|
name: 'getStatus',
|
|
14
14
|
type: "(item: T) => { color: 'accent' | 'success' | 'error' | 'warning' | 'red' | 'orange' | 'green' | 'yellow' | 'blue' | 'gray' | string; icon?: IconName; label: string } | null",
|
|
15
15
|
description:
|
|
16
|
-
'Derive the status indicator for a row: a semantic color (accent, success, error, warning, red, orange, green, yellow, blue, gray
|
|
16
|
+
'Derive the status indicator for a row: a semantic color (accent, success, error, warning, red, orange, green, yellow, blue, gray, each mapped to a theme token) or a raw CSS color as an escape hatch; an optional icon to signal status by shape instead of the dot (recommended when multiple statuses coexist; valid names: close, chevronDown, chevronLeft, chevronRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone); and a required accessible label (announced via role="img" and shown in a tooltip on hover, so a status is never conveyed by color alone). Return null for rows with no status. Memoize with useCallback for a stable plugin identity.',
|
|
17
17
|
required: true,
|
|
18
18
|
},
|
|
19
19
|
],
|
|
@@ -108,9 +108,9 @@ export const docs = {
|
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
name: 'statusVariant',
|
|
111
|
-
type: "'attached' | 'detached'",
|
|
111
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
112
112
|
description:
|
|
113
|
-
'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.',
|
|
113
|
+
'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.',
|
|
114
114
|
default: "'attached'",
|
|
115
115
|
},
|
|
116
116
|
{
|
|
@@ -154,6 +154,12 @@ export const docs = {
|
|
|
154
154
|
description:
|
|
155
155
|
'HTML name attribute for the textarea element, useful for form submissions.',
|
|
156
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: 'width',
|
|
159
|
+
type: 'SizeValue',
|
|
160
|
+
description:
|
|
161
|
+
'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.',
|
|
162
|
+
},
|
|
157
163
|
{
|
|
158
164
|
name: 'onFocus',
|
|
159
165
|
type: '(e: FocusEvent<HTMLTextAreaElement>) => void',
|
|
@@ -288,9 +294,9 @@ export const docsZh = {
|
|
|
288
294
|
},
|
|
289
295
|
{
|
|
290
296
|
name: 'statusVariant',
|
|
291
|
-
type: "'attached' | 'detached'",
|
|
297
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
292
298
|
description:
|
|
293
|
-
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached
|
|
299
|
+
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
|
|
294
300
|
default: "'attached'",
|
|
295
301
|
},
|
|
296
302
|
{
|
|
@@ -403,7 +409,7 @@ export const docsDense = {
|
|
|
403
409
|
rows: 'Visible text rows.',
|
|
404
410
|
maxLength: 'Max chars allowed. Shows counter (current/max) below textarea. No native enforcement.',
|
|
405
411
|
status: 'Colored border+icon status. Optional floating message below textarea.',
|
|
406
|
-
statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing.',
|
|
412
|
+
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.',
|
|
407
413
|
labelTooltip: 'Tooltip in info icon at label end.',
|
|
408
414
|
startIcon: 'Icon inside leading edge of textarea wrapper.',
|
|
409
415
|
hasSpellCheck: 'Enables/disables browser spell checking.',
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
type ReactNode,
|
|
28
28
|
} from 'react';
|
|
29
29
|
import * as stylex from '@stylexjs/stylex';
|
|
30
|
-
import type {IconName} from '../Icon';
|
|
31
30
|
import {
|
|
32
31
|
colorVars,
|
|
33
32
|
spacingVars,
|
|
@@ -42,13 +41,14 @@ import {
|
|
|
42
41
|
inputStatusFocusWithinStyles,
|
|
43
42
|
type FieldStatusVariant,
|
|
44
43
|
} from '../Field';
|
|
45
|
-
import {
|
|
44
|
+
import {renderIconSlot, type IconType} from '../Icon';
|
|
46
45
|
import {Spinner} from '../Spinner';
|
|
47
46
|
import {useTooltip} from '../Tooltip';
|
|
48
47
|
import {mergeProps, mergeRefs} from '../utils';
|
|
49
48
|
import type {BaseProps} from '../BaseProps';
|
|
50
49
|
import type {SizeValue} from '../utils/types';
|
|
51
50
|
import {useInputContainer} from '../hooks/useInputContainer';
|
|
51
|
+
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
52
52
|
import {useSize} from '../SizeContext/SizeContext';
|
|
53
53
|
import {themeProps} from '../utils/themeProps';
|
|
54
54
|
import {VisuallyHidden} from '../VisuallyHidden';
|
|
@@ -222,6 +222,7 @@ export interface TextAreaProps extends Omit<
|
|
|
222
222
|
* How the status message is placed relative to the input.
|
|
223
223
|
* - 'attached': message overlaps directly below the input (bordered treatment)
|
|
224
224
|
* - 'detached': message floats below as a separate element with spacing
|
|
225
|
+
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
|
|
225
226
|
* @default 'attached'
|
|
226
227
|
*/
|
|
227
228
|
statusVariant?: FieldStatusVariant;
|
|
@@ -350,25 +351,19 @@ export function TextArea({
|
|
|
350
351
|
isEnabled: showsDisabledMessage,
|
|
351
352
|
});
|
|
352
353
|
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const statusIconColorMap: Record<
|
|
360
|
-
TextAreaStatusType,
|
|
361
|
-
'warning' | 'error' | 'success'
|
|
362
|
-
> = {
|
|
363
|
-
warning: 'warning',
|
|
364
|
-
error: 'error',
|
|
365
|
-
success: 'success',
|
|
366
|
-
};
|
|
354
|
+
const {statusIcon, describedBy: statusTooltipDescribedBy} =
|
|
355
|
+
useInputStatusIcon({
|
|
356
|
+
status,
|
|
357
|
+
statusVariant,
|
|
358
|
+
});
|
|
367
359
|
|
|
368
360
|
const ariaDescribedBy =
|
|
369
361
|
[
|
|
370
362
|
description ? descriptionID : null,
|
|
371
|
-
status?.message ? statusMessageID : null,
|
|
363
|
+
statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
|
|
364
|
+
// The tooltip variant renders no message box; describe the input by the
|
|
365
|
+
// tooltip's content instead so the status is still announced.
|
|
366
|
+
statusTooltipDescribedBy,
|
|
372
367
|
maxLength != null ? counterID : null,
|
|
373
368
|
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
374
369
|
]
|
|
@@ -442,7 +437,9 @@ export function TextArea({
|
|
|
442
437
|
textareaSizeStyles[size],
|
|
443
438
|
effectivelyDisabled && inputWrapperStyles.disabled,
|
|
444
439
|
status && inputStatusBorderStyles[status.type],
|
|
445
|
-
status &&
|
|
440
|
+
status &&
|
|
441
|
+
!effectivelyDisabled &&
|
|
442
|
+
inputStatusHoverShadowStyles[status.type],
|
|
446
443
|
status && inputStatusFocusWithinStyles[status.type],
|
|
447
444
|
xstyle,
|
|
448
445
|
),
|
|
@@ -488,14 +485,8 @@ export function TextArea({
|
|
|
488
485
|
)}
|
|
489
486
|
/>
|
|
490
487
|
{isBusy && <Spinner size="sm" />}
|
|
491
|
-
{
|
|
492
|
-
<span {...stylex.props(styles.statusIcon)}>
|
|
493
|
-
<Icon
|
|
494
|
-
icon={statusIconMap[status.type]}
|
|
495
|
-
size="md"
|
|
496
|
-
color={statusIconColorMap[status.type]}
|
|
497
|
-
/>
|
|
498
|
-
</span>
|
|
488
|
+
{statusIcon && (
|
|
489
|
+
<span {...stylex.props(styles.statusIcon)}>{statusIcon}</span>
|
|
499
490
|
)}
|
|
500
491
|
</div>
|
|
501
492
|
{maxLength != null && (
|
|
@@ -116,9 +116,9 @@ export const docs = {
|
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
name: 'statusVariant',
|
|
119
|
-
type: "'attached' | 'detached'",
|
|
119
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
120
120
|
description:
|
|
121
|
-
'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.',
|
|
121
|
+
'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.',
|
|
122
122
|
default: "'attached'",
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -140,6 +140,12 @@ export const docs = {
|
|
|
140
140
|
description:
|
|
141
141
|
'The HTML name attribute for the input, useful for form submissions.',
|
|
142
142
|
},
|
|
143
|
+
{
|
|
144
|
+
name: 'width',
|
|
145
|
+
type: 'SizeValue',
|
|
146
|
+
description:
|
|
147
|
+
'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.',
|
|
148
|
+
},
|
|
143
149
|
],
|
|
144
150
|
theming: {
|
|
145
151
|
targets: [
|
|
@@ -283,9 +289,9 @@ export const docsZh = {
|
|
|
283
289
|
},
|
|
284
290
|
{
|
|
285
291
|
name: 'statusVariant',
|
|
286
|
-
type: "'attached' | 'detached'",
|
|
292
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
287
293
|
description:
|
|
288
|
-
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached
|
|
294
|
+
'状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
|
|
289
295
|
default: "'attached'",
|
|
290
296
|
},
|
|
291
297
|
{
|
|
@@ -382,7 +388,7 @@ export const docsDense = {
|
|
|
382
388
|
labelTooltip: 'Tooltip in info icon at label end.',
|
|
383
389
|
startIcon: 'SVG icon at input start (e.g. heroicons or lucide).',
|
|
384
390
|
status: 'Validation status; colored border+icon. Message floats below. Error sets aria-invalid.',
|
|
385
|
-
statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing.',
|
|
391
|
+
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.',
|
|
386
392
|
hasClear: 'Shows clear button when input has value. Clears value on click.',
|
|
387
393
|
hasAutoFocus: 'Auto-focus input on mount.',
|
|
388
394
|
htmlName: 'HTML name attr for form submissions.',
|
|
@@ -13,6 +13,7 @@ import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
|
13
13
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {TestIcon} from '../__tests__/TestIcon';
|
|
16
|
+
import {InputGroup} from '../InputGroup';
|
|
16
17
|
import {TextInput} from './TextInput';
|
|
17
18
|
|
|
18
19
|
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
@@ -229,6 +230,26 @@ describe('TextInput', () => {
|
|
|
229
230
|
expect(screen.getByText('Invalid email address')).toBeInTheDocument();
|
|
230
231
|
});
|
|
231
232
|
|
|
233
|
+
it('has no dangling aria-describedby ids inside InputGroup (WCAG 1.3.1)', () => {
|
|
234
|
+
// Inside an InputGroup no Field renders, so the status message element
|
|
235
|
+
// does not exist; aria-describedby must not reference its id.
|
|
236
|
+
render(
|
|
237
|
+
<InputGroup label="Contact">
|
|
238
|
+
<TextInput
|
|
239
|
+
label="Email"
|
|
240
|
+
value=""
|
|
241
|
+
onChange={() => {}}
|
|
242
|
+
status={{type: 'error', message: 'Invalid email address'}}
|
|
243
|
+
/>
|
|
244
|
+
</InputGroup>,
|
|
245
|
+
);
|
|
246
|
+
const input = screen.getByRole('textbox');
|
|
247
|
+
const describedBy = input.getAttribute('aria-describedby') ?? '';
|
|
248
|
+
for (const idToken of describedBy.split(/\s+/).filter(Boolean)) {
|
|
249
|
+
expect(document.getElementById(idToken)).not.toBeNull();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
232
253
|
it('does not render status message when not provided', () => {
|
|
233
254
|
render(
|
|
234
255
|
<TextInput
|
|
@@ -660,11 +681,15 @@ describe('TextInput', () => {
|
|
|
660
681
|
});
|
|
661
682
|
});
|
|
662
683
|
|
|
663
|
-
|
|
664
684
|
describe('TextInput statusVariant forwarding', () => {
|
|
665
685
|
it('defaults to attached (status renders with data-variant="attached")', () => {
|
|
666
686
|
const {container} = render(
|
|
667
|
-
<TextInput
|
|
687
|
+
<TextInput
|
|
688
|
+
label="Email"
|
|
689
|
+
value=""
|
|
690
|
+
onChange={() => {}}
|
|
691
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
692
|
+
/>,
|
|
668
693
|
);
|
|
669
694
|
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
670
695
|
'data-variant',
|
|
@@ -674,11 +699,113 @@ describe('TextInput statusVariant forwarding', () => {
|
|
|
674
699
|
|
|
675
700
|
it('forwards statusVariant="detached" to the underlying Field status', () => {
|
|
676
701
|
const {container} = render(
|
|
677
|
-
<TextInput
|
|
702
|
+
<TextInput
|
|
703
|
+
label="Email"
|
|
704
|
+
value=""
|
|
705
|
+
onChange={() => {}}
|
|
706
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
707
|
+
statusVariant="detached"
|
|
708
|
+
/>,
|
|
678
709
|
);
|
|
679
710
|
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
680
711
|
'data-variant',
|
|
681
712
|
'detached',
|
|
682
713
|
);
|
|
683
714
|
});
|
|
715
|
+
|
|
716
|
+
it('renders no message box for statusVariant="tooltip"', () => {
|
|
717
|
+
const {container} = render(
|
|
718
|
+
<TextInput
|
|
719
|
+
label="Email"
|
|
720
|
+
value=""
|
|
721
|
+
onChange={() => {}}
|
|
722
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
723
|
+
statusVariant="tooltip"
|
|
724
|
+
/>,
|
|
725
|
+
);
|
|
726
|
+
expect(
|
|
727
|
+
container.querySelector('.astryx-field-status'),
|
|
728
|
+
).not.toBeInTheDocument();
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it('surfaces the status message in a tooltip for statusVariant="tooltip"', () => {
|
|
732
|
+
render(
|
|
733
|
+
<TextInput
|
|
734
|
+
label="Email"
|
|
735
|
+
value=""
|
|
736
|
+
onChange={() => {}}
|
|
737
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
738
|
+
statusVariant="tooltip"
|
|
739
|
+
/>,
|
|
740
|
+
);
|
|
741
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
742
|
+
expect(tooltip).toHaveTextContent('Invalid email');
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('describes the input by the status tooltip for statusVariant="tooltip"', () => {
|
|
746
|
+
render(
|
|
747
|
+
<TextInput
|
|
748
|
+
label="Email"
|
|
749
|
+
value=""
|
|
750
|
+
onChange={() => {}}
|
|
751
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
752
|
+
statusVariant="tooltip"
|
|
753
|
+
/>,
|
|
754
|
+
);
|
|
755
|
+
const input = screen.getByRole('textbox');
|
|
756
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
757
|
+
expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('renders the tooltip status affordance as a focusable button (WCAG 2.1.1)', () => {
|
|
761
|
+
render(
|
|
762
|
+
<TextInput
|
|
763
|
+
label="Email"
|
|
764
|
+
value=""
|
|
765
|
+
onChange={() => {}}
|
|
766
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
767
|
+
statusVariant="tooltip"
|
|
768
|
+
/>,
|
|
769
|
+
);
|
|
770
|
+
// The status affordance is a real button with an accessible name naming
|
|
771
|
+
// the status type (WCAG 4.1.2), so keyboard-only users (no AT) can reach it.
|
|
772
|
+
const statusButton = screen.getByRole('button', {name: /error details/i});
|
|
773
|
+
expect(statusButton).toBeInTheDocument();
|
|
774
|
+
expect(statusButton).toHaveAttribute('type', 'button');
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('opens the status tooltip on keyboard focus for statusVariant="tooltip"', async () => {
|
|
778
|
+
const user = userEvent.setup();
|
|
779
|
+
render(
|
|
780
|
+
<TextInput
|
|
781
|
+
label="Email"
|
|
782
|
+
value=""
|
|
783
|
+
onChange={() => {}}
|
|
784
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
785
|
+
statusVariant="tooltip"
|
|
786
|
+
/>,
|
|
787
|
+
);
|
|
788
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
789
|
+
// Tab from the input to the status button; keyboard focus reveals the tip.
|
|
790
|
+
await user.tab();
|
|
791
|
+
await user.tab();
|
|
792
|
+
await waitFor(() => {
|
|
793
|
+
expect(tooltip).toHaveAttribute('popover-open');
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('describes the status button by the tooltip content for statusVariant="tooltip"', () => {
|
|
798
|
+
render(
|
|
799
|
+
<TextInput
|
|
800
|
+
label="Email"
|
|
801
|
+
value=""
|
|
802
|
+
onChange={() => {}}
|
|
803
|
+
status={{type: 'error', message: 'Invalid email'}}
|
|
804
|
+
statusVariant="tooltip"
|
|
805
|
+
/>,
|
|
806
|
+
);
|
|
807
|
+
const statusButton = screen.getByRole('button', {name: /error details/i});
|
|
808
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
809
|
+
expect(statusButton.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
810
|
+
});
|
|
684
811
|
});
|