@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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file tooltipEntries.ts
|
|
5
|
+
* @input A Date and the consumer's tooltip entry list
|
|
6
|
+
* @output Pure formatting of one tooltip line per entry, plus the entry types
|
|
7
|
+
* @position Leaf module; owns which zone each tooltip line names and whether
|
|
8
|
+
* it needs a zone abbreviation, then defers to formatInstant to render it
|
|
9
|
+
*
|
|
10
|
+
* This module holds only the decisions a tooltip makes that the visible text
|
|
11
|
+
* does not: resolving `timezoneID` (including the `'local'` alias and the
|
|
12
|
+
* fallback for an unknown identifier), and deciding which lines need a zone
|
|
13
|
+
* abbreviation to be told apart. The formatting itself is formatInstant's,
|
|
14
|
+
* shared with the rendered text, so the two surfaces cannot drift.
|
|
15
|
+
*
|
|
16
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
17
|
+
* - /packages/core/src/Timestamp/Timestamp.tsx
|
|
18
|
+
* - /packages/core/src/Timestamp/formatInstant.ts
|
|
19
|
+
* - /packages/core/src/Timestamp/tooltipEntries.test.ts
|
|
20
|
+
* - /packages/core/src/Timestamp/Timestamp.doc.mjs
|
|
21
|
+
* - /packages/core/src/Timestamp/index.ts
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {devWarn} from '../utils/devWarning';
|
|
25
|
+
import {formatInstant} from './formatInstant';
|
|
26
|
+
import type {InstantFormat} from './formatInstant';
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Formats available to a tooltip line.
|
|
34
|
+
*
|
|
35
|
+
* Every `TimestampFormat` that names a fixed instant, plus `'full'` — the long
|
|
36
|
+
* absolute style the tooltip has always shown ("February 19, 2026 at 5:00:00 PM
|
|
37
|
+
* UTC"). `'relative'` and `'auto'` are excluded: a relative phrase ignores any
|
|
38
|
+
* zone, which would make `timezoneID` silently inert on that line.
|
|
39
|
+
*
|
|
40
|
+
* `'full'` lives only in this vocabulary, never in `TimestampFormat` — nothing
|
|
41
|
+
* asked for it as a visible display format, and keeping it out means new
|
|
42
|
+
* members added to `TimestampFormat` become valid tooltip formats for free.
|
|
43
|
+
*
|
|
44
|
+
* The public spelling of {@link InstantFormat}: consumers reading Timestamp's
|
|
45
|
+
* props should not have to know the name of its internal formatter.
|
|
46
|
+
*/
|
|
47
|
+
export type TimestampTooltipFormat = InstantFormat;
|
|
48
|
+
|
|
49
|
+
/** One line of the Timestamp tooltip. */
|
|
50
|
+
export interface TimestampTooltipEntry {
|
|
51
|
+
/**
|
|
52
|
+
* IANA time zone identifier, e.g. `'UTC'`, `'America/Los_Angeles'`.
|
|
53
|
+
* Omit it — or pass `'local'` — for the viewer's own zone.
|
|
54
|
+
*
|
|
55
|
+
* Prefer region identifiers. Fixed-offset abbreviations such as `'EST'` are
|
|
56
|
+
* accepted by the platform but never observe daylight saving, so they read
|
|
57
|
+
* an hour wrong for half the year — `'America/New_York'` is what people
|
|
58
|
+
* usually mean by "Eastern".
|
|
59
|
+
*
|
|
60
|
+
* An identifier the platform does not recognize falls back to the viewer's
|
|
61
|
+
* zone with a console warning rather than throwing.
|
|
62
|
+
*/
|
|
63
|
+
timezoneID?: string;
|
|
64
|
+
/**
|
|
65
|
+
* How this line renders the instant.
|
|
66
|
+
* @default 'full'
|
|
67
|
+
*/
|
|
68
|
+
format?: TimestampTooltipFormat;
|
|
69
|
+
/**
|
|
70
|
+
* Text shown beside the time, e.g. `'Local'`, `'UTC'`, `'Pacific'`.
|
|
71
|
+
* Supplied already translated; Timestamp never invents or localizes labels.
|
|
72
|
+
*/
|
|
73
|
+
label?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A rendered tooltip line. */
|
|
77
|
+
export interface TimestampTooltipLine {
|
|
78
|
+
label?: string;
|
|
79
|
+
value: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Zone resolution
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
/** Spelling that means "the viewer's own zone" without naming it. */
|
|
87
|
+
const LOCAL_ZONE_ALIAS = 'local';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Zone identifiers already reported as unknown.
|
|
91
|
+
*
|
|
92
|
+
* Resolution runs once per entry on every render, so without this a single
|
|
93
|
+
* typo on a live timestamp would emit the same warning every tick, forever.
|
|
94
|
+
* One report per bad identifier is enough to act on.
|
|
95
|
+
*/
|
|
96
|
+
const warnedTimezoneIDs = new Set<string>();
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolves an entry's zone to something safe to hand to `Intl`.
|
|
100
|
+
*
|
|
101
|
+
* Returns `undefined` for the viewer's own zone, which is also what `Intl`
|
|
102
|
+
* treats as "use the host zone" — so the local path never constructs a
|
|
103
|
+
* formatter with an explicit `timeZone` and cannot drift from today's output.
|
|
104
|
+
*/
|
|
105
|
+
function resolveTimezoneID(timezoneID: string | undefined): string | undefined {
|
|
106
|
+
if (
|
|
107
|
+
timezoneID === undefined ||
|
|
108
|
+
timezoneID.toLowerCase() === LOCAL_ZONE_ALIAS
|
|
109
|
+
) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// An unrecognized identifier makes every Intl.DateTimeFormat constructor
|
|
114
|
+
// throw a RangeError. Rendering a timestamp must never take down the tree —
|
|
115
|
+
// degrade to the viewer's zone and say so, mirroring how an unparseable
|
|
116
|
+
// `value` is handled in Timestamp.tsx.
|
|
117
|
+
try {
|
|
118
|
+
new Intl.DateTimeFormat(undefined, {timeZone: timezoneID});
|
|
119
|
+
} catch {
|
|
120
|
+
if (!warnedTimezoneIDs.has(timezoneID)) {
|
|
121
|
+
warnedTimezoneIDs.add(timezoneID);
|
|
122
|
+
devWarn(
|
|
123
|
+
'Timestamp',
|
|
124
|
+
`unknown time zone ${JSON.stringify(timezoneID)} in tooltipEntries. Falling back to the viewer's time zone.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return timezoneID;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A stable key for "which zone did the consumer ask for", used to decide
|
|
135
|
+
* whether the tooltip needs zone abbreviations to disambiguate its lines.
|
|
136
|
+
*
|
|
137
|
+
* Deliberately keyed on the *requested* zone, not on the resolved offset: a
|
|
138
|
+
* consumer who asks for two zones gets both labelled even when the viewer
|
|
139
|
+
* happens to be sitting in one of them, so the rendering does not change shape
|
|
140
|
+
* depending on where the reader is. That also keeps this testable under any
|
|
141
|
+
* machine timezone.
|
|
142
|
+
*/
|
|
143
|
+
function zoneKey(resolved: string | undefined): string {
|
|
144
|
+
return resolved === undefined ? LOCAL_ZONE_ALIAS : resolved.toLowerCase();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// Zone marking
|
|
149
|
+
// =============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Whether a format should carry a zone abbreviation.
|
|
153
|
+
*
|
|
154
|
+
* `'full'` always has one (that is the style). `system_*` never does — those
|
|
155
|
+
* are machine shapes and a trailing "PST" would break anything parsing them,
|
|
156
|
+
* which is also why `isTimezoneShown` has never applied to them.
|
|
157
|
+
*
|
|
158
|
+
* The two human formats that can carry one do so when either the reader has
|
|
159
|
+
* to tell several lines apart, or the line shows a zone the consumer named
|
|
160
|
+
* explicitly. That second case matters even for a lone entry: the tooltip is
|
|
161
|
+
* the `<time>` element's `aria-describedby`, and that element's accessible
|
|
162
|
+
* name is the absolute time in the *viewer's* zone — so an unmarked foreign
|
|
163
|
+
* time is announced directly after a local one with nothing to distinguish
|
|
164
|
+
* them. A line the consumer did not steer stays unmarked, matching how the
|
|
165
|
+
* visible text reads with `isTimezoneShown` off.
|
|
166
|
+
*/
|
|
167
|
+
function shouldShowZoneName(
|
|
168
|
+
format: TimestampTooltipFormat,
|
|
169
|
+
hasMultipleZones: boolean,
|
|
170
|
+
isNamedZone: boolean,
|
|
171
|
+
): boolean {
|
|
172
|
+
if (format === 'full') {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (format === 'date_time' || format === 'time') {
|
|
176
|
+
return hasMultipleZones || isNamedZone;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Renders one tooltip line per entry, in the order given.
|
|
183
|
+
*
|
|
184
|
+
* Pure: the same `date` and `entries` always produce the same lines for a given
|
|
185
|
+
* host zone and locale.
|
|
186
|
+
*/
|
|
187
|
+
export function formatTooltipLines(
|
|
188
|
+
date: Date,
|
|
189
|
+
entries: ReadonlyArray<TimestampTooltipEntry>,
|
|
190
|
+
): ReadonlyArray<TimestampTooltipLine> {
|
|
191
|
+
const resolved = entries.map(entry => resolveTimezoneID(entry.timezoneID));
|
|
192
|
+
const hasMultipleZones = new Set(resolved.map(zoneKey)).size > 1;
|
|
193
|
+
|
|
194
|
+
return entries.map((entry, index) => {
|
|
195
|
+
const format = entry.format ?? 'full';
|
|
196
|
+
const timeZone = resolved[index];
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
...(entry.label === undefined ? {} : {label: entry.label}),
|
|
200
|
+
value: formatInstant(date, format, {
|
|
201
|
+
timeZone,
|
|
202
|
+
isTimezoneShown: shouldShowZoneName(
|
|
203
|
+
format,
|
|
204
|
+
hasMultipleZones,
|
|
205
|
+
timeZone !== undefined,
|
|
206
|
+
),
|
|
207
|
+
}),
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
}
|
package/src/Token/Token.test.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {describe, it, expect, vi} from 'vitest';
|
|
|
13
13
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {Token} from './Token';
|
|
16
|
+
import type {TokenColor} from './Token';
|
|
16
17
|
|
|
17
18
|
describe('Token', () => {
|
|
18
19
|
it('renders with label', () => {
|
|
@@ -51,6 +52,23 @@ describe('Token', () => {
|
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
it('reflects a custom (theme-augmented) color as a class and data attribute', () => {
|
|
56
|
+
// Themes extend TokenColorMap via module augmentation and supply the
|
|
57
|
+
// styling through generated theme CSS; the runtime just forwards the
|
|
58
|
+
// value as the stable class + data-color reflection so the theme
|
|
59
|
+
// selector can match. Cast simulates a consumer-augmented color.
|
|
60
|
+
render(
|
|
61
|
+
<Token
|
|
62
|
+
label="Brand"
|
|
63
|
+
color={'brand' as TokenColor}
|
|
64
|
+
data-testid="token-custom"
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
const token = screen.getByTestId('token-custom');
|
|
68
|
+
expect(token).toHaveClass('astryx-token', 'brand');
|
|
69
|
+
expect(token).toHaveAttribute('data-color', 'brand');
|
|
70
|
+
});
|
|
71
|
+
|
|
54
72
|
it('renders as a span with invisible button when onClick is provided', () => {
|
|
55
73
|
const handleClick = vi.fn();
|
|
56
74
|
render(
|
|
@@ -135,10 +153,7 @@ describe('Token', () => {
|
|
|
135
153
|
|
|
136
154
|
it('renders endContent', () => {
|
|
137
155
|
render(
|
|
138
|
-
<Token
|
|
139
|
-
label="Token"
|
|
140
|
-
endContent={<span data-testid="end">End</span>}
|
|
141
|
-
/>,
|
|
156
|
+
<Token label="Token" endContent={<span data-testid="end">End</span>} />,
|
|
142
157
|
);
|
|
143
158
|
expect(screen.getByTestId('end')).toBeInTheDocument();
|
|
144
159
|
expect(screen.getByText('End')).toBeInTheDocument();
|
|
@@ -241,12 +256,7 @@ describe('Token accessibility', () => {
|
|
|
241
256
|
|
|
242
257
|
it('disables both buttons when isDisabled is true', () => {
|
|
243
258
|
render(
|
|
244
|
-
<Token
|
|
245
|
-
label="Token"
|
|
246
|
-
onClick={() => {}}
|
|
247
|
-
onRemove={() => {}}
|
|
248
|
-
isDisabled
|
|
249
|
-
/>,
|
|
259
|
+
<Token label="Token" onClick={() => {}} onRemove={() => {}} isDisabled />,
|
|
250
260
|
);
|
|
251
261
|
const buttons = screen.getAllByRole('button');
|
|
252
262
|
for (const button of buttons) {
|
|
@@ -355,11 +365,7 @@ describe('Token focus outline', () => {
|
|
|
355
365
|
it('invisible button does not show its own focus outline', async () => {
|
|
356
366
|
const user = userEvent.setup();
|
|
357
367
|
render(
|
|
358
|
-
<Token
|
|
359
|
-
label="Focusable"
|
|
360
|
-
onClick={() => {}}
|
|
361
|
-
data-testid="focus-token"
|
|
362
|
-
/>,
|
|
368
|
+
<Token label="Focusable" onClick={() => {}} data-testid="focus-token" />,
|
|
363
369
|
);
|
|
364
370
|
const button = screen.getByRole('button', {name: 'Focusable'});
|
|
365
371
|
await user.tab();
|
package/src/Token/Token.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Token.tsx
|
|
7
7
|
* @input Uses React, ReactNode, StyleXStyles
|
|
8
|
-
* @output Exports Token component, TokenProps, TokenColor types
|
|
8
|
+
* @output Exports Token component, TokenProps, TokenColor, TokenColorMap types
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by Token.test.tsx
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
@@ -41,20 +41,37 @@ import {useTranslator} from '../i18n';
|
|
|
41
41
|
// =============================================================================
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Extensible color map for Token.
|
|
45
|
+
*
|
|
46
|
+
* Theme packages can add custom colors via TypeScript module augmentation:
|
|
47
|
+
* @example
|
|
48
|
+
* ```
|
|
49
|
+
* declare module '@astryxdesign/core/Token' {
|
|
50
|
+
* interface TokenColorMap {
|
|
51
|
+
* brand: true;
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export interface TokenColorMap {
|
|
57
|
+
default: true;
|
|
58
|
+
red: true;
|
|
59
|
+
orange: true;
|
|
60
|
+
yellow: true;
|
|
61
|
+
green: true;
|
|
62
|
+
teal: true;
|
|
63
|
+
cyan: true;
|
|
64
|
+
blue: true;
|
|
65
|
+
purple: true;
|
|
66
|
+
pink: true;
|
|
67
|
+
gray: true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Token color type derived from TokenColorMap.
|
|
72
|
+
* Extensible via module augmentation of TokenColorMap.
|
|
45
73
|
*/
|
|
46
|
-
export type TokenColor =
|
|
47
|
-
| 'default'
|
|
48
|
-
| 'red'
|
|
49
|
-
| 'orange'
|
|
50
|
-
| 'yellow'
|
|
51
|
-
| 'green'
|
|
52
|
-
| 'teal'
|
|
53
|
-
| 'cyan'
|
|
54
|
-
| 'blue'
|
|
55
|
-
| 'purple'
|
|
56
|
-
| 'pink'
|
|
57
|
-
| 'gray';
|
|
74
|
+
export type TokenColor = keyof TokenColorMap;
|
|
58
75
|
|
|
59
76
|
export type TokenSize = 'sm' | 'md' | 'lg';
|
|
60
77
|
|
package/src/Token/index.ts
CHANGED
|
@@ -192,7 +192,14 @@ export const docs = {
|
|
|
192
192
|
{
|
|
193
193
|
name: 'handleRef',
|
|
194
194
|
type: 'React.Ref<TokenizerHandle>',
|
|
195
|
-
description:
|
|
195
|
+
description:
|
|
196
|
+
'Imperative handle exposing focusInput(), focusFirstToken(), focusLastToken(), clearInput(), and selectAll().',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'width',
|
|
200
|
+
type: 'SizeValue',
|
|
201
|
+
description:
|
|
202
|
+
'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.',
|
|
196
203
|
},
|
|
197
204
|
{
|
|
198
205
|
name: 'xstyle',
|
|
@@ -739,7 +739,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
739
739
|
isTruncated && styles.truncatedWrapper,
|
|
740
740
|
isDisabled && inputWrapperStyles.disabled,
|
|
741
741
|
status && inputStatusBorderStyles[status.type],
|
|
742
|
-
status && inputStatusHoverShadowStyles[status.type],
|
|
742
|
+
status && !isDisabled && inputStatusHoverShadowStyles[status.type],
|
|
743
743
|
status && inputStatusFocusWithinStyles[status.type],
|
|
744
744
|
),
|
|
745
745
|
)}>
|
|
@@ -837,7 +837,9 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
837
837
|
isTruncated && styles.truncatedWrapper,
|
|
838
838
|
isDisabled && inputWrapperStyles.disabled,
|
|
839
839
|
status && inputStatusBorderStyles[status.type],
|
|
840
|
-
status &&
|
|
840
|
+
status &&
|
|
841
|
+
!isDisabled &&
|
|
842
|
+
inputStatusHoverShadowStyles[status.type],
|
|
841
843
|
status && inputStatusFocusWithinStyles[status.type],
|
|
842
844
|
),
|
|
843
845
|
)}>
|
|
@@ -221,4 +221,50 @@ describe('Tooltip', () => {
|
|
|
221
221
|
expect(onOpenChange).not.toHaveBeenCalledWith(false);
|
|
222
222
|
});
|
|
223
223
|
});
|
|
224
|
+
|
|
225
|
+
describe('press-to-dismiss', () => {
|
|
226
|
+
it('hides the tooltip when the trigger is pressed', async () => {
|
|
227
|
+
const onOpenChange = vi.fn();
|
|
228
|
+
render(
|
|
229
|
+
<Tooltip content="Copy link" onOpenChange={onOpenChange} delay={0}>
|
|
230
|
+
<button type="button">Trigger</button>
|
|
231
|
+
</Tooltip>,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const trigger = screen.getByRole('button', {name: 'Trigger'});
|
|
235
|
+
fireEvent.mouseEnter(trigger);
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
fireEvent.pointerDown(trigger);
|
|
241
|
+
await waitFor(() => {
|
|
242
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('does not press-dismiss a controlled tooltip', async () => {
|
|
247
|
+
const onOpenChange = vi.fn();
|
|
248
|
+
render(
|
|
249
|
+
<Tooltip
|
|
250
|
+
content="Controlled"
|
|
251
|
+
isOpen
|
|
252
|
+
onOpenChange={onOpenChange}
|
|
253
|
+
delay={0}>
|
|
254
|
+
<button type="button">Trigger</button>
|
|
255
|
+
</Tooltip>,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const trigger = screen.getByRole('button', {name: 'Trigger'});
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
261
|
+
});
|
|
262
|
+
onOpenChange.mockClear();
|
|
263
|
+
|
|
264
|
+
fireEvent.pointerDown(trigger);
|
|
265
|
+
// Give any (incorrect) async hide a chance to run.
|
|
266
|
+
await new Promise(r => setTimeout(r, 20));
|
|
267
|
+
expect(onOpenChange).not.toHaveBeenCalledWith(false);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
224
270
|
});
|
|
@@ -366,6 +366,19 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
366
366
|
scheduleHide();
|
|
367
367
|
}, [scheduleHide]);
|
|
368
368
|
|
|
369
|
+
// Pressing the trigger hides its own tooltip: once the control is activated
|
|
370
|
+
// the hint has served its purpose, and a tooltip lingering over a
|
|
371
|
+
// just-pressed control reads as stale. Fires on pointerdown so it feels
|
|
372
|
+
// immediate. Uncontrolled tooltips only — a controlled tooltip's visibility
|
|
373
|
+
// is owned by the consumer. `layer.hide()` self-guards when already closed.
|
|
374
|
+
const handlePointerDown = useCallback(() => {
|
|
375
|
+
if (isOpen !== undefined) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
clearTimeouts();
|
|
379
|
+
layer.hide();
|
|
380
|
+
}, [isOpen, clearTimeouts, layer]);
|
|
381
|
+
|
|
369
382
|
// Interaction ref that handles event listeners only
|
|
370
383
|
const interactionRef: RefCallback<HTMLElement> = useCallback(
|
|
371
384
|
(el: HTMLElement | null) => {
|
|
@@ -375,12 +388,18 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
375
388
|
triggerRef.current.removeEventListener('mouseleave', handleMouseLeave);
|
|
376
389
|
triggerRef.current.removeEventListener('focusin', handleFocusIn);
|
|
377
390
|
triggerRef.current.removeEventListener('focusout', handleFocusOut);
|
|
391
|
+
triggerRef.current.removeEventListener(
|
|
392
|
+
'pointerdown',
|
|
393
|
+
handlePointerDown,
|
|
394
|
+
);
|
|
378
395
|
}
|
|
379
396
|
|
|
380
397
|
if (el) {
|
|
381
398
|
// Attach hover listeners
|
|
382
399
|
el.addEventListener('mouseenter', handleMouseEnter);
|
|
383
400
|
el.addEventListener('mouseleave', handleMouseLeave);
|
|
401
|
+
// Press-to-dismiss: activating the trigger hides its own tooltip.
|
|
402
|
+
el.addEventListener('pointerdown', handlePointerDown);
|
|
384
403
|
|
|
385
404
|
// Attach focus listeners based on focusTrigger option
|
|
386
405
|
const shouldAttachFocus =
|
|
@@ -401,6 +420,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
401
420
|
handleMouseLeave,
|
|
402
421
|
handleFocusIn,
|
|
403
422
|
handleFocusOut,
|
|
423
|
+
handlePointerDown,
|
|
404
424
|
],
|
|
405
425
|
);
|
|
406
426
|
|
|
@@ -420,12 +420,103 @@ describe('TopNavItem', () => {
|
|
|
420
420
|
|
|
421
421
|
it('applies aria-disabled when isDisabled', () => {
|
|
422
422
|
render(<TopNavItem label="Home" href="#" isDisabled />);
|
|
423
|
-
expect(screen.
|
|
423
|
+
expect(screen.getByText('Home').closest('a')).toHaveAttribute(
|
|
424
|
+
'aria-disabled',
|
|
425
|
+
'true',
|
|
426
|
+
);
|
|
424
427
|
});
|
|
425
428
|
|
|
426
429
|
it('sets tabIndex to -1 when disabled', () => {
|
|
427
430
|
render(<TopNavItem label="Home" href="#" isDisabled />);
|
|
428
|
-
expect(screen.
|
|
431
|
+
expect(screen.getByText('Home').closest('a')).toHaveAttribute(
|
|
432
|
+
'tabIndex',
|
|
433
|
+
'-1',
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('disabled items do not navigate', () => {
|
|
438
|
+
it('drops href/target and suppresses clicks when disabled (default mode)', () => {
|
|
439
|
+
const handleClick = vi.fn();
|
|
440
|
+
render(
|
|
441
|
+
<TopNavItem
|
|
442
|
+
label="Home"
|
|
443
|
+
href="/home"
|
|
444
|
+
target="_blank"
|
|
445
|
+
onClick={handleClick}
|
|
446
|
+
isDisabled
|
|
447
|
+
/>,
|
|
448
|
+
);
|
|
449
|
+
// An href-less anchor exposes no link role, so it is not announced
|
|
450
|
+
// with a live destination.
|
|
451
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
452
|
+
const item = screen.getByText('Home').closest('a') as HTMLAnchorElement;
|
|
453
|
+
expect(item).not.toHaveAttribute('href');
|
|
454
|
+
expect(item).not.toHaveAttribute('target');
|
|
455
|
+
expect(item).toHaveAttribute('aria-disabled', 'true');
|
|
456
|
+
expect(item).toHaveAttribute('tabIndex', '-1');
|
|
457
|
+
|
|
458
|
+
// fireEvent bypasses pointer-events: none, simulating programmatic/AT
|
|
459
|
+
// activation. It returns false when preventDefault was called, i.e.
|
|
460
|
+
// any default navigation behavior is cancelled.
|
|
461
|
+
const notCancelled = fireEvent.click(item);
|
|
462
|
+
expect(notCancelled).toBe(false);
|
|
463
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('drops href/target and suppresses clicks when disabled (drawer mode)', () => {
|
|
467
|
+
const handleClick = vi.fn();
|
|
468
|
+
render(
|
|
469
|
+
<TopNavRenderContext value="drawer">
|
|
470
|
+
<TopNavItem
|
|
471
|
+
label="Home"
|
|
472
|
+
href="/home"
|
|
473
|
+
target="_blank"
|
|
474
|
+
onClick={handleClick}
|
|
475
|
+
isDisabled
|
|
476
|
+
/>
|
|
477
|
+
</TopNavRenderContext>,
|
|
478
|
+
);
|
|
479
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
480
|
+
const item = screen.getByText('Home').closest('a') as HTMLAnchorElement;
|
|
481
|
+
expect(item).not.toHaveAttribute('href');
|
|
482
|
+
expect(item).not.toHaveAttribute('target');
|
|
483
|
+
expect(item).toHaveAttribute('aria-disabled', 'true');
|
|
484
|
+
expect(item).toHaveAttribute('tabIndex', '-1');
|
|
485
|
+
|
|
486
|
+
// fireEvent bypasses pointer-events: none, simulating programmatic/AT
|
|
487
|
+
// activation. It returns false when preventDefault was called.
|
|
488
|
+
const notCancelled = fireEvent.click(item);
|
|
489
|
+
expect(notCancelled).toBe(false);
|
|
490
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('keeps href and click behavior for enabled items (default mode)', async () => {
|
|
494
|
+
const user = userEvent.setup();
|
|
495
|
+
const handleClick = vi.fn();
|
|
496
|
+
render(<TopNavItem label="Home" href="#" onClick={handleClick} />);
|
|
497
|
+
const link = screen.getByRole('link', {name: 'Home'});
|
|
498
|
+
expect(link).toHaveAttribute('href', '#');
|
|
499
|
+
expect(link).not.toHaveAttribute('aria-disabled');
|
|
500
|
+
|
|
501
|
+
await user.click(link);
|
|
502
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('keeps href and click behavior for enabled items (drawer mode)', async () => {
|
|
506
|
+
const user = userEvent.setup();
|
|
507
|
+
const handleClick = vi.fn();
|
|
508
|
+
render(
|
|
509
|
+
<TopNavRenderContext value="drawer">
|
|
510
|
+
<TopNavItem label="Home" href="#" onClick={handleClick} />
|
|
511
|
+
</TopNavRenderContext>,
|
|
512
|
+
);
|
|
513
|
+
const link = screen.getByRole('link', {name: 'Home'});
|
|
514
|
+
expect(link).toHaveAttribute('href', '#');
|
|
515
|
+
expect(link).not.toHaveAttribute('aria-disabled');
|
|
516
|
+
|
|
517
|
+
await user.click(link);
|
|
518
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
519
|
+
});
|
|
429
520
|
});
|
|
430
521
|
|
|
431
522
|
it('renders icon with label', () => {
|
|
@@ -29,7 +29,7 @@ export const docs = {
|
|
|
29
29
|
{
|
|
30
30
|
name: 'isDisabled',
|
|
31
31
|
type: 'boolean',
|
|
32
|
-
description: 'Whether the nav item is disabled. Sets aria-disabled and prevents interaction.',
|
|
32
|
+
description: 'Whether the nav item is disabled. Sets aria-disabled, drops href/target so the item cannot navigate, and prevents interaction.',
|
|
33
33
|
default: 'false',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -91,7 +91,7 @@ export const docsZh = {
|
|
|
91
91
|
{
|
|
92
92
|
name: 'isDisabled',
|
|
93
93
|
type: 'boolean',
|
|
94
|
-
description: '导航项是否被禁用。设置 aria-disabled
|
|
94
|
+
description: '导航项是否被禁用。设置 aria-disabled,移除 href/target 使其无法导航,并阻止交互。',
|
|
95
95
|
default: 'false',
|
|
96
96
|
},
|
|
97
97
|
{
|
|
@@ -121,7 +121,7 @@ export const docsDense = {
|
|
|
121
121
|
label: 'Visible text or aria-label when isIconOnly is true.',
|
|
122
122
|
href: 'Navigation URL.',
|
|
123
123
|
isSelected: 'Sets aria-current="page"+highlighted styles.',
|
|
124
|
-
isDisabled: 'Sets aria-disabled, prevents interaction.',
|
|
124
|
+
isDisabled: 'Sets aria-disabled, drops href/target (no navigation), prevents interaction.',
|
|
125
125
|
icon: 'Icon before label.',
|
|
126
126
|
children: 'Custom content instead of label text.',
|
|
127
127
|
as: 'Custom link component. Overrides LinkProvider default. Must accept href, className, style, children.',
|