@astryxdesign/core 0.1.4 → 0.1.5-canary.76e6697
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +141 -0
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +20 -18
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts +0 -2
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/useImperativeAlertDialog.js +3 -5
- package/dist/AppShell/AppShell.d.ts +1 -1
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +55 -53
- package/dist/AspectRatio/AspectRatio.d.ts +48 -10
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +33 -12
- package/dist/AspectRatio/index.d.ts +1 -1
- package/dist/AspectRatio/index.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +28 -26
- package/dist/Avatar/AvatarStatusDot.js +6 -6
- package/dist/AvatarGroup/AvatarGroup.js +6 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -7
- package/dist/Badge/Badge.js +3 -3
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +32 -25
- package/dist/Blockquote/Blockquote.js +7 -7
- package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
- package/dist/Breadcrumbs/Breadcrumbs.js +7 -7
- package/dist/Button/Button.js +19 -19
- package/dist/ButtonGroup/ButtonGroup.js +7 -7
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +68 -48
- package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +18 -10
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +24 -21
- package/dist/Center/Center.js +3 -3
- package/dist/Chat/ChatComposer.js +23 -23
- package/dist/Chat/ChatComposerDrawer.js +14 -14
- package/dist/Chat/ChatComposerInput.d.ts +1 -1
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +27 -15
- package/dist/Chat/ChatDictationButton.d.ts +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +13 -11
- package/dist/Chat/ChatLayout.js +17 -17
- package/dist/Chat/ChatLayoutScrollButton.d.ts +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +12 -10
- package/dist/Chat/ChatMessage.js +12 -12
- package/dist/Chat/ChatMessageBubble.js +7 -7
- package/dist/Chat/ChatMessageList.js +14 -14
- package/dist/Chat/ChatMessageMetadata.d.ts +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +14 -12
- package/dist/Chat/ChatPastedTextToken.js +13 -13
- package/dist/Chat/ChatSendButton.js +3 -3
- package/dist/Chat/ChatSystemMessage.d.ts +1 -1
- package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
- package/dist/Chat/ChatSystemMessage.js +17 -17
- package/dist/Chat/ChatTokenizedText.d.ts +1 -1
- package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
- package/dist/Chat/ChatTokenizedText.js +24 -15
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +81 -74
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -15
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +25 -23
- package/dist/CheckboxList/CheckboxList.js +9 -9
- package/dist/CheckboxList/CheckboxListItem.d.ts +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +8 -6
- package/dist/Citation/Citation.js +10 -10
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +32 -9
- package/dist/Code/Code.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +88 -52
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +109 -22
- package/dist/Collapsible/CollapsibleGroup.d.ts +37 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +64 -12
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +24 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +26 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.d.ts +4 -1
- package/dist/Collapsible/useCollapsible.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.js +20 -6
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +36 -34
- package/dist/CommandPalette/CommandPaletteEmpty.js +3 -3
- package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
- package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
- package/dist/CommandPalette/CommandPaletteInput.js +13 -13
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/CommandPalette/CommandPaletteList.js +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts +7 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +76 -43
- package/dist/DateInput/DateInput.js +18 -18
- package/dist/DateRangeInput/DateRangeInput.js +21 -21
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +30 -26
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +9 -9
- package/dist/Dialog/DialogHeader.js +16 -16
- package/dist/Dialog/useImperativeDialog.js +3 -3
- package/dist/Divider/Divider.js +7 -7
- package/dist/DropdownMenu/DropdownMenu.js +10 -10
- package/dist/DropdownMenu/DropdownMenuItem.d.ts +2 -17
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
- package/dist/DropdownMenu/renderDropdownItems.js +10 -10
- package/dist/EmptyState/EmptyState.js +9 -9
- package/dist/Field/Field.js +17 -17
- package/dist/Field/FieldLabel.js +15 -15
- package/dist/Field/InputClearButton.js +5 -5
- package/dist/FieldStatus/FieldStatus.js +3 -3
- package/dist/FileInput/FileInput.js +29 -29
- package/dist/FormLayout/FormLayout.js +5 -5
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +29 -12
- package/dist/Grid/GridSpan.js +3 -3
- package/dist/HStack/HStack.js +3 -3
- package/dist/Heading/Heading.js +10 -10
- package/dist/HoverCard/HoverCard.js +9 -9
- package/dist/HoverCard/useHoverCard.d.ts +5 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +3 -3
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +16 -12
- package/dist/Icon/defaultIcons.js +116 -116
- package/dist/IconButton/IconButton.js +3 -3
- package/dist/InputGroup/InputGroup.js +11 -11
- package/dist/InputGroup/InputGroupText.js +3 -3
- package/dist/Item/Item.js +18 -18
- package/dist/Kbd/Kbd.js +5 -5
- package/dist/Layer/LayerProvider.js +7 -7
- package/dist/Layer/useLayer.d.ts +29 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +44 -31
- package/dist/Layout/Layout.js +21 -21
- package/dist/Layout/LayoutContent.js +3 -3
- package/dist/Layout/LayoutFooter.js +5 -5
- package/dist/Layout/LayoutHeader.js +5 -5
- package/dist/Layout/LayoutPanel.js +3 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +55 -31
- package/dist/Lightbox/useLightbox.d.ts +0 -1
- package/dist/Lightbox/useLightbox.d.ts.map +1 -1
- package/dist/Lightbox/useLightbox.js +3 -4
- package/dist/Link/Link.d.ts +0 -2
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +15 -17
- package/dist/Link/LinkProvider.js +3 -3
- package/dist/Link/useLinkify.js +3 -3
- package/dist/List/List.js +11 -11
- package/dist/List/ListItem.js +11 -11
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +102 -102
- package/dist/MetadataList/MetadataList.js +10 -10
- package/dist/MetadataList/MetadataListItem.js +15 -15
- package/dist/MobileNav/MobileNav.d.ts +1 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +26 -23
- package/dist/MobileNav/MobileNavToggle.js +5 -5
- package/dist/MoreMenu/MoreMenu.js +3 -3
- package/dist/MultiSelector/MultiSelector.js +56 -56
- package/dist/NavIcon/NavIcon.js +3 -3
- package/dist/NavMenu/NavHeadingMenu.js +5 -5
- package/dist/NavMenu/NavHeadingMenuItem.js +8 -8
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +48 -32
- package/dist/Outline/Outline.js +15 -15
- package/dist/OverflowList/OverflowList.js +8 -8
- package/dist/Overlay/Overlay.js +3 -3
- package/dist/Overlay/OverlayScrim.js +5 -5
- package/dist/Overlay/useOverlay.d.ts +0 -3
- package/dist/Overlay/useOverlay.d.ts.map +1 -1
- package/dist/Overlay/useOverlay.js +3 -6
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +125 -72
- package/dist/Popover/Popover.js +16 -16
- package/dist/Popover/usePopover.js +7 -7
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +110 -92
- package/dist/PowerSearch/PowerSearchEditPopover.js +84 -84
- package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
- package/dist/PowerSearch/PowerSearchToken.js +5 -5
- package/dist/PowerSearch/PowerSearchValueEditor.js +54 -54
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +25 -18
- package/dist/RadioList/RadioList.js +7 -7
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +24 -21
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +28 -10
- package/dist/Section/Section.js +5 -5
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +32 -10
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +18 -12
- package/dist/SelectableCard/SelectableCard.js +5 -5
- package/dist/Selector/Selector.js +46 -46
- package/dist/Selector/SelectorOption.js +3 -3
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +38 -34
- package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +8 -6
- package/dist/SideNav/SideNavHeading.js +87 -87
- package/dist/SideNav/SideNavItem.js +42 -42
- package/dist/SideNav/SideNavSection.d.ts +1 -1
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +18 -16
- package/dist/Skeleton/Skeleton.js +3 -3
- package/dist/Slider/Slider.js +23 -23
- package/dist/Spinner/Spinner.js +9 -9
- package/dist/StatusDot/StatusDot.js +5 -5
- package/dist/Switch/Switch.d.ts +8 -4
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +34 -29
- package/dist/TabList/Tab.js +18 -18
- package/dist/TabList/TabList.js +5 -5
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +57 -30
- package/dist/TabList/tab.markers.stylex.d.ts +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +50 -39
- package/dist/Table/Table.js +7 -7
- package/dist/Table/TableBody.d.ts +1 -1
- package/dist/Table/TableBody.d.ts.map +1 -1
- package/dist/Table/TableBody.js +9 -5
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableFooter.d.ts +1 -1
- package/dist/Table/TableFooter.d.ts.map +1 -1
- package/dist/Table/TableFooter.js +9 -5
- package/dist/Table/TableHeader.d.ts +1 -1
- package/dist/Table/TableHeader.d.ts.map +1 -1
- package/dist/Table/TableHeader.js +9 -5
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/TableRow.js +5 -5
- package/dist/Table/index.d.ts +2 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +14 -11
- package/dist/Table/plugins/filtering/useTableFiltering.js +71 -71
- package/dist/Table/plugins/groupedRows/index.d.ts +3 -0
- package/dist/Table/plugins/groupedRows/index.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/index.js +3 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +78 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +250 -0
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +7 -7
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +23 -23
- package/dist/Table/plugins/selection/useTableSelection.js +18 -18
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +17 -17
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Table/tableContextMenu.js +9 -9
- package/dist/Text/Text.js +10 -10
- package/dist/TextArea/TextArea.js +14 -14
- package/dist/TextInput/TextInput.js +15 -15
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +30 -29
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +26 -21
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +37 -26
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -17
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +23 -11
- package/dist/Toast/useToast.d.ts.map +1 -1
- package/dist/Toast/useToast.js +44 -4
- package/dist/ToggleButton/ToggleButton.js +12 -12
- package/dist/ToggleButton/ToggleButtonGroup.js +5 -5
- package/dist/Token/Token.js +21 -21
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +36 -34
- package/dist/Toolbar/Toolbar.js +18 -18
- package/dist/Tooltip/Tooltip.js +11 -11
- package/dist/Tooltip/useTooltip.d.ts +5 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +3 -3
- package/dist/TopNav/TopNav.js +32 -32
- package/dist/TopNav/TopNavHeading.js +67 -67
- package/dist/TopNav/TopNavItem.js +5 -5
- package/dist/TopNav/TopNavMegaMenu.js +30 -30
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +25 -14
- package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
- package/dist/TopNav/TopNavMenu.js +32 -32
- package/dist/TreeList/TreeList.js +8 -8
- package/dist/TreeList/TreeListBranches.js +11 -11
- package/dist/TreeList/TreeListItem.js +30 -30
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +23 -20
- package/dist/Typeahead/Typeahead.js +12 -12
- package/dist/Typeahead/TypeaheadItem.js +10 -10
- package/dist/VStack/VStack.js +3 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +0 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden/VisuallyHidden.js +0 -3
- package/dist/astryx.css +7 -0
- package/dist/astryx.umd.js +63 -63
- package/dist/astryx.umd.js.map +4 -4
- package/dist/docs-types.d.ts +8 -2
- package/dist/docs-types.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- 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/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/hooks/useHotkeys.d.ts +51 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -0
- package/dist/hooks/useHotkeys.js +160 -0
- package/dist/hooks/useInteractiveRole.d.ts +6 -2
- package/dist/hooks/useInteractiveRole.d.ts.map +1 -1
- package/dist/hooks/useInteractiveRole.js +6 -2
- package/dist/hooks/useKeyboardHint.js +9 -9
- package/dist/hooks/useStreamingText.d.ts +3 -1
- package/dist/hooks/useStreamingText.d.ts.map +1 -1
- package/dist/hooks/useStreamingText.js +14 -5
- package/dist/theme/MediaTheme.js +3 -3
- package/dist/theme/Theme.js +7 -7
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/syntax/SyntaxTheme.js +5 -5
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/theme/useTheme.d.ts +3 -2
- package/dist/theme/useTheme.d.ts.map +1 -1
- package/dist/theme/useTheme.js +73 -6
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/composeEventHandlers.d.ts +30 -0
- package/dist/utils/composeEventHandlers.d.ts.map +1 -0
- package/dist/utils/composeEventHandlers.js +40 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +4 -0
- package/src/AlertDialog/AlertDialog.tsx +2 -0
- package/src/AlertDialog/useImperativeAlertDialog.tsx +0 -2
- package/src/AppShell/AppShell.tsx +2 -0
- package/src/AspectRatio/AspectRatio.doc.mjs +28 -1
- package/src/AspectRatio/AspectRatio.test.tsx +142 -1
- package/src/AspectRatio/AspectRatio.tsx +70 -10
- package/src/AspectRatio/index.ts +5 -1
- package/src/Avatar/Avatar.doc.mjs +11 -0
- package/src/Avatar/Avatar.test.tsx +32 -1
- package/src/Avatar/Avatar.tsx +14 -22
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -0
- package/src/AvatarGroup/AvatarGroup.tsx +2 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -2
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +122 -17
- package/src/Calendar/Calendar.tsx +26 -5
- package/src/Calendar/dayCellUtils.ts +3 -1
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +4 -2
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.test.tsx +27 -1
- package/src/Chat/ChatComposerInput.tsx +20 -15
- package/src/Chat/ChatDictationButton.test.tsx +43 -0
- package/src/Chat/ChatDictationButton.tsx +4 -4
- package/src/Chat/ChatLayoutScrollButton.test.tsx +33 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessageMetadata.test.tsx +32 -0
- package/src/Chat/ChatMessageMetadata.tsx +4 -6
- package/src/Chat/ChatSystemMessage.test.tsx +26 -11
- package/src/Chat/ChatSystemMessage.tsx +7 -7
- package/src/Chat/ChatTokenizedText.test.tsx +29 -16
- package/src/Chat/ChatTokenizedText.tsx +15 -8
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/Chat/index.ts +1 -4
- package/src/Chat/useTriggerMenu.tsx +1 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
- package/src/CheckboxInput/CheckboxInput.tsx +2 -0
- package/src/CheckboxList/CheckboxList.test.tsx +19 -0
- package/src/CheckboxList/CheckboxListItem.tsx +11 -2
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +122 -3
- package/src/CodeBlock/CodeBlock.tsx +62 -4
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.test.tsx +403 -0
- package/src/Collapsible/Collapsible.tsx +85 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +314 -0
- package/src/Collapsible/CollapsibleGroup.tsx +97 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +34 -2
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/useCollapsible.ts +20 -6
- package/src/CommandPalette/CommandPalette.doc.mjs +5 -0
- package/src/CommandPalette/CommandPalette.tsx +5 -3
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +87 -0
- package/src/ContextMenu/ContextMenu.tsx +87 -28
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateInput/DateInput.test.tsx +11 -20
- package/src/DateRangeInput/DateRangeInput.doc.mjs +9 -6
- package/src/DateRangeInput/DateRangeInput.test.tsx +31 -44
- package/src/DateTimeInput/DateTimeInput.doc.mjs +3 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +118 -16
- package/src/DateTimeInput/DateTimeInput.tsx +8 -2
- package/src/DateTimeInput/index.ts +1 -0
- package/src/Dialog/Dialog.doc.mjs +4 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +22 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +5 -17
- package/src/EmptyState/EmptyState.doc.mjs +1 -1
- package/src/Field/Field.test.tsx +1 -4
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FieldStatus/FieldStatus.test.tsx +282 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/HoverCard/HoverCard.doc.mjs +6 -6
- package/src/HoverCard/useHoverCard.doc.mjs +5 -5
- package/src/HoverCard/useHoverCard.tsx +9 -2
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.doc.mjs +1 -0
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +185 -0
- package/src/Item/Item.tsx +1 -1
- package/src/Layer/useLayer.doc.mjs +2 -2
- package/src/Layer/useLayer.test.tsx +221 -2
- package/src/Layer/useLayer.tsx +69 -30
- package/src/Layout/Layout.test.tsx +285 -0
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Layout/LayoutSlots.test.tsx +364 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/Lightbox/useLightbox.tsx +0 -1
- package/src/Link/Link.tsx +0 -2
- package/src/Markdown/Markdown.tsx +9 -7
- package/src/Markdown/parser.perf.test.ts +34 -12
- package/src/MobileNav/MobileNav.test.tsx +56 -0
- package/src/MobileNav/MobileNav.tsx +13 -8
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/parseOutlineFromMarkdown.test.ts +80 -0
- package/src/OverflowList/OverflowList.test.tsx +416 -0
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Overlay/Overlay.test.tsx +356 -0
- package/src/Overlay/useOverlay.tsx +0 -3
- package/src/Pagination/Pagination.test.tsx +162 -0
- package/src/Pagination/Pagination.tsx +80 -26
- package/src/Popover/Popover.doc.mjs +6 -6
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/Popover/usePopover.doc.mjs +1 -1
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.doc.mjs +2 -0
- package/src/ProgressBar/ProgressBar.test.tsx +30 -0
- package/src/ProgressBar/ProgressBar.tsx +12 -5
- package/src/RadioList/RadioList.test.tsx +19 -0
- package/src/RadioList/RadioListItem.tsx +9 -4
- package/src/Resizable/ResizeHandle.test.tsx +218 -0
- package/src/Resizable/ResizeHandle.tsx +22 -4
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SegmentedControl/SegmentedControl.test.tsx +192 -0
- package/src/SegmentedControl/SegmentedControl.tsx +27 -5
- package/src/SegmentedControl/SegmentedControlItem.tsx +9 -4
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.test.tsx +27 -1
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +66 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/SideNav/SideNavCollapseButton.tsx +4 -3
- package/src/SideNav/SideNavSection.tsx +6 -4
- package/src/SizeContext/SizeContext.test.tsx +226 -0
- package/src/Switch/Switch.doc.mjs +7 -7
- package/src/Switch/Switch.test.tsx +102 -3
- package/src/Switch/Switch.tsx +20 -7
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +13 -2
- package/src/Table/Table.doc.mjs +6 -1
- package/src/Table/Table.test.tsx +87 -0
- package/src/Table/TableBody.tsx +15 -2
- package/src/Table/TableFooter.tsx +15 -2
- package/src/Table/TableHeader.tsx +15 -2
- package/src/Table/index.ts +5 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/groupedRows/index.ts +7 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +231 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +349 -0
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -1
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/Table/useTableGroupedRows.doc.mjs +71 -0
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Timestamp/Timestamp.test.tsx +52 -0
- package/src/Timestamp/Timestamp.tsx +30 -16
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/Toast/useToast.test.tsx +258 -0
- package/src/Toast/useToast.tsx +41 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.test.tsx +54 -0
- package/src/Tokenizer/Tokenizer.tsx +5 -4
- package/src/Tooltip/Tooltip.doc.mjs +6 -6
- package/src/Tooltip/useTooltip.doc.mjs +5 -5
- package/src/Tooltip/useTooltip.tsx +12 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TopNav/TopNav.test.tsx +23 -7
- package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +9 -2
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/VisuallyHidden/VisuallyHidden.tsx +0 -3
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/__tests__/fastRoleQueries.ts +66 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/docs-types.ts +9 -1
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/hooks/useHotkeys.doc.mjs +52 -0
- package/src/hooks/useHotkeys.test.ts +245 -0
- package/src/hooks/useHotkeys.ts +203 -0
- package/src/hooks/useInteractiveRole.ts +12 -4
- package/src/hooks/useStreamingText.test.ts +54 -19
- package/src/hooks/useStreamingText.ts +16 -5
- package/src/hooks/useTreeFocus.test.tsx +1 -3
- package/src/reset.css +29 -0
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/themingTargets.test.ts +353 -0
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +126 -2
- package/src/theme/useTheme.ts +87 -6
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/composeEventHandlers.test.ts +71 -0
- package/src/utils/composeEventHandlers.ts +44 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/mergeProps.test.ts +166 -0
- package/src/utils/sharedResizeObserver.test.ts +2 -2
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
- package/dist/Carousel/carousel.markers.stylex.d.ts +0 -7
- package/dist/Carousel/carousel.markers.stylex.d.ts.map +0 -1
- package/dist/Carousel/carousel.markers.stylex.js +0 -12
- package/src/Carousel/carousel.markers.stylex.ts +0 -10
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {describe, it, expect, vi} from 'vitest';
|
|
11
|
-
import {render, screen, within} from '@testing-library/react';
|
|
11
|
+
import {render, renderHook, screen, within} from '@testing-library/react';
|
|
12
12
|
import userEvent from '@testing-library/user-event';
|
|
13
13
|
import {useState} from 'react';
|
|
14
14
|
import {Table} from '../../Table';
|
|
@@ -421,6 +421,40 @@ describe('useTableSortableState', () => {
|
|
|
421
421
|
});
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
+
describe('NaN handling', () => {
|
|
425
|
+
it('keeps valid numbers sorted when a NaN cell is present', () => {
|
|
426
|
+
const data: Employee[] = [
|
|
427
|
+
{id: '1', name: 'A', age: 5, department: 'X', salary: 1},
|
|
428
|
+
{id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
|
|
429
|
+
{id: '3', name: 'C', age: 1, department: 'X', salary: 1},
|
|
430
|
+
{id: '4', name: 'D', age: 3, department: 'X', salary: 1},
|
|
431
|
+
];
|
|
432
|
+
const {result} = renderHook(() =>
|
|
433
|
+
useTableSortableState({
|
|
434
|
+
data,
|
|
435
|
+
defaultSort: [{sortKey: 'age', direction: 'ascending'}],
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
// NaN groups at the end like null; the valid numbers stay ordered.
|
|
439
|
+
expect(result.current.sortedData.map(e => e.age)).toEqual([1, 3, 5, NaN]);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('sorts NaN to the start in descending order, like null', () => {
|
|
443
|
+
const data: Employee[] = [
|
|
444
|
+
{id: '1', name: 'A', age: 5, department: 'X', salary: 1},
|
|
445
|
+
{id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
|
|
446
|
+
{id: '3', name: 'C', age: 1, department: 'X', salary: 1},
|
|
447
|
+
];
|
|
448
|
+
const {result} = renderHook(() =>
|
|
449
|
+
useTableSortableState({
|
|
450
|
+
data,
|
|
451
|
+
defaultSort: [{sortKey: 'age', direction: 'descending'}],
|
|
452
|
+
}),
|
|
453
|
+
);
|
|
454
|
+
expect(result.current.sortedData.map(e => e.age)).toEqual([NaN, 5, 1]);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
424
458
|
describe('null/undefined handling', () => {
|
|
425
459
|
it('sorts null values to the end', () => {
|
|
426
460
|
const data: Employee[] = [
|
|
@@ -168,14 +168,18 @@ function defaultCompare<T extends Record<string, unknown>>(
|
|
|
168
168
|
const aVal = a[sortKey];
|
|
169
169
|
const bVal = b[sortKey];
|
|
170
170
|
|
|
171
|
-
// null/undefined sort to the end
|
|
172
|
-
|
|
171
|
+
// null/undefined/NaN sort to the end. NaN must not reach the numeric fast
|
|
172
|
+
// path: a NaN comparator result reads as "equal" to Array.sort, which makes
|
|
173
|
+
// the comparator inconsistent and corrupts the order of the other rows.
|
|
174
|
+
const aMissing = aVal == null || (typeof aVal === 'number' && Number.isNaN(aVal));
|
|
175
|
+
const bMissing = bVal == null || (typeof bVal === 'number' && Number.isNaN(bVal));
|
|
176
|
+
if (aMissing && bMissing) {
|
|
173
177
|
return 0;
|
|
174
178
|
}
|
|
175
|
-
if (
|
|
179
|
+
if (aMissing) {
|
|
176
180
|
return 1;
|
|
177
181
|
}
|
|
178
|
-
if (
|
|
182
|
+
if (bMissing) {
|
|
179
183
|
return -1;
|
|
180
184
|
}
|
|
181
185
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../docs-types').ComponentDoc} */
|
|
4
|
+
|
|
5
|
+
export const docs = {
|
|
6
|
+
name: 'useTableGroupedRows',
|
|
7
|
+
subComponentOf: 'Table',
|
|
8
|
+
displayName: 'useTableGroupedRows',
|
|
9
|
+
description:
|
|
10
|
+
'Hook that groups a flat data array into collapsible section rows. Each distinct groupBy value becomes a full-width section-header row with a chevron toggle, the group label, and a member count; collapsing hides that group\u2019s data rows while keeping the header visible. Mirrors useTableRowExpansionState: the consumer owns the collapsedGroups set and the hook returns {data, plugin, idKey} \u2014 pass all three to Table (data, plugins, and idKey respectively).',
|
|
11
|
+
props: [
|
|
12
|
+
{
|
|
13
|
+
name: 'data',
|
|
14
|
+
type: 'T[]',
|
|
15
|
+
description: 'The flat data to group.',
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'groupBy',
|
|
20
|
+
type: '(item: T) => string',
|
|
21
|
+
description:
|
|
22
|
+
'Derive the group key for a row. Rows with the same key share a section.',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'collapsedGroups',
|
|
27
|
+
type: 'Set<string>',
|
|
28
|
+
description: 'Set of currently-collapsed group keys.',
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'onToggleGroup',
|
|
33
|
+
type: '(groupKey: string) => void',
|
|
34
|
+
description: 'Called with a group key when its header is toggled.',
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'renderGroupHeader',
|
|
39
|
+
type: '(groupKey: string, count: number, collapsed: boolean) => ReactNode',
|
|
40
|
+
description:
|
|
41
|
+
"Custom renderer for a group header's content (right of the chevron). Defaults to `<groupKey> (<count>)`.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'getRowKey',
|
|
45
|
+
type: '(item: T) => string',
|
|
46
|
+
description:
|
|
47
|
+
'Stable key for a real row. Falls back to a positional key when omitted.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'groupOrder',
|
|
51
|
+
type: 'string[]',
|
|
52
|
+
description:
|
|
53
|
+
'Explicit group ordering; groups not listed keep first-seen order after these.',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** @type {import('../docs-types').TranslationDoc} */
|
|
59
|
+
export const docsDense = {
|
|
60
|
+
description:
|
|
61
|
+
'Groups a flat data array into collapsible section rows. Each groupBy value becomes a full-width header (chevron + label + count); collapsing hides its rows. Returns {data, plugin, idKey} \u2014 pass to Table data / plugins / idKey. Consumer owns the collapsedGroups set.',
|
|
62
|
+
propDescriptions: {
|
|
63
|
+
data: 'The flat data to group.',
|
|
64
|
+
groupBy: 'Derive a row\u2019s group key. Same key = same section.',
|
|
65
|
+
collapsedGroups: 'Set of currently-collapsed group keys.',
|
|
66
|
+
onToggleGroup: 'Called with the group key when a header is toggled.',
|
|
67
|
+
renderGroupHeader: "Custom header content (right of chevron). Default '<key> (<count>)'.",
|
|
68
|
+
getRowKey: 'Stable key for a real row; positional fallback when omitted.',
|
|
69
|
+
groupOrder: 'Pin these group keys first; others keep first-seen order.',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -13,7 +13,7 @@ import {useState} from 'react';
|
|
|
13
13
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
14
14
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
15
15
|
import userEvent from '@testing-library/user-event';
|
|
16
|
-
import {
|
|
16
|
+
import {TestIcon} from '../__tests__/TestIcon';
|
|
17
17
|
import {TextArea} from './TextArea';
|
|
18
18
|
|
|
19
19
|
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
@@ -193,7 +193,7 @@ describe('TextArea', () => {
|
|
|
193
193
|
label="Description"
|
|
194
194
|
value=""
|
|
195
195
|
onChange={() => {}}
|
|
196
|
-
startIcon={
|
|
196
|
+
startIcon={TestIcon}
|
|
197
197
|
/>,
|
|
198
198
|
);
|
|
199
199
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
@@ -577,7 +577,7 @@ describe('TextArea', () => {
|
|
|
577
577
|
label="Notes"
|
|
578
578
|
value=""
|
|
579
579
|
onChange={() => {}}
|
|
580
|
-
startIcon={<
|
|
580
|
+
startIcon={<TestIcon />}
|
|
581
581
|
/>,
|
|
582
582
|
);
|
|
583
583
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
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
|
-
import {
|
|
15
|
+
import {TestIcon} from '../__tests__/TestIcon';
|
|
16
16
|
import {TextInput} from './TextInput';
|
|
17
17
|
|
|
18
18
|
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
@@ -163,7 +163,7 @@ describe('TextInput', () => {
|
|
|
163
163
|
label="Search"
|
|
164
164
|
value=""
|
|
165
165
|
onChange={() => {}}
|
|
166
|
-
startIcon={
|
|
166
|
+
startIcon={TestIcon}
|
|
167
167
|
/>,
|
|
168
168
|
);
|
|
169
169
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
@@ -482,7 +482,7 @@ describe('TextInput', () => {
|
|
|
482
482
|
label="Search"
|
|
483
483
|
value=""
|
|
484
484
|
onChange={() => {}}
|
|
485
|
-
startIcon={<
|
|
485
|
+
startIcon={<TestIcon />}
|
|
486
486
|
/>,
|
|
487
487
|
);
|
|
488
488
|
|
|
@@ -37,11 +37,45 @@ describe('Thumbnail', () => {
|
|
|
37
37
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it('
|
|
40
|
+
it('exposes the label as an accessible name via a valid group role', () => {
|
|
41
41
|
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
42
|
-
|
|
42
|
+
// The accessible name must be carried by a valid named role (group),
|
|
43
|
+
// not by a bare aria-label on a generic div (aria-prohibited-attr).
|
|
44
|
+
const group = screen.getByRole('group', {name: 'photo.png'});
|
|
45
|
+
expect(group).toBe(screen.getByTestId('thumb'));
|
|
43
46
|
});
|
|
44
47
|
|
|
48
|
+
it('does not put aria-label on a generic (roleless) element', () => {
|
|
49
|
+
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
50
|
+
const thumb = screen.getByTestId('thumb');
|
|
51
|
+
// The labeled element must declare a role so the name is legitimate.
|
|
52
|
+
expect(thumb).toHaveAttribute('role', 'group');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('keeps interactive children accessible while exposing the group name', () => {
|
|
56
|
+
const onRemove = vi.fn();
|
|
57
|
+
render(
|
|
58
|
+
<Thumbnail
|
|
59
|
+
src="/img.jpg"
|
|
60
|
+
alt="Clickable"
|
|
61
|
+
label="file.png"
|
|
62
|
+
onClick={vi.fn()}
|
|
63
|
+
onRemove={onRemove}
|
|
64
|
+
/>,
|
|
65
|
+
);
|
|
66
|
+
// A group role (unlike img) must not hide descendant controls.
|
|
67
|
+
expect(
|
|
68
|
+
screen.getByRole('group', {name: 'file.png — Clickable'}),
|
|
69
|
+
).toBeInTheDocument();
|
|
70
|
+
expect(
|
|
71
|
+
screen.getByRole('button', {name: 'Open file.png — Clickable'}),
|
|
72
|
+
).toBeInTheDocument();
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByRole('button', {name: 'Remove file.png — Clickable'}),
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
45
79
|
it('label is shown via tooltip, not as inline text', () => {
|
|
46
80
|
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
47
81
|
// Label should exist in DOM (tooltip) but not as a direct child text node
|
|
@@ -58,8 +58,9 @@ export interface ThumbnailProps extends BaseProps<HTMLDivElement> {
|
|
|
58
58
|
alt?: string;
|
|
59
59
|
/**
|
|
60
60
|
* Accessible label for the thumbnail (e.g. file name).
|
|
61
|
-
* Not rendered visually — shown in a tooltip on hover
|
|
62
|
-
*
|
|
61
|
+
* Not rendered visually — shown in a tooltip on hover, exposed as the
|
|
62
|
+
* accessible name of the thumbnail group, and used as the accessible
|
|
63
|
+
* name for the remove button.
|
|
63
64
|
*/
|
|
64
65
|
label?: string;
|
|
65
66
|
/**
|
|
@@ -296,6 +297,7 @@ export function Thumbnail({
|
|
|
296
297
|
<div
|
|
297
298
|
ref={ref}
|
|
298
299
|
data-testid={testId}
|
|
300
|
+
role="group"
|
|
299
301
|
aria-label={accessibleName}
|
|
300
302
|
{...mergeProps(
|
|
301
303
|
themeProps('thumbnail'),
|
|
@@ -160,6 +160,43 @@ describe('TimeInput', () => {
|
|
|
160
160
|
expect(onChange).not.toHaveBeenCalled();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it('sets aria-invalid="true" when typed input is out of range', () => {
|
|
164
|
+
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
165
|
+
|
|
166
|
+
const input = screen.getByRole('textbox');
|
|
167
|
+
fireEvent.change(input, {target: {value: '25:99'}});
|
|
168
|
+
|
|
169
|
+
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('does not set aria-invalid when typed input is a valid time', () => {
|
|
173
|
+
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
174
|
+
|
|
175
|
+
const input = screen.getByRole('textbox');
|
|
176
|
+
fireEvent.change(input, {target: {value: '3:45 pm'}});
|
|
177
|
+
|
|
178
|
+
expect(input).not.toHaveAttribute('aria-invalid');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('announces an alert message when typed input is invalid', () => {
|
|
182
|
+
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
183
|
+
|
|
184
|
+
const input = screen.getByRole('textbox');
|
|
185
|
+
fireEvent.change(input, {target: {value: '25:99'}});
|
|
186
|
+
|
|
187
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Invalid time');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('does not announce an alert message when input is valid', () => {
|
|
191
|
+
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
192
|
+
|
|
193
|
+
const input = screen.getByRole('textbox');
|
|
194
|
+
fireEvent.change(input, {target: {value: '3:45 pm'}});
|
|
195
|
+
|
|
196
|
+
expect(screen.getByRole('alert')).toHaveTextContent('');
|
|
197
|
+
expect(screen.queryByText('Invalid time')).not.toBeInTheDocument();
|
|
198
|
+
});
|
|
199
|
+
|
|
163
200
|
it('calls onChange on blur when input is valid', async () => {
|
|
164
201
|
const user = userEvent.setup();
|
|
165
202
|
const onChange = vi.fn();
|
|
@@ -635,7 +635,9 @@ export function TimeInput({
|
|
|
635
635
|
data-autofocus={hasAutoFocus || undefined}
|
|
636
636
|
aria-describedby={ariaDescribedBy}
|
|
637
637
|
aria-required={isRequired === true ? 'true' : undefined}
|
|
638
|
-
aria-invalid={
|
|
638
|
+
aria-invalid={
|
|
639
|
+
status?.type === 'error' || !isInputValid ? 'true' : undefined
|
|
640
|
+
}
|
|
639
641
|
aria-busy={isBusy || undefined}
|
|
640
642
|
aria-labelledby={ariaLabelledBy}
|
|
641
643
|
{...stylex.props(
|
|
@@ -644,6 +646,14 @@ export function TimeInput({
|
|
|
644
646
|
!isInputValid && styles.inputInvalid,
|
|
645
647
|
)}
|
|
646
648
|
/>
|
|
649
|
+
{/*
|
|
650
|
+
Live region announcing invalid typed input to assistive technology.
|
|
651
|
+
The value silently reverts on blur, so without this a screen-reader
|
|
652
|
+
user would get no feedback that their entry was rejected (WCAG 3.3.1).
|
|
653
|
+
*/}
|
|
654
|
+
<VisuallyHidden as="div" role="alert" aria-live="assertive">
|
|
655
|
+
{!isInputValid ? 'Invalid time' : ''}
|
|
656
|
+
</VisuallyHidden>
|
|
647
657
|
{isBusy && <Spinner size="sm" />}
|
|
648
658
|
{hasClear && value && !isDisabled && (
|
|
649
659
|
<button
|
|
@@ -33,6 +33,31 @@ describe('Timestamp', () => {
|
|
|
33
33
|
expect(screen.getByText('2 hours ago')).toBeInTheDocument();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
it('does not round a tier up past its own boundary', () => {
|
|
37
|
+
// Just under each threshold the count must stay within the tier, e.g.
|
|
38
|
+
// 59.98 minutes is "59 minutes ago", never "60 minutes ago".
|
|
39
|
+
const {rerender} = render(
|
|
40
|
+
<Timestamp value={Date.now() / 1000 - 3599} format="relative" />,
|
|
41
|
+
);
|
|
42
|
+
expect(screen.getByText('59 minutes ago')).toBeInTheDocument();
|
|
43
|
+
|
|
44
|
+
rerender(
|
|
45
|
+
<Timestamp value={Date.now() / 1000 - 86399} format="relative" />,
|
|
46
|
+
);
|
|
47
|
+
expect(screen.getByText('23 hours ago')).toBeInTheDocument();
|
|
48
|
+
|
|
49
|
+
rerender(
|
|
50
|
+
<Timestamp value={Date.now() / 1000 - 2591999} format="relative" />,
|
|
51
|
+
);
|
|
52
|
+
expect(screen.getByText('29 days ago')).toBeInTheDocument();
|
|
53
|
+
|
|
54
|
+
// Same guarantee on the future side.
|
|
55
|
+
rerender(
|
|
56
|
+
<Timestamp value={Date.now() / 1000 + 3599} format="relative" />,
|
|
57
|
+
);
|
|
58
|
+
expect(screen.getByText('in 59 minutes')).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
36
61
|
it('renders "now" for very recent times', () => {
|
|
37
62
|
const fiveSecondsAgo = Date.now() / 1000 - 5;
|
|
38
63
|
render(<Timestamp value={fiveSecondsAgo} format="relative" />);
|
|
@@ -290,4 +315,31 @@ describe('Timestamp', () => {
|
|
|
290
315
|
const el = screen.getByRole('time');
|
|
291
316
|
expect(el.textContent).not.toContain('ago');
|
|
292
317
|
});
|
|
318
|
+
|
|
319
|
+
// --- Invalid values ---
|
|
320
|
+
|
|
321
|
+
it('renders nothing instead of crashing on an unparseable string value', () => {
|
|
322
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
323
|
+
try {
|
|
324
|
+
const {container} = render(
|
|
325
|
+
<Timestamp value="not-a-date" data-testid="ts" />,
|
|
326
|
+
);
|
|
327
|
+
expect(container).toBeEmptyDOMElement();
|
|
328
|
+
expect(warn).toHaveBeenCalledWith(
|
|
329
|
+
expect.stringContaining('could not parse value'),
|
|
330
|
+
);
|
|
331
|
+
} finally {
|
|
332
|
+
warn.mockRestore();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('renders nothing instead of crashing on a NaN value', () => {
|
|
337
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
338
|
+
try {
|
|
339
|
+
const {container} = render(<Timestamp value={NaN} data-testid="ts" />);
|
|
340
|
+
expect(container).toBeEmptyDOMElement();
|
|
341
|
+
} finally {
|
|
342
|
+
warn.mockRestore();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
293
345
|
});
|
|
@@ -180,22 +180,22 @@ function getRelativeTimeString(date: Date, now: Date): string {
|
|
|
180
180
|
return 'in a few seconds';
|
|
181
181
|
}
|
|
182
182
|
if (absDiff < HOUR) {
|
|
183
|
-
const mins = Math.
|
|
183
|
+
const mins = Math.floor(absDiff / MINUTE);
|
|
184
184
|
return `in ${mins} ${mins === 1 ? 'minute' : 'minutes'}`;
|
|
185
185
|
}
|
|
186
186
|
if (absDiff < DAY) {
|
|
187
|
-
const hours = Math.
|
|
187
|
+
const hours = Math.floor(absDiff / HOUR);
|
|
188
188
|
return `in ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
|
|
189
189
|
}
|
|
190
190
|
if (absDiff < MONTH) {
|
|
191
|
-
const days = Math.
|
|
191
|
+
const days = Math.floor(absDiff / DAY);
|
|
192
192
|
return `in ${days} ${days === 1 ? 'day' : 'days'}`;
|
|
193
193
|
}
|
|
194
194
|
if (absDiff < YEAR) {
|
|
195
|
-
const months = Math.
|
|
195
|
+
const months = Math.floor(absDiff / MONTH);
|
|
196
196
|
return `in ${months} ${months === 1 ? 'month' : 'months'}`;
|
|
197
197
|
}
|
|
198
|
-
const years = Math.
|
|
198
|
+
const years = Math.floor(absDiff / YEAR);
|
|
199
199
|
return `in ${years} ${years === 1 ? 'year' : 'years'}`;
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -203,25 +203,25 @@ function getRelativeTimeString(date: Date, now: Date): string {
|
|
|
203
203
|
return `${diffSeconds} seconds ago`;
|
|
204
204
|
}
|
|
205
205
|
if (diffSeconds < HOUR) {
|
|
206
|
-
const mins = Math.
|
|
206
|
+
const mins = Math.floor(diffSeconds / MINUTE);
|
|
207
207
|
return `${mins} ${mins === 1 ? 'minute' : 'minutes'} ago`;
|
|
208
208
|
}
|
|
209
209
|
if (diffSeconds < DAY) {
|
|
210
|
-
const hours = Math.
|
|
210
|
+
const hours = Math.floor(diffSeconds / HOUR);
|
|
211
211
|
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
|
|
212
212
|
}
|
|
213
213
|
if (diffSeconds < 2 * DAY) {
|
|
214
214
|
return 'yesterday';
|
|
215
215
|
}
|
|
216
216
|
if (diffSeconds < MONTH) {
|
|
217
|
-
const days = Math.
|
|
217
|
+
const days = Math.floor(diffSeconds / DAY);
|
|
218
218
|
return `${days} days ago`;
|
|
219
219
|
}
|
|
220
220
|
if (diffSeconds < YEAR) {
|
|
221
|
-
const months = Math.
|
|
221
|
+
const months = Math.floor(diffSeconds / MONTH);
|
|
222
222
|
return `${months} ${months === 1 ? 'month' : 'months'} ago`;
|
|
223
223
|
}
|
|
224
|
-
const years = Math.
|
|
224
|
+
const years = Math.floor(diffSeconds / YEAR);
|
|
225
225
|
return `${years} ${years === 1 ? 'year' : 'years'} ago`;
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -360,7 +360,12 @@ export function Timestamp({
|
|
|
360
360
|
const [now, setNow] = useState(() => new Date());
|
|
361
361
|
|
|
362
362
|
const date = parseValue(value);
|
|
363
|
-
|
|
363
|
+
// An unparseable value (a malformed date string, or a NaN timestamp from
|
|
364
|
+
// missing data) yields an Invalid Date, and formatting one throws "Invalid
|
|
365
|
+
// time value" — crashing the whole tree. Compute nothing from it here and
|
|
366
|
+
// bail out below (after the hooks) instead.
|
|
367
|
+
const isValidDate = !Number.isNaN(date.getTime());
|
|
368
|
+
const isoString = isValidDate ? date.toISOString() : '';
|
|
364
369
|
|
|
365
370
|
// Determine effective format
|
|
366
371
|
const diffSeconds = Math.round((now.getTime() - date.getTime()) / 1000);
|
|
@@ -372,19 +377,20 @@ export function Timestamp({
|
|
|
372
377
|
: format;
|
|
373
378
|
|
|
374
379
|
// Format the display text
|
|
375
|
-
const displayText =
|
|
376
|
-
|
|
380
|
+
const displayText = !isValidDate
|
|
381
|
+
? ''
|
|
382
|
+
: effectiveFormat === 'relative'
|
|
377
383
|
? getRelativeTimeString(date, now)
|
|
378
384
|
: isAbsoluteFormat(effectiveFormat)
|
|
379
385
|
? formatTimestamp(date, effectiveFormat, isTimezoneShown)
|
|
380
386
|
: '';
|
|
381
387
|
|
|
382
388
|
// Full absolute text for tooltip and aria-label
|
|
383
|
-
const fullAbsoluteText = getFullAbsoluteString(date);
|
|
389
|
+
const fullAbsoluteText = isValidDate ? getFullAbsoluteString(date) : '';
|
|
384
390
|
|
|
385
391
|
// Live updates
|
|
386
392
|
useEffect(() => {
|
|
387
|
-
if (!isLive || effectiveFormat !== 'relative') {
|
|
393
|
+
if (!isLive || !isValidDate || effectiveFormat !== 'relative') {
|
|
388
394
|
return;
|
|
389
395
|
}
|
|
390
396
|
|
|
@@ -394,7 +400,15 @@ export function Timestamp({
|
|
|
394
400
|
}, interval);
|
|
395
401
|
|
|
396
402
|
return () => clearInterval(timer);
|
|
397
|
-
}, [isLive, effectiveFormat, diffSeconds]);
|
|
403
|
+
}, [isLive, isValidDate, effectiveFormat, diffSeconds]);
|
|
404
|
+
|
|
405
|
+
// Placed after all hooks so the hook order stays stable across renders.
|
|
406
|
+
if (!isValidDate) {
|
|
407
|
+
console.warn(
|
|
408
|
+
`Timestamp: could not parse value ${JSON.stringify(value)} as a date. Rendering nothing.`,
|
|
409
|
+
);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
398
412
|
|
|
399
413
|
const showTooltip = hasTooltip && effectiveFormat === 'relative';
|
|
400
414
|
|
package/src/Toast/Toast.tsx
CHANGED
|
@@ -121,8 +121,14 @@ export function Toast({
|
|
|
121
121
|
// Will be initialized by startTimer when actually used
|
|
122
122
|
const startTimeRef = useRef<number | null>(null);
|
|
123
123
|
|
|
124
|
+
// Read onDismiss through a ref: the viewport re-creates it on every render
|
|
125
|
+
// (another toast arriving/exiting), and a startTimer that depends on it
|
|
126
|
+
// would restart — and un-pause — this toast's timer on unrelated renders.
|
|
127
|
+
const onDismissRef = useRef(onDismiss);
|
|
128
|
+
onDismissRef.current = onDismiss;
|
|
129
|
+
|
|
124
130
|
const startTimer = useCallback(() => {
|
|
125
|
-
if (!isAutoHide) {
|
|
131
|
+
if (!isAutoHide || isPausedRef.current) {
|
|
126
132
|
return;
|
|
127
133
|
}
|
|
128
134
|
if (timerRef.current) {
|
|
@@ -130,9 +136,9 @@ export function Toast({
|
|
|
130
136
|
}
|
|
131
137
|
startTimeRef.current = Date.now();
|
|
132
138
|
timerRef.current = setTimeout(() => {
|
|
133
|
-
|
|
139
|
+
onDismissRef.current('auto');
|
|
134
140
|
}, remainingRef.current);
|
|
135
|
-
}, [isAutoHide
|
|
141
|
+
}, [isAutoHide]);
|
|
136
142
|
|
|
137
143
|
const pauseTimer = useCallback(() => {
|
|
138
144
|
if (!isAutoHide || isPausedRef.current) {
|
|
@@ -165,6 +171,8 @@ export function Toast({
|
|
|
165
171
|
clearTimeout(timerRef.current);
|
|
166
172
|
}
|
|
167
173
|
};
|
|
174
|
+
// startTimer's identity is stable per isAutoHide, so this runs on mount
|
|
175
|
+
// and on a genuine duration change — not on unrelated viewport renders.
|
|
168
176
|
}, [autoHideDuration, startTimer]);
|
|
169
177
|
|
|
170
178
|
// Pause the auto-hide timer while the window is not focused, so a toast
|
|
@@ -212,3 +212,67 @@ describe('ToastViewport region ARIA', () => {
|
|
|
212
212
|
expect(region).not.toHaveAttribute('aria-modal');
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
|
+
|
|
216
|
+
describe('toast timer lifecycle (#3589)', () => {
|
|
217
|
+
it('fires onHide exactly once when dismissed twice during the exit window', () => {
|
|
218
|
+
const onHide = vi.fn();
|
|
219
|
+
renderViewport(
|
|
220
|
+
<ShowToastButton options={{body: 'Once', onHide}} triggerLabel="Show" />,
|
|
221
|
+
);
|
|
222
|
+
act(() => {
|
|
223
|
+
fireEvent.click(screen.getByText('Show'));
|
|
224
|
+
});
|
|
225
|
+
const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
|
|
226
|
+
act(() => {
|
|
227
|
+
fireEvent.click(dismiss);
|
|
228
|
+
});
|
|
229
|
+
// The toast stays mounted during its exit transition; a second click
|
|
230
|
+
// lands on the same still-mounted button.
|
|
231
|
+
act(() => {
|
|
232
|
+
fireEvent.click(dismiss);
|
|
233
|
+
});
|
|
234
|
+
expect(onHide).toHaveBeenCalledTimes(1);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('keeps a window-blur pause alive when another toast arrives', () => {
|
|
238
|
+
vi.useFakeTimers();
|
|
239
|
+
try {
|
|
240
|
+
const onHide = vi.fn();
|
|
241
|
+
renderViewport(
|
|
242
|
+
<>
|
|
243
|
+
<ShowToastButton
|
|
244
|
+
options={{body: 'Paused', autoHideDuration: 3000, onHide}}
|
|
245
|
+
triggerLabel="Show A"
|
|
246
|
+
/>
|
|
247
|
+
<ShowToastButton options={INFO_B} triggerLabel="Show B" />
|
|
248
|
+
</>,
|
|
249
|
+
);
|
|
250
|
+
act(() => {
|
|
251
|
+
fireEvent.click(screen.getByText('Show A'));
|
|
252
|
+
});
|
|
253
|
+
act(() => {
|
|
254
|
+
fireEvent.blur(window);
|
|
255
|
+
});
|
|
256
|
+
// A second toast arriving re-renders the viewport; the paused timer
|
|
257
|
+
// must not silently restart.
|
|
258
|
+
act(() => {
|
|
259
|
+
fireEvent.click(screen.getByText('Show B'));
|
|
260
|
+
});
|
|
261
|
+
act(() => {
|
|
262
|
+
vi.advanceTimersByTime(60_000);
|
|
263
|
+
});
|
|
264
|
+
expect(onHide).not.toHaveBeenCalled();
|
|
265
|
+
// Focus returns: the remaining time resumes and completes normally.
|
|
266
|
+
act(() => {
|
|
267
|
+
fireEvent.focus(window);
|
|
268
|
+
});
|
|
269
|
+
act(() => {
|
|
270
|
+
vi.advanceTimersByTime(60_000);
|
|
271
|
+
});
|
|
272
|
+
expect(onHide).toHaveBeenCalledTimes(1);
|
|
273
|
+
expect(onHide).toHaveBeenCalledWith('auto');
|
|
274
|
+
} finally {
|
|
275
|
+
vi.useRealTimers();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -114,6 +114,9 @@ export function ToastViewport({
|
|
|
114
114
|
|
|
115
115
|
// Show the popover on mount so it enters the top layer.
|
|
116
116
|
const viewportRef = useRef<HTMLDivElement>(null);
|
|
117
|
+
// Toast ids whose exit has begun — guards onHide from double-firing (see
|
|
118
|
+
// removeToast). Mirrors exitingIds state, readable synchronously.
|
|
119
|
+
const exitingIdsRef = useRef<Set<string>>(new Set());
|
|
117
120
|
// The element that was focused before the user jumped into the viewport
|
|
118
121
|
// (via F6). Used to restore focus once all toasts are dismissed so focus
|
|
119
122
|
// never falls to <body>.
|
|
@@ -171,6 +174,14 @@ export function ToastViewport({
|
|
|
171
174
|
}, []);
|
|
172
175
|
|
|
173
176
|
const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
|
|
177
|
+
// An exiting toast stays in toastsRef until its exit transition ends, so
|
|
178
|
+
// a second dismissal inside that window (double-click, auto-timer plus
|
|
179
|
+
// manual dismiss()) would re-fire onHide. Track exiting ids in a ref —
|
|
180
|
+
// the exitingIds state dedupe below runs too late to protect onHide.
|
|
181
|
+
if (exitingIdsRef.current.has(id)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
exitingIdsRef.current.add(id);
|
|
174
185
|
const entry = toastsRef.current.find(t => t.id === id);
|
|
175
186
|
if (entry) {
|
|
176
187
|
entry.options.onHide?.(reason);
|
|
@@ -210,6 +221,7 @@ export function ToastViewport({
|
|
|
210
221
|
}, []);
|
|
211
222
|
|
|
212
223
|
const handleExited = useCallback((id: string) => {
|
|
224
|
+
exitingIdsRef.current.delete(id);
|
|
213
225
|
setExitingIds(prev => {
|
|
214
226
|
if (!prev.has(id)) {
|
|
215
227
|
return prev;
|