@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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import {render, screen, fireEvent, within} from '@testing-library/react';
|
|
5
|
+
import {useState, useCallback} from 'react';
|
|
6
|
+
import {Table} from '../../Table';
|
|
7
|
+
import type {TableColumn} from '../../types';
|
|
8
|
+
import {useTableGroupedRows} from './useTableGroupedRows';
|
|
9
|
+
|
|
10
|
+
interface Person extends Record<string, unknown> {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
team: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const people: Person[] = [
|
|
17
|
+
{id: 'a', name: 'Alice', team: 'Core'},
|
|
18
|
+
{id: 'b', name: 'Bob', team: 'Core'},
|
|
19
|
+
{id: 'c', name: 'Carol', team: 'Infra'},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const columns: TableColumn<Person>[] = [{key: 'name', header: 'Name'}];
|
|
23
|
+
|
|
24
|
+
const EMPTY = new Set<string>();
|
|
25
|
+
|
|
26
|
+
function Harness({
|
|
27
|
+
rows = people,
|
|
28
|
+
initialCollapsed = EMPTY,
|
|
29
|
+
renderGroupHeader,
|
|
30
|
+
}: {
|
|
31
|
+
rows?: Person[];
|
|
32
|
+
initialCollapsed?: Set<string>;
|
|
33
|
+
renderGroupHeader?: (
|
|
34
|
+
groupKey: string,
|
|
35
|
+
count: number,
|
|
36
|
+
collapsed: boolean,
|
|
37
|
+
) => React.ReactNode;
|
|
38
|
+
}) {
|
|
39
|
+
const [collapsedGroups, setCollapsed] = useState(initialCollapsed);
|
|
40
|
+
const onToggleGroup = useCallback((key: string) => {
|
|
41
|
+
setCollapsed(prev => {
|
|
42
|
+
const next = new Set(prev);
|
|
43
|
+
if (next.has(key)) {
|
|
44
|
+
next.delete(key);
|
|
45
|
+
} else {
|
|
46
|
+
next.add(key);
|
|
47
|
+
}
|
|
48
|
+
return next;
|
|
49
|
+
});
|
|
50
|
+
}, []);
|
|
51
|
+
const grouped = useTableGroupedRows<Person>({
|
|
52
|
+
data: rows,
|
|
53
|
+
groupBy: p => p.team,
|
|
54
|
+
collapsedGroups,
|
|
55
|
+
onToggleGroup,
|
|
56
|
+
getRowKey: p => p.id,
|
|
57
|
+
renderGroupHeader,
|
|
58
|
+
});
|
|
59
|
+
return (
|
|
60
|
+
<Table
|
|
61
|
+
data={grouped.data}
|
|
62
|
+
columns={columns}
|
|
63
|
+
idKey={grouped.idKey}
|
|
64
|
+
plugins={{grouped: grouped.plugin}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('useTableGroupedRows', () => {
|
|
70
|
+
it('renders a header row per group with label and count', () => {
|
|
71
|
+
render(<Harness />);
|
|
72
|
+
// Two groups: Core (2), Infra (1).
|
|
73
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
74
|
+
expect(screen.getByText('(2)')).toBeInTheDocument();
|
|
75
|
+
expect(screen.getByText('Infra')).toBeInTheDocument();
|
|
76
|
+
expect(screen.getByText('(1)')).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('shows group members when expanded', () => {
|
|
80
|
+
render(<Harness />);
|
|
81
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
83
|
+
expect(screen.getByText('Carol')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('hides members of a collapsed group but keeps the header', () => {
|
|
87
|
+
render(<Harness initialCollapsed={new Set(['Core'])} />);
|
|
88
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
89
|
+
expect(screen.queryByText('Alice')).not.toBeInTheDocument();
|
|
90
|
+
expect(screen.queryByText('Bob')).not.toBeInTheDocument();
|
|
91
|
+
// Other group unaffected.
|
|
92
|
+
expect(screen.getByText('Carol')).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('toggles a group via its chevron and back again', () => {
|
|
96
|
+
render(<Harness />);
|
|
97
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
98
|
+
// The standalone chevron button toggles the group; its accessible name is
|
|
99
|
+
// qualified with the group key.
|
|
100
|
+
fireEvent.click(screen.getByRole('button', {name: 'Collapse group Core'}));
|
|
101
|
+
expect(screen.queryByText('Alice')).not.toBeInTheDocument();
|
|
102
|
+
expect(screen.queryByText('Bob')).not.toBeInTheDocument();
|
|
103
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
104
|
+
expect(screen.getByText('Carol')).toBeInTheDocument();
|
|
105
|
+
// Toggle back: the Core chevron now says "Expand group Core".
|
|
106
|
+
fireEvent.click(screen.getByRole('button', {name: 'Expand group Core'}));
|
|
107
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
108
|
+
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('exposes each group toggle as a named, keyboard-operable button', () => {
|
|
112
|
+
render(<Harness />);
|
|
113
|
+
// Native <button>: focusable and operable without a custom key handler.
|
|
114
|
+
const coreToggle = screen.getByRole('button', {
|
|
115
|
+
name: 'Collapse group Core',
|
|
116
|
+
});
|
|
117
|
+
expect(coreToggle.tagName).toBe('BUTTON');
|
|
118
|
+
expect(coreToggle).toHaveAttribute('aria-expanded', 'true');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('sets aria-expanded on the header row reflecting collapse state', () => {
|
|
122
|
+
render(<Harness initialCollapsed={new Set(['Core'])} />);
|
|
123
|
+
const rows = screen.getAllByRole('row');
|
|
124
|
+
// rows[1] = Core header (collapsed), rows[2] = Infra header (expanded).
|
|
125
|
+
expect(rows[1]).toHaveAttribute('aria-expanded', 'false');
|
|
126
|
+
expect(rows[2]).toHaveAttribute('aria-expanded', 'true');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('respects groupOrder in the flattened data', () => {
|
|
130
|
+
function OrderedHarness() {
|
|
131
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
132
|
+
const grouped = useTableGroupedRows<Person>({
|
|
133
|
+
data: people,
|
|
134
|
+
groupBy: p => p.team,
|
|
135
|
+
collapsedGroups: collapsed,
|
|
136
|
+
onToggleGroup: k => setCollapsed(new Set([k])),
|
|
137
|
+
getRowKey: p => p.id,
|
|
138
|
+
groupOrder: ['Infra', 'Core'],
|
|
139
|
+
});
|
|
140
|
+
expect(grouped.data.length).toBeGreaterThan(0);
|
|
141
|
+
return (
|
|
142
|
+
<Table
|
|
143
|
+
data={grouped.data}
|
|
144
|
+
columns={columns}
|
|
145
|
+
idKey={grouped.idKey}
|
|
146
|
+
plugins={{grouped: grouped.plugin}}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
render(<OrderedHarness />);
|
|
151
|
+
const rows = screen.getAllByRole('row');
|
|
152
|
+
// rows[0] = column header; rows[1] = first group header (Infra).
|
|
153
|
+
expect(within(rows[1]).getByText('Infra')).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('renders custom group header content via renderGroupHeader', () => {
|
|
157
|
+
render(
|
|
158
|
+
<Harness
|
|
159
|
+
renderGroupHeader={(key, count, collapsed) => (
|
|
160
|
+
<span>{`${key}::${count}::${collapsed ? 'closed' : 'open'}`}</span>
|
|
161
|
+
)}
|
|
162
|
+
/>,
|
|
163
|
+
);
|
|
164
|
+
expect(screen.getByText('Core::2::open')).toBeInTheDocument();
|
|
165
|
+
expect(screen.getByText('Infra::1::open')).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('renders nothing (no group headers) for empty data', () => {
|
|
169
|
+
render(<Harness rows={[]} />);
|
|
170
|
+
// Column header row still renders; no group header rows.
|
|
171
|
+
expect(screen.queryByText('Core')).not.toBeInTheDocument();
|
|
172
|
+
expect(screen.queryByText('Infra')).not.toBeInTheDocument();
|
|
173
|
+
expect(
|
|
174
|
+
screen.queryByRole('button', {name: /group/i}),
|
|
175
|
+
).not.toBeInTheDocument();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('keeps a group collapsed across a data change (state keyed by group)', () => {
|
|
179
|
+
function ChangingHarness() {
|
|
180
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(
|
|
181
|
+
new Set(['Core']),
|
|
182
|
+
);
|
|
183
|
+
const [rows, setRows] = useState(people);
|
|
184
|
+
const onToggleGroup = useCallback((key: string) => {
|
|
185
|
+
setCollapsed(prev => {
|
|
186
|
+
const next = new Set(prev);
|
|
187
|
+
if (next.has(key)) {
|
|
188
|
+
next.delete(key);
|
|
189
|
+
} else {
|
|
190
|
+
next.add(key);
|
|
191
|
+
}
|
|
192
|
+
return next;
|
|
193
|
+
});
|
|
194
|
+
}, []);
|
|
195
|
+
const grouped = useTableGroupedRows<Person>({
|
|
196
|
+
data: rows,
|
|
197
|
+
groupBy: p => p.team,
|
|
198
|
+
collapsedGroups: collapsed,
|
|
199
|
+
onToggleGroup,
|
|
200
|
+
getRowKey: p => p.id,
|
|
201
|
+
});
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
onClick={() =>
|
|
207
|
+
setRows([...people, {id: 'd', name: 'Dave', team: 'Core'}])
|
|
208
|
+
}>
|
|
209
|
+
add
|
|
210
|
+
</button>
|
|
211
|
+
<Table
|
|
212
|
+
data={grouped.data}
|
|
213
|
+
columns={columns}
|
|
214
|
+
idKey={grouped.idKey}
|
|
215
|
+
plugins={{grouped: grouped.plugin}}
|
|
216
|
+
/>
|
|
217
|
+
</>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
render(<ChangingHarness />);
|
|
221
|
+
// Core collapsed initially: Alice hidden.
|
|
222
|
+
expect(screen.queryByText('Alice')).not.toBeInTheDocument();
|
|
223
|
+
// Add a new Core member; Core must stay collapsed (keyed by group value).
|
|
224
|
+
fireEvent.click(screen.getByText('add'));
|
|
225
|
+
expect(screen.queryByText('Alice')).not.toBeInTheDocument();
|
|
226
|
+
expect(screen.queryByText('Dave')).not.toBeInTheDocument();
|
|
227
|
+
// Count reflects the new member (3), header still present.
|
|
228
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
229
|
+
expect(screen.getByText('(3)')).toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTableGroupedRows.tsx
|
|
7
|
+
* @input React, StyleX, Icon, Table types + the flat data array
|
|
8
|
+
* @output Exports useTableGroupedRows hook + config/result types
|
|
9
|
+
* @position Grouped-rows plugin; consumed by Table via plugins prop
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/index.ts (exports)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {useCallback, useMemo, type ReactNode} from 'react';
|
|
16
|
+
import * as stylex from '@stylexjs/stylex';
|
|
17
|
+
import {
|
|
18
|
+
spacingVars,
|
|
19
|
+
colorVars,
|
|
20
|
+
fontWeightVars,
|
|
21
|
+
} from '../../../theme/tokens.stylex';
|
|
22
|
+
import {Icon} from '../../../Icon';
|
|
23
|
+
import type {TablePlugin} from '../../types';
|
|
24
|
+
|
|
25
|
+
// A synthetic group-header row injected into the flattened data. Real rows
|
|
26
|
+
// never carry this marker.
|
|
27
|
+
const GROUP_HEADER = Symbol('tableGroupHeader');
|
|
28
|
+
|
|
29
|
+
interface GroupHeader {
|
|
30
|
+
[GROUP_HEADER]: true;
|
|
31
|
+
groupKey: string;
|
|
32
|
+
count: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isGroupHeader(item: unknown): item is GroupHeader {
|
|
36
|
+
return (
|
|
37
|
+
typeof item === 'object' &&
|
|
38
|
+
item !== null &&
|
|
39
|
+
(item as Record<symbol, unknown>)[GROUP_HEADER] === true
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Proxy handler: any field access beyond the marker fields resolves to `''`
|
|
44
|
+
// so user cell renderers (`item.name.toUpperCase()`) never throw on a header.
|
|
45
|
+
const HEADER_PROXY_HANDLER: ProxyHandler<Record<string | symbol, unknown>> = {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async -- Proxy get trap, not a promise-returning fn
|
|
47
|
+
get(t: Record<string | symbol, unknown>, prop: string | symbol): unknown {
|
|
48
|
+
if (prop === GROUP_HEADER || prop === 'groupKey' || prop === 'count') {
|
|
49
|
+
return t[prop];
|
|
50
|
+
}
|
|
51
|
+
return prop in t ? t[prop] : '';
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a synthetic header row wrapped in a Proxy so arbitrary field access
|
|
57
|
+
* from user cell renderers (e.g. `item.name.toUpperCase()`) resolves to `''`
|
|
58
|
+
* instead of throwing — BaseTable evaluates `col.renderCell(item)` on every
|
|
59
|
+
* row (including synthetic headers) before `transformBodyRow` can replace the
|
|
60
|
+
* row's cells. `transformBodyRow` then discards those cells and renders a
|
|
61
|
+
* single full-width header cell.
|
|
62
|
+
*/
|
|
63
|
+
function makeHeader<T extends Record<string, unknown>>(
|
|
64
|
+
groupKey: string,
|
|
65
|
+
count: number,
|
|
66
|
+
): T {
|
|
67
|
+
const target: Record<string | symbol, unknown> = {
|
|
68
|
+
[GROUP_HEADER]: true,
|
|
69
|
+
groupKey,
|
|
70
|
+
count,
|
|
71
|
+
};
|
|
72
|
+
return new Proxy(target, HEADER_PROXY_HANDLER) as unknown as T;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Configuration for {@link useTableGroupedRows}. */
|
|
76
|
+
export interface UseTableGroupedRowsConfig<T extends Record<string, unknown>> {
|
|
77
|
+
/** The flat data to group. */
|
|
78
|
+
data: T[];
|
|
79
|
+
/** Derive the group key for a row. Rows with the same key share a section. */
|
|
80
|
+
groupBy: (item: T) => string;
|
|
81
|
+
/** Set of currently-collapsed group keys. */
|
|
82
|
+
collapsedGroups: Set<string>;
|
|
83
|
+
/** Called with a group key when its header is toggled. */
|
|
84
|
+
onToggleGroup: (groupKey: string) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Custom renderer for a group header's content (right of the chevron).
|
|
87
|
+
* Defaults to `<groupKey> (<count>)`.
|
|
88
|
+
*/
|
|
89
|
+
renderGroupHeader?: (
|
|
90
|
+
groupKey: string,
|
|
91
|
+
count: number,
|
|
92
|
+
collapsed: boolean,
|
|
93
|
+
) => ReactNode;
|
|
94
|
+
/** Stable key for a real row. Falls back to a positional key when omitted. */
|
|
95
|
+
getRowKey?: (item: T) => string;
|
|
96
|
+
/** Explicit group ordering; groups not listed keep first-seen order after these. */
|
|
97
|
+
groupOrder?: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface UseTableGroupedRowsResult<T extends Record<string, unknown>> {
|
|
101
|
+
/** Ready-to-use plugin for `<Table plugins>`. */
|
|
102
|
+
plugin: TablePlugin<T>;
|
|
103
|
+
/** Flattened rows: `[header, ...visibleRows, header, ...visibleRows]`. */
|
|
104
|
+
data: T[];
|
|
105
|
+
/**
|
|
106
|
+
* Row-key resolver (also keys synthetic headers as `__group_<key>`). Pass to
|
|
107
|
+
* `<Table idKey>` — named for parallelism with the Table prop:
|
|
108
|
+
* `<Table idKey={grouped.idKey} />`.
|
|
109
|
+
*/
|
|
110
|
+
idKey: (item: T) => string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const styles = stylex.create({
|
|
114
|
+
headerRow: {
|
|
115
|
+
cursor: 'pointer',
|
|
116
|
+
userSelect: 'none',
|
|
117
|
+
backgroundColor: colorVars['--color-background-muted'],
|
|
118
|
+
// Divider beneath each group header row (Ernest review #2).
|
|
119
|
+
borderBottomWidth: '1px',
|
|
120
|
+
borderBottomStyle: 'solid',
|
|
121
|
+
borderBottomColor: colorVars['--color-border'],
|
|
122
|
+
},
|
|
123
|
+
headerCell: {
|
|
124
|
+
paddingBlock: spacingVars['--spacing-2'],
|
|
125
|
+
// No inline start padding so the chevron aligns with the table's leading
|
|
126
|
+
// edge (Ernest review #1).
|
|
127
|
+
paddingInlineStart: spacingVars['--spacing-1'],
|
|
128
|
+
paddingInlineEnd: spacingVars['--spacing-3'],
|
|
129
|
+
},
|
|
130
|
+
headerInner: {
|
|
131
|
+
display: 'flex',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
gap: spacingVars['--spacing-1'],
|
|
134
|
+
},
|
|
135
|
+
// Standalone chevron button with no heavy chrome (transparent, borderless,
|
|
136
|
+
// zero padding) so the icon sits flush with the start of the table
|
|
137
|
+
// (Ernest review #1) while staying keyboard-operable.
|
|
138
|
+
chevron: {
|
|
139
|
+
display: 'inline-flex',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
justifyContent: 'center',
|
|
142
|
+
flexShrink: '0',
|
|
143
|
+
padding: 0,
|
|
144
|
+
margin: 0,
|
|
145
|
+
background: 'transparent',
|
|
146
|
+
border: 'none',
|
|
147
|
+
cursor: 'pointer',
|
|
148
|
+
color: {
|
|
149
|
+
default: colorVars['--color-icon-secondary'],
|
|
150
|
+
':hover': colorVars['--color-icon-primary'],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
chevronIcon: {
|
|
154
|
+
display: 'inline-flex',
|
|
155
|
+
transitionProperty: 'transform',
|
|
156
|
+
transitionDuration: '150ms',
|
|
157
|
+
},
|
|
158
|
+
chevronExpanded: {
|
|
159
|
+
transform: 'rotate(90deg)',
|
|
160
|
+
},
|
|
161
|
+
// Emphasized body text — same size as body, heavier weight (Ernest #3).
|
|
162
|
+
label: {
|
|
163
|
+
fontWeight: fontWeightVars['--font-weight-semibold'],
|
|
164
|
+
color: colorVars['--color-text-primary'],
|
|
165
|
+
},
|
|
166
|
+
count: {
|
|
167
|
+
fontWeight: fontWeightVars['--font-weight-normal'],
|
|
168
|
+
color: colorVars['--color-text-secondary'],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Groups a flat data array into collapsible section rows. Each distinct
|
|
174
|
+
* `groupBy` value becomes a full-width section-header row with a chevron
|
|
175
|
+
* toggle, the group label, and a member count; collapsing hides that group's
|
|
176
|
+
* data rows while keeping the header visible.
|
|
177
|
+
*
|
|
178
|
+
* Mirrors {@link useTableRowExpansionState}: the consumer owns the
|
|
179
|
+
* `collapsedGroups` set and this hook returns `{data, plugin, idKey}` —
|
|
180
|
+
* pass all three to `<Table>`.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```tsx
|
|
184
|
+
* const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
185
|
+
* const grouped = useTableGroupedRows({
|
|
186
|
+
* data: rows,
|
|
187
|
+
* groupBy: r => r.team,
|
|
188
|
+
* collapsedGroups: collapsed,
|
|
189
|
+
* onToggleGroup: key =>
|
|
190
|
+
* setCollapsed(prev => {
|
|
191
|
+
* const next = new Set(prev);
|
|
192
|
+
* next.has(key) ? next.delete(key) : next.add(key);
|
|
193
|
+
* return next;
|
|
194
|
+
* }),
|
|
195
|
+
* getRowKey: r => r.id,
|
|
196
|
+
* });
|
|
197
|
+
* <Table
|
|
198
|
+
* data={grouped.data}
|
|
199
|
+
* columns={columns}
|
|
200
|
+
* idKey={grouped.idKey}
|
|
201
|
+
* plugins={{grouped: grouped.plugin}}
|
|
202
|
+
* />;
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function useTableGroupedRows<T extends Record<string, unknown>>(
|
|
206
|
+
config: UseTableGroupedRowsConfig<T>,
|
|
207
|
+
): UseTableGroupedRowsResult<T> {
|
|
208
|
+
const {
|
|
209
|
+
data,
|
|
210
|
+
groupBy,
|
|
211
|
+
collapsedGroups,
|
|
212
|
+
onToggleGroup,
|
|
213
|
+
renderGroupHeader,
|
|
214
|
+
getRowKey: getRowKeyProp,
|
|
215
|
+
groupOrder,
|
|
216
|
+
} = config;
|
|
217
|
+
|
|
218
|
+
const flattened = useMemo((): T[] => {
|
|
219
|
+
if (data.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Group preserving first-seen order.
|
|
224
|
+
const groups = new Map<string, T[]>();
|
|
225
|
+
for (const item of data) {
|
|
226
|
+
const key = groupBy(item);
|
|
227
|
+
const bucket = groups.get(key);
|
|
228
|
+
if (bucket) {
|
|
229
|
+
bucket.push(item);
|
|
230
|
+
} else {
|
|
231
|
+
groups.set(key, [item]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Determine iteration order.
|
|
236
|
+
let keys = [...groups.keys()];
|
|
237
|
+
if (groupOrder && groupOrder.length > 0) {
|
|
238
|
+
const ordered = groupOrder.filter(k => groups.has(k));
|
|
239
|
+
const rest = keys.filter(k => !groupOrder.includes(k));
|
|
240
|
+
keys = [...ordered, ...rest];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const out: T[] = [];
|
|
244
|
+
for (const key of keys) {
|
|
245
|
+
const rows = groups.get(key) ?? [];
|
|
246
|
+
out.push(makeHeader<T>(key, rows.length));
|
|
247
|
+
if (!collapsedGroups.has(key)) {
|
|
248
|
+
out.push(...rows);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}, [data, groupBy, collapsedGroups, groupOrder]);
|
|
253
|
+
|
|
254
|
+
// Positional fallback index, built once per flattened array so key lookup
|
|
255
|
+
// stays O(1) instead of O(n) per row (which would make table keying O(n²)).
|
|
256
|
+
const positionByItem = useMemo(() => {
|
|
257
|
+
if (getRowKeyProp) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const map = new Map<T, number>();
|
|
261
|
+
for (let i = 0; i < flattened.length; i++) {
|
|
262
|
+
map.set(flattened[i], i);
|
|
263
|
+
}
|
|
264
|
+
return map;
|
|
265
|
+
}, [flattened, getRowKeyProp]);
|
|
266
|
+
|
|
267
|
+
const idKey = useCallback(
|
|
268
|
+
(item: T): string => {
|
|
269
|
+
if (isGroupHeader(item)) {
|
|
270
|
+
return `__group_${item.groupKey}`;
|
|
271
|
+
}
|
|
272
|
+
if (getRowKeyProp) {
|
|
273
|
+
return getRowKeyProp(item);
|
|
274
|
+
}
|
|
275
|
+
return String(positionByItem?.get(item) ?? -1);
|
|
276
|
+
},
|
|
277
|
+
[getRowKeyProp, positionByItem],
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const plugin = useMemo(
|
|
281
|
+
(): TablePlugin<T> => ({
|
|
282
|
+
// Replace a header row's pre-rendered cells with one full-width cell.
|
|
283
|
+
transformBodyRow(props, item) {
|
|
284
|
+
if (!isGroupHeader(item)) {
|
|
285
|
+
return props;
|
|
286
|
+
}
|
|
287
|
+
const header = item as unknown as GroupHeader;
|
|
288
|
+
const collapsed = collapsedGroups.has(header.groupKey);
|
|
289
|
+
const toggle = () => onToggleGroup(header.groupKey);
|
|
290
|
+
const content: ReactNode = renderGroupHeader ? (
|
|
291
|
+
renderGroupHeader(header.groupKey, header.count, collapsed)
|
|
292
|
+
) : (
|
|
293
|
+
<span {...stylex.props(styles.label)}>
|
|
294
|
+
{header.groupKey}{' '}
|
|
295
|
+
<span {...stylex.props(styles.count)}>({header.count})</span>
|
|
296
|
+
</span>
|
|
297
|
+
);
|
|
298
|
+
return {
|
|
299
|
+
...props,
|
|
300
|
+
htmlProps: {
|
|
301
|
+
...props.htmlProps,
|
|
302
|
+
// Convenience: clicking anywhere on the row toggles it. The chevron
|
|
303
|
+
// button below is the accessible, keyboard-operable control, so the
|
|
304
|
+
// row keeps its implicit `row` role (no role override here).
|
|
305
|
+
onClick: toggle,
|
|
306
|
+
'aria-expanded': !collapsed,
|
|
307
|
+
},
|
|
308
|
+
styles: [...props.styles, styles.headerRow],
|
|
309
|
+
children: (
|
|
310
|
+
// colSpan larger than the column count is clamped by the browser
|
|
311
|
+
// to the actual number of columns, so the header always spans the
|
|
312
|
+
// full width without the plugin knowing the column count.
|
|
313
|
+
<td colSpan={999} {...stylex.props(styles.headerCell)}>
|
|
314
|
+
<span {...stylex.props(styles.headerInner)}>
|
|
315
|
+
{/* Standalone chevron button, flush with the table's start
|
|
316
|
+
edge (no heavy button chrome) — the keyboard control. */}
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
{...stylex.props(styles.chevron)}
|
|
320
|
+
onClick={e => {
|
|
321
|
+
e.stopPropagation();
|
|
322
|
+
toggle();
|
|
323
|
+
}}
|
|
324
|
+
aria-label={
|
|
325
|
+
collapsed
|
|
326
|
+
? `Expand group ${header.groupKey}`
|
|
327
|
+
: `Collapse group ${header.groupKey}`
|
|
328
|
+
}
|
|
329
|
+
aria-expanded={!collapsed}>
|
|
330
|
+
<span
|
|
331
|
+
{...stylex.props(
|
|
332
|
+
styles.chevronIcon,
|
|
333
|
+
!collapsed && styles.chevronExpanded,
|
|
334
|
+
)}>
|
|
335
|
+
<Icon icon="chevronRight" size="xsm" />
|
|
336
|
+
</span>
|
|
337
|
+
</button>
|
|
338
|
+
{content}
|
|
339
|
+
</span>
|
|
340
|
+
</td>
|
|
341
|
+
),
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
345
|
+
[collapsedGroups, onToggleGroup, renderGroupHeader],
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
return {plugin, data: flattened, idKey};
|
|
349
|
+
}
|
|
@@ -38,6 +38,10 @@ export function paginateData<T>(
|
|
|
38
38
|
const size = Number.isFinite(pageSize)
|
|
39
39
|
? Math.max(1, Math.floor(pageSize))
|
|
40
40
|
: 10;
|
|
41
|
-
|
|
41
|
+
// page needs the same coercion: a negative start index makes Array.slice
|
|
42
|
+
// count from the END of the data (page -1 returns the tail dressed up as a
|
|
43
|
+
// page), and a fractional page returns a slice straddling two pages.
|
|
44
|
+
const p = Number.isFinite(page) ? Math.max(1, Math.floor(page)) : 1;
|
|
45
|
+
const start = (p - 1) * size;
|
|
42
46
|
return data.slice(start, start + size);
|
|
43
47
|
}
|
|
@@ -132,6 +132,19 @@ describe('paginateData', () => {
|
|
|
132
132
|
expect(paginateData([], 1, 10)).toEqual([]);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
it('clamps invalid page numbers to the first page (#3593)', () => {
|
|
136
|
+
const data = generateItems(30);
|
|
137
|
+
// Negative pages previously fed a negative index to Array.slice, which
|
|
138
|
+
// counts from the END of the data — the tail dressed up as a page.
|
|
139
|
+
expect(paginateData(data, -1, 10)[0].id).toBe('1');
|
|
140
|
+
expect(paginateData(data, 0, 10)[0].id).toBe('1');
|
|
141
|
+
expect(paginateData(data, NaN, 10)[0].id).toBe('1');
|
|
142
|
+
// Fractional pages floor to the containing page instead of straddling two.
|
|
143
|
+
expect(paginateData(data, 1.5, 10).map(i => i.id)).toEqual(
|
|
144
|
+
paginateData(data, 1, 10).map(i => i.id),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
135
148
|
it('returns empty array when page exceeds data', () => {
|
|
136
149
|
const data = generateItems(10);
|
|
137
150
|
expect(paginateData(data, 5, 10)).toEqual([]);
|
|
@@ -109,7 +109,7 @@ export interface UseTableRowExpansionStateResult<
|
|
|
109
109
|
* computing the expand-all state.
|
|
110
110
|
*
|
|
111
111
|
* @example
|
|
112
|
-
* ```
|
|
112
|
+
* ```
|
|
113
113
|
* const [expandedKeys, setExpandedKeys] = useState<Set<string>>(new Set());
|
|
114
114
|
* const {data, expansionConfig} = useTableRowExpansionState({
|
|
115
115
|
* baseData: tree,
|
|
@@ -302,6 +302,29 @@ describe('useTableSelectionState with filtered data', () => {
|
|
|
302
302
|
expect(screen.getByTestId('selected-keys')).toHaveTextContent('1,3');
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
+
it('select-all is unchecked when the filter matches nothing (#3591)', async () => {
|
|
306
|
+
const user = userEvent.setup();
|
|
307
|
+
render(<FilteredSelectionTable />);
|
|
308
|
+
|
|
309
|
+
// Select Bob, then filter to an empty result set.
|
|
310
|
+
const checkboxes = screen.getAllByLabelText('Select row');
|
|
311
|
+
await user.click(checkboxes[1]);
|
|
312
|
+
expect(screen.getByTestId('selected-keys')).toHaveTextContent('2');
|
|
313
|
+
|
|
314
|
+
const input = screen.getByTestId('filter-input');
|
|
315
|
+
await user.clear(input);
|
|
316
|
+
await user.type(input, 'zzz');
|
|
317
|
+
|
|
318
|
+
// Zero visible rows must not read as "all selected" — previously the
|
|
319
|
+
// header checkbox rendered checked here and deselect-all was a no-op.
|
|
320
|
+
const selectAll = screen.getByLabelText('Select all rows');
|
|
321
|
+
expect(selectAll).not.toBeChecked();
|
|
322
|
+
|
|
323
|
+
// The hidden selection itself is preserved (frozen), as designed.
|
|
324
|
+
await user.clear(input);
|
|
325
|
+
expect(screen.getByTestId('selected-keys')).toHaveTextContent('2');
|
|
326
|
+
});
|
|
327
|
+
|
|
305
328
|
it('selections persist when filter changes', async () => {
|
|
306
329
|
const user = userEvent.setup();
|
|
307
330
|
render(<FilteredSelectionTable />);
|
|
@@ -125,12 +125,17 @@ export function useTableSelectionState<T extends Record<string, unknown>>(
|
|
|
125
125
|
[selectedKeys, selectableIDs],
|
|
126
126
|
);
|
|
127
127
|
|
|
128
|
-
const getAllSelected = useCallback(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
const getAllSelected = useCallback(() => {
|
|
129
|
+
// Require at least one actionable row: with zero visible selectable
|
|
130
|
+
// rows (e.g. a filter with no matches while a selection exists), the
|
|
131
|
+
// union-based size comparison would read any non-empty selection as
|
|
132
|
+
// "all selected" — a checked header checkbox over an empty table that
|
|
133
|
+
// deselect-all can't clear, since the invisible keys count as frozen.
|
|
134
|
+
if (selectableIDs.size === 0) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return allSelectableIDs.size === selectedKeys.size;
|
|
138
|
+
}, [allSelectableIDs, selectableIDs, selectedKeys]);
|
|
134
139
|
|
|
135
140
|
// Use refs to keep onSelectAll stable
|
|
136
141
|
const frozenSelectedIDsRef = useRef(frozenSelectedIDs);
|