@astryxdesign/core 0.4.6 → 0.4.7-canary.20bae6c
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 +36 -0
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +3 -2
- package/dist/AspectRatio/AspectRatio.d.ts +24 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +23 -4
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +3 -2
- package/dist/BottomSheet/BottomSheet.d.ts +1 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +54 -15
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +6 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.js +62 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +13 -6
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +23 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +12 -4
- package/dist/Calendar/Calendar.d.ts +3 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +13 -14
- package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
- package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
- package/dist/Calendar/getInitialFocusDate.js +62 -0
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +3 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +3 -3
- package/dist/Chat/ChatMessageList.d.ts +22 -3
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +6 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +3 -2
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +3 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +3 -2
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +34 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +3 -2
- package/dist/Dialog/Dialog.d.ts +7 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +89 -58
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +3 -2
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +6 -2
- package/dist/HoverCard/HoverCard.d.ts +6 -0
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.d.ts +5 -0
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -25
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -2
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +10 -3
- package/dist/Layer/LayerDepthContext.d.ts +52 -0
- package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
- package/dist/Layer/LayerDepthContext.js +66 -0
- package/dist/Layer/index.d.ts +3 -0
- package/dist/Layer/index.d.ts.map +1 -1
- package/dist/Layer/index.js +4 -0
- package/dist/Layer/layerStack.d.ts +98 -0
- package/dist/Layer/layerStack.d.ts.map +1 -0
- package/dist/Layer/layerStack.js +306 -0
- package/dist/Layer/useLayerDismissal.d.ts +93 -0
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
- package/dist/Layer/useLayerDismissal.js +91 -0
- package/dist/Lightbox/Lightbox.d.ts +1 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +103 -81
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +38 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +149 -23
- package/dist/MobileNav/MobileNav.d.ts +6 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +61 -36
- package/dist/MultiSelector/MultiSelector.d.ts +15 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +17 -6
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +3 -2
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +3 -2
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +3 -2
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +20 -17
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +3 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +3 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +5 -0
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +4 -3
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +4 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +3 -3
- package/dist/Stepper/Step.d.ts +140 -0
- package/dist/Stepper/Step.d.ts.map +1 -0
- package/dist/Stepper/Step.js +1045 -0
- package/dist/Stepper/StepStatus.d.ts +24 -0
- package/dist/Stepper/StepStatus.d.ts.map +1 -0
- package/dist/Stepper/StepStatus.js +1 -0
- package/dist/Stepper/Stepper.d.ts +93 -0
- package/dist/Stepper/Stepper.d.ts.map +1 -0
- package/dist/Stepper/Stepper.js +184 -0
- package/dist/Stepper/StepperContext.d.ts +41 -0
- package/dist/Stepper/StepperContext.d.ts.map +1 -0
- package/dist/Stepper/StepperContext.js +34 -0
- package/dist/Stepper/index.d.ts +8 -0
- package/dist/Stepper/index.d.ts.map +1 -0
- package/dist/Stepper/index.js +7 -0
- package/dist/Stepper/stepper.stylex.d.ts +15 -0
- package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
- package/dist/Stepper/stepper.stylex.js +20 -0
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +3 -2
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +5 -1
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +3 -3
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +4 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
- package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
- package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +19 -5
- package/dist/Table/types.d.ts +22 -4
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -0
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +6 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +3 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +3 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +5 -3
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +2 -2
- package/dist/Tooltip/Tooltip.d.ts +5 -0
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +5 -0
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +39 -24
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +3 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +3 -2
- package/dist/astryx.css +43 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFocusTrap.d.ts +7 -4
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -91
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +10 -3
- package/dist/hooks/useListFocus.d.ts +5 -2
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +12 -6
- package/dist/hooks/useMergedRefs.d.ts +18 -0
- package/dist/hooks/useMergedRefs.d.ts.map +1 -0
- package/dist/hooks/useMergedRefs.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/timeParser.d.ts +1 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +26 -12
- package/locales/en.json +27 -7
- package/locales/pseudo.json +15 -0
- package/package.json +8 -3
- package/src/AppShell/AppShell.tsx +3 -2
- package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
- package/src/AspectRatio/AspectRatio.test.tsx +57 -10
- package/src/AspectRatio/AspectRatio.tsx +40 -1
- package/src/Avatar/Avatar.doc.mjs +2 -1
- package/src/AvatarGroup/AvatarGroup.tsx +3 -2
- package/src/BottomSheet/BottomSheet.test.tsx +123 -0
- package/src/BottomSheet/BottomSheet.tsx +36 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +225 -0
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +82 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +66 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +19 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +16 -2
- package/src/BottomSheet/useSheetGestures.test.ts +27 -0
- package/src/BottomSheet/useSheetGestures.ts +25 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
- package/src/Button/Button.doc.mjs +22 -0
- package/src/ButtonGroup/ButtonGroup.doc.mjs +7 -1
- package/src/ButtonGroup/ButtonGroup.test.tsx +185 -1
- package/src/ButtonGroup/ButtonGroup.tsx +11 -3
- package/src/Calendar/Calendar.doc.mjs +4 -3
- package/src/Calendar/Calendar.test.tsx +52 -0
- package/src/Calendar/Calendar.tsx +18 -15
- package/src/Calendar/getInitialFocusDate.test.ts +94 -0
- package/src/Calendar/getInitialFocusDate.ts +93 -0
- package/src/Carousel/Carousel.tsx +3 -2
- package/src/Chat/ChatLayout.tsx +3 -2
- package/src/Chat/ChatMessageList.doc.mjs +9 -1
- package/src/Chat/ChatMessageList.test.tsx +46 -0
- package/src/Chat/ChatMessageList.tsx +28 -4
- package/src/CheckboxInput/CheckboxInput.tsx +3 -2
- package/src/ClickableCard/ClickableCard.tsx +3 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
- package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
- package/src/ContextMenu/ContextMenu.tsx +4 -3
- package/src/DateInput/DateInput.tsx +3 -2
- package/src/DateInput/DateInputTouch.test.tsx +36 -0
- package/src/DateInput/TouchDateField.tsx +35 -1
- package/src/DateTimeInput/DateTimeInput.tsx +2 -2
- package/src/Dialog/Dialog.doc.mjs +8 -3
- package/src/Dialog/Dialog.test.tsx +261 -1
- package/src/Dialog/Dialog.tsx +109 -58
- package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
- package/src/Field/Field.doc.mjs +13 -2
- package/src/FileInput/FileInput.tsx +3 -2
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +5 -0
- package/src/HoverCard/HoverCard.tsx +6 -0
- package/src/HoverCard/useHoverCard.tsx +81 -30
- package/src/Item/Item.tsx +5 -4
- package/src/Kbd/Kbd.test.tsx +17 -0
- package/src/Kbd/Kbd.tsx +10 -3
- package/src/Layer/LayerDepthContext.tsx +62 -0
- package/src/Layer/index.ts +9 -0
- package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
- package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
- package/src/Layer/layerStack.ts +362 -0
- package/src/Layer/useLayerDismissal.test.tsx +469 -0
- package/src/Layer/useLayerDismissal.ts +173 -0
- package/src/Layout/Layout.doc.mjs +2 -2
- package/src/Lightbox/Lightbox.doc.mjs +12 -0
- package/src/Lightbox/Lightbox.tsx +124 -102
- package/src/Link/Link.doc.mjs +39 -0
- package/src/Markdown/index.ts +1 -0
- package/src/Markdown/parser.perf.test.ts +71 -1
- package/src/Markdown/parser.test.ts +145 -2
- package/src/Markdown/parser.ts +208 -22
- package/src/MobileNav/MobileNav.tsx +62 -36
- package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
- package/src/MultiSelector/MultiSelector.test.tsx +105 -0
- package/src/MultiSelector/MultiSelector.tsx +50 -6
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavMenu/NavHeadingMenu.tsx +3 -2
- package/src/NumberInput/NumberInput.doc.mjs +5 -0
- package/src/Outline/Outline.tsx +3 -2
- package/src/OverflowList/OverflowList.tsx +3 -2
- package/src/Overlay/Overlay.tsx +3 -2
- package/src/Popover/Popover.test.tsx +27 -1
- package/src/Popover/usePopover.tsx +31 -28
- package/src/PowerSearch/PowerSearch.doc.mjs +26 -0
- package/src/PowerSearch/PowerSearch.tsx +5 -2
- package/src/Resizable/ResizeHandle.tsx +3 -2
- package/src/SegmentedControl/SegmentedControl.tsx +3 -2
- package/src/SelectableCard/SelectableCard.tsx +3 -2
- package/src/Selector/Selector.doc.mjs +12 -1
- package/src/Selector/Selector.test.tsx +21 -0
- package/src/Selector/Selector.tsx +11 -1
- package/src/SideNav/SideNav.doc.mjs +6 -6
- package/src/SideNav/SideNav.tsx +4 -2
- package/src/SideNav/SideNavItem.tsx +4 -2
- package/src/Slider/Slider.tsx +3 -2
- package/src/Stepper/Step.doc.mjs +98 -0
- package/src/Stepper/Step.tsx +1638 -0
- package/src/Stepper/StepStatus.ts +26 -0
- package/src/Stepper/Stepper.doc.mjs +370 -0
- package/src/Stepper/Stepper.test.tsx +1024 -0
- package/src/Stepper/Stepper.tsx +258 -0
- package/src/Stepper/StepperContext.ts +71 -0
- package/src/Stepper/index.ts +18 -0
- package/src/Stepper/stepper.stylex.ts +19 -0
- package/src/Switch/Switch.tsx +3 -2
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.test.tsx +21 -4
- package/src/TabList/TabList.tsx +3 -2
- package/src/Table/BaseTable.tsx +6 -3
- package/src/Table/Table.test.tsx +35 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
- package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
- package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
- package/src/Table/types.ts +22 -4
- package/src/Table/useBaseTablePlugins.ts +5 -0
- package/src/Table/useTableGroupedRows.doc.mjs +5 -4
- package/src/Table/useTablePagination.doc.mjs +7 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableSelection.doc.mjs +31 -0
- package/src/Text/Text.test.tsx +23 -1
- package/src/Text/Text.tsx +6 -2
- package/src/TextArea/TextArea.tsx +3 -2
- package/src/TextInput/TextInput.doc.mjs +10 -0
- package/src/TextInput/TextInput.tsx +3 -2
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.tsx +2 -2
- package/src/Timestamp/Timestamp.tsx +4 -2
- package/src/Toast/Toast.doc.mjs +6 -0
- package/src/Token/TokenLink.tsx +2 -2
- package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
- package/src/Toolbar/Toolbar.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.test.tsx +32 -0
- package/src/Tooltip/Tooltip.tsx +5 -0
- package/src/Tooltip/useTooltip.tsx +47 -24
- package/src/TopNav/TopNavMegaMenu.tsx +3 -2
- package/src/Typeahead/BaseTypeahead.tsx +3 -2
- package/src/Typeahead/Typeahead.doc.mjs +5 -0
- package/src/Typeahead/Typeahead.tsx +3 -2
- package/src/__tests__/complexComponentContract.test.tsx +81 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useContainerReveal.doc.mjs +2 -2
- package/src/hooks/useFocusTrap.ts +54 -107
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
- package/src/hooks/useHotkeys.test.ts +18 -0
- package/src/hooks/useHotkeys.ts +10 -3
- package/src/hooks/useListFocus.doc.mjs +2 -2
- package/src/hooks/useListFocus.test.tsx +65 -3
- package/src/hooks/useListFocus.ts +15 -7
- package/src/hooks/useMergedRefs.doc.mjs +79 -0
- package/src/hooks/useMergedRefs.test.tsx +62 -0
- package/src/hooks/useMergedRefs.ts +36 -0
- package/src/index.ts +1 -0
- package/src/theme/MediaTheme.doc.mjs +5 -5
- package/src/utils/timeParser.test.ts +6 -1
- package/src/utils/timeParser.ts +35 -13
- package/src/__tests__/inputWidthContract.test.tsx +0 -69
|
@@ -5,6 +5,7 @@ import {render, screen, fireEvent, within} from '@testing-library/react';
|
|
|
5
5
|
import {useState, useCallback} from 'react';
|
|
6
6
|
import {Table} from '../../Table';
|
|
7
7
|
import type {TableColumn} from '../../types';
|
|
8
|
+
import {useTableRowStatus, type TableRowStatus} from '../rowStatus';
|
|
8
9
|
import {useTableGroupedRows} from './useTableGroupedRows';
|
|
9
10
|
|
|
10
11
|
interface Person extends Record<string, unknown> {
|
|
@@ -175,6 +176,105 @@ describe('useTableGroupedRows', () => {
|
|
|
175
176
|
).not.toBeInTheDocument();
|
|
176
177
|
});
|
|
177
178
|
|
|
179
|
+
it('never runs a column renderCell against a group header row', () => {
|
|
180
|
+
// The renderer that breaks: it keys a lookup off a field rather than
|
|
181
|
+
// printing it, so the header Proxy answering `''` is no better than
|
|
182
|
+
// `undefined`.
|
|
183
|
+
const TEAM_META: Record<string, {dot: string}> = {
|
|
184
|
+
Core: {dot: 'green'},
|
|
185
|
+
Infra: {dot: 'blue'},
|
|
186
|
+
};
|
|
187
|
+
const seen: Person[] = [];
|
|
188
|
+
const lookupColumns: TableColumn<Person>[] = [
|
|
189
|
+
{
|
|
190
|
+
key: 'team',
|
|
191
|
+
header: 'Team',
|
|
192
|
+
renderCell: item => {
|
|
193
|
+
seen.push(item);
|
|
194
|
+
return <span>{TEAM_META[item.team].dot}</span>;
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
function LookupHarness() {
|
|
200
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(EMPTY);
|
|
201
|
+
const grouped = useTableGroupedRows<Person>({
|
|
202
|
+
data: people,
|
|
203
|
+
groupBy: p => p.team,
|
|
204
|
+
collapsedGroups: collapsed,
|
|
205
|
+
onToggleGroup: k => setCollapsed(new Set([k])),
|
|
206
|
+
getRowKey: p => p.id,
|
|
207
|
+
});
|
|
208
|
+
return (
|
|
209
|
+
<Table
|
|
210
|
+
data={grouped.data}
|
|
211
|
+
columns={lookupColumns}
|
|
212
|
+
idKey={grouped.idKey}
|
|
213
|
+
plugins={{grouped: grouped.plugin}}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
expect(() => render(<LookupHarness />)).not.toThrow();
|
|
219
|
+
expect(seen.map(p => p.name)).toEqual(['Alice', 'Bob', 'Carol']);
|
|
220
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
221
|
+
expect(screen.getByText('Infra')).toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe.each([
|
|
225
|
+
['grouped first', true],
|
|
226
|
+
['rowStatus first', false],
|
|
227
|
+
])(
|
|
228
|
+
'with a column contributed by another plugin (%s)',
|
|
229
|
+
(_label, groupedFirst) => {
|
|
230
|
+
// `grouped` and `rowStatus` are both absent from PLUGIN_ORDER, so the
|
|
231
|
+
// record's insertion order is the pipeline order. Whichever way round
|
|
232
|
+
// the consumer types it, the status column's renderCell must not see a
|
|
233
|
+
// header row.
|
|
234
|
+
const STATUS_BY_TEAM: Record<string, TableRowStatus> = {
|
|
235
|
+
Core: {color: 'success', label: 'Healthy'},
|
|
236
|
+
Infra: {color: 'error', label: 'Paging'},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function StatusHarness() {
|
|
240
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(EMPTY);
|
|
241
|
+
const grouped = useTableGroupedRows<Person>({
|
|
242
|
+
data: people,
|
|
243
|
+
groupBy: p => p.team,
|
|
244
|
+
collapsedGroups: collapsed,
|
|
245
|
+
onToggleGroup: k => setCollapsed(new Set([k])),
|
|
246
|
+
getRowKey: p => p.id,
|
|
247
|
+
});
|
|
248
|
+
const rowStatus = useTableRowStatus<Person>({
|
|
249
|
+
getStatus: useCallback((item: Person) => {
|
|
250
|
+
const {color, label} = STATUS_BY_TEAM[item.team];
|
|
251
|
+
return {color, label};
|
|
252
|
+
}, []),
|
|
253
|
+
});
|
|
254
|
+
return (
|
|
255
|
+
<Table
|
|
256
|
+
data={grouped.data}
|
|
257
|
+
columns={columns}
|
|
258
|
+
idKey={grouped.idKey}
|
|
259
|
+
plugins={
|
|
260
|
+
groupedFirst
|
|
261
|
+
? {grouped: grouped.plugin, rowStatus}
|
|
262
|
+
: {rowStatus, grouped: grouped.plugin}
|
|
263
|
+
}
|
|
264
|
+
/>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
it('renders the group headers without throwing', () => {
|
|
269
|
+
expect(() => render(<StatusHarness />)).not.toThrow();
|
|
270
|
+
expect(screen.getByText('Core')).toBeInTheDocument();
|
|
271
|
+
expect(screen.getByText('Infra')).toBeInTheDocument();
|
|
272
|
+
// One indicator per real row, none on the two headers.
|
|
273
|
+
expect(screen.getAllByRole('img')).toHaveLength(3);
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
178
278
|
it('keeps a group collapsed across a data change (state keyed by group)', () => {
|
|
179
279
|
function ChangingHarness() {
|
|
180
280
|
const [collapsed, setCollapsed] = useState<Set<string>>(
|
|
@@ -41,8 +41,11 @@ function isGroupHeader(item: unknown): item is GroupHeader {
|
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Proxy handler: any field access beyond the marker fields resolves to `''
|
|
45
|
-
//
|
|
44
|
+
// Proxy handler: any field access beyond the marker fields resolves to `''`,
|
|
45
|
+
// which keeps anything that reads a header's data — the sortable and filtering
|
|
46
|
+
// plugins, a consumer's own row handler — off `undefined`. Cell renderers are
|
|
47
|
+
// handled at render time instead (see `transformBodyCell` below); the Proxy
|
|
48
|
+
// never protected them, because `''` fails a lookup exactly as `undefined` does.
|
|
46
49
|
const HEADER_PROXY_HANDLER: ProxyHandler<Record<string | symbol, unknown>> = {
|
|
47
50
|
// eslint-disable-next-line @typescript-eslint/promise-function-async -- Proxy get trap, not a promise-returning fn
|
|
48
51
|
get(t: Record<string | symbol, unknown>, prop: string | symbol): unknown {
|
|
@@ -54,12 +57,9 @@ const HEADER_PROXY_HANDLER: ProxyHandler<Record<string | symbol, unknown>> = {
|
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
|
-
* Build a synthetic header row wrapped in a Proxy so
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* row (including synthetic headers) before `transformBodyRow` can replace the
|
|
61
|
-
* row's cells. `transformBodyRow` then discards those cells and renders a
|
|
62
|
-
* single full-width header cell.
|
|
60
|
+
* Build a synthetic header row wrapped in a Proxy so field access resolves to
|
|
61
|
+
* `''` instead of `undefined`. `transformBodyRow` then discards the row's
|
|
62
|
+
* cells and renders a single full-width header cell.
|
|
63
63
|
*/
|
|
64
64
|
function makeHeader<T extends Record<string, unknown>>(
|
|
65
65
|
groupKey: string,
|
|
@@ -301,6 +301,15 @@ export function useTableGroupedRows<T extends Record<string, unknown>>(
|
|
|
301
301
|
|
|
302
302
|
const plugin = useMemo(
|
|
303
303
|
(): TablePlugin<T> => ({
|
|
304
|
+
// A header row is not one of the caller's rows, so running a cell
|
|
305
|
+
// renderer against it can only misread it or throw — and the cells are
|
|
306
|
+
// discarded by `transformBodyRow` below regardless.
|
|
307
|
+
transformBodyCell(props, _column, item) {
|
|
308
|
+
if (!isGroupHeader(item)) {
|
|
309
|
+
return props;
|
|
310
|
+
}
|
|
311
|
+
return {...props, isContentSuppressed: true};
|
|
312
|
+
},
|
|
304
313
|
// Replace a header row's pre-rendered cells with one full-width cell.
|
|
305
314
|
transformBodyRow(props, item) {
|
|
306
315
|
if (!isGroupHeader(item)) {
|
|
@@ -229,7 +229,7 @@ describe('useTablePagination', () => {
|
|
|
229
229
|
).toBeTruthy();
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
it('transformTableContext renders Pagination above and below', () => {
|
|
232
|
+
it('transformTableContext renders distinctly named Pagination above and below', () => {
|
|
233
233
|
render(
|
|
234
234
|
<PaginatedTable
|
|
235
235
|
data={generateItems(30)}
|
|
@@ -237,10 +237,40 @@ describe('useTablePagination', () => {
|
|
|
237
237
|
position="both"
|
|
238
238
|
/>,
|
|
239
239
|
);
|
|
240
|
-
const
|
|
241
|
-
|
|
240
|
+
const table = screen.getByRole('table');
|
|
241
|
+
// Each nav landmark gets a unique accessible name (axe landmark-unique)
|
|
242
|
+
const topNav = screen.getByRole('navigation', {
|
|
243
|
+
name: 'Table pagination (top)',
|
|
244
|
+
});
|
|
245
|
+
const bottomNav = screen.getByRole('navigation', {
|
|
246
|
+
name: 'Table pagination (bottom)',
|
|
242
247
|
});
|
|
243
|
-
|
|
248
|
+
// Top nav comes before the table, bottom nav after it in DOM order
|
|
249
|
+
expect(
|
|
250
|
+
table.compareDocumentPosition(topNav) &
|
|
251
|
+
Node.DOCUMENT_POSITION_PRECEDING,
|
|
252
|
+
).toBeTruthy();
|
|
253
|
+
expect(
|
|
254
|
+
table.compareDocumentPosition(bottomNav) &
|
|
255
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
256
|
+
).toBeTruthy();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('position="both" interpolates a consumer label into distinct nav names', () => {
|
|
260
|
+
render(
|
|
261
|
+
<PaginatedTable
|
|
262
|
+
data={generateItems(30)}
|
|
263
|
+
pageSize={10}
|
|
264
|
+
position="both"
|
|
265
|
+
label="Users table"
|
|
266
|
+
/>,
|
|
267
|
+
);
|
|
268
|
+
expect(
|
|
269
|
+
screen.getByRole('navigation', {name: 'Users table (top)'}),
|
|
270
|
+
).toBeInTheDocument();
|
|
271
|
+
expect(
|
|
272
|
+
screen.getByRole('navigation', {name: 'Users table (bottom)'}),
|
|
273
|
+
).toBeInTheDocument();
|
|
244
274
|
});
|
|
245
275
|
|
|
246
276
|
it('transformTableContext does not render Pagination when position is none', () => {
|
|
@@ -164,6 +164,9 @@ export interface UseTablePaginationConfig {
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Accessible label for the pagination nav landmark.
|
|
167
|
+
* When `position` is 'both', the two navs get distinct names by suffixing
|
|
168
|
+
* this label — "{label} (top)" above and "{label} (bottom)" below — so
|
|
169
|
+
* same-type landmarks stay distinguishable to assistive technology.
|
|
167
170
|
* @default 'Table pagination'
|
|
168
171
|
*/
|
|
169
172
|
label?: string;
|
|
@@ -211,6 +214,10 @@ export function useTablePagination<T extends Record<string, unknown>>(
|
|
|
211
214
|
label: labelFromProps,
|
|
212
215
|
} = config;
|
|
213
216
|
const label = labelFromProps ?? t('@astryx.table.pagination.label');
|
|
217
|
+
// position='both' renders two nav landmarks; suffix the base label so each
|
|
218
|
+
// gets a unique accessible name (axe landmark-unique).
|
|
219
|
+
const labelAbove = t('@astryx.table.pagination.labelAbove', {label});
|
|
220
|
+
const labelBelow = t('@astryx.table.pagination.labelBelow', {label});
|
|
214
221
|
|
|
215
222
|
// Same guard as Pagination itself: 0/NaN/negative pageSize would produce an
|
|
216
223
|
// Infinity/NaN totalPages here, which bypasses Pagination's own coercion
|
|
@@ -240,8 +247,20 @@ export function useTablePagination<T extends Record<string, unknown>>(
|
|
|
240
247
|
|
|
241
248
|
// Keep current config in a ref so the plugin can read the latest values
|
|
242
249
|
// without needing to recreate the plugin object on every change.
|
|
243
|
-
const configRef = useRef({
|
|
244
|
-
|
|
250
|
+
const configRef = useRef({
|
|
251
|
+
paginationProps,
|
|
252
|
+
position,
|
|
253
|
+
align,
|
|
254
|
+
labelAbove,
|
|
255
|
+
labelBelow,
|
|
256
|
+
});
|
|
257
|
+
configRef.current = {
|
|
258
|
+
paginationProps,
|
|
259
|
+
position,
|
|
260
|
+
align,
|
|
261
|
+
labelAbove,
|
|
262
|
+
labelBelow,
|
|
263
|
+
};
|
|
245
264
|
|
|
246
265
|
// Stable plugin object \u2014 created once, reads config via ref.
|
|
247
266
|
return useMemo(
|
|
@@ -251,6 +270,8 @@ export function useTablePagination<T extends Record<string, unknown>>(
|
|
|
251
270
|
position: pos,
|
|
252
271
|
paginationProps: props,
|
|
253
272
|
align: a,
|
|
273
|
+
labelAbove: above,
|
|
274
|
+
labelBelow: below,
|
|
254
275
|
} = configRef.current;
|
|
255
276
|
if (pos === 'none') {
|
|
256
277
|
return children;
|
|
@@ -277,7 +298,18 @@ export function useTablePagination<T extends Record<string, unknown>>(
|
|
|
277
298
|
a === 'end' && styles.alignEnd,
|
|
278
299
|
a === 'start' && styles.alignStart,
|
|
279
300
|
)}>
|
|
280
|
-
<Pagination
|
|
301
|
+
<Pagination
|
|
302
|
+
{...props}
|
|
303
|
+
// Two navs with the same name are indistinguishable landmarks;
|
|
304
|
+
// give each side a unique name when both render.
|
|
305
|
+
label={
|
|
306
|
+
pos === 'both'
|
|
307
|
+
? side === 'above'
|
|
308
|
+
? above
|
|
309
|
+
: below
|
|
310
|
+
: props.label
|
|
311
|
+
}
|
|
312
|
+
/>
|
|
281
313
|
</div>
|
|
282
314
|
);
|
|
283
315
|
|
|
@@ -21,6 +21,7 @@ import {useMemo, useRef, type ReactNode} from 'react';
|
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import {spacingVars, colorVars, radiusVars} from '../../../theme/tokens.stylex';
|
|
23
23
|
import {Icon} from '../../../Icon';
|
|
24
|
+
import {VisuallyHidden} from '../../../VisuallyHidden';
|
|
24
25
|
import {resolveContextActions} from '../../tableContextMenu';
|
|
25
26
|
import {useTranslator} from '../../../i18n';
|
|
26
27
|
import {rtlStyles} from '../../../utils';
|
|
@@ -207,7 +208,13 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
|
|
|
207
208
|
const expansionColumn = useMemo(
|
|
208
209
|
(): TableColumn<T> => ({
|
|
209
210
|
key: '__expansion',
|
|
210
|
-
|
|
211
|
+
// A `<th>` with no discernible text is announced as an unlabelled
|
|
212
|
+
// column; the label is hidden because the column shows only chevrons.
|
|
213
|
+
header: (
|
|
214
|
+
<VisuallyHidden>
|
|
215
|
+
{t('@astryx.tableRowExpansion.columnHeader')}
|
|
216
|
+
</VisuallyHidden>
|
|
217
|
+
),
|
|
211
218
|
width: EXPANSION_COLUMN_WIDTH,
|
|
212
219
|
resizable: false,
|
|
213
220
|
renderCell: (item: T) => {
|
|
@@ -226,7 +233,7 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
|
|
|
226
233
|
);
|
|
227
234
|
},
|
|
228
235
|
}),
|
|
229
|
-
[expandedKeys, onToggle, getRowKey, getIsItemExpandable],
|
|
236
|
+
[expandedKeys, onToggle, getRowKey, getIsItemExpandable, t],
|
|
230
237
|
);
|
|
231
238
|
|
|
232
239
|
return useMemo(
|
|
@@ -44,12 +44,18 @@ function Harness({
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
describe('useTableRowStatus', () => {
|
|
47
|
-
it('
|
|
47
|
+
it('names the status column header for assistive technology', () => {
|
|
48
48
|
render(<Harness />);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(
|
|
52
|
-
|
|
49
|
+
// The gutter looks blank but its th carries a visually hidden name.
|
|
50
|
+
const header = screen.getByRole('columnheader', {name: 'Row status'});
|
|
51
|
+
expect(header).toHaveAttribute('data-column-key', '__rowStatus');
|
|
52
|
+
// Status column is first.
|
|
53
|
+
expect(screen.getAllByRole('columnheader')[0]).toBe(header);
|
|
54
|
+
// The name must come from the clipped VisuallyHidden span — bare th text
|
|
55
|
+
// would be a visible header on what should stay a blank gutter.
|
|
56
|
+
const hiddenText = within(header).getByText('Row status');
|
|
57
|
+
expect(hiddenText.tagName).toBe('SPAN');
|
|
58
|
+
expect(hiddenText.className).not.toBe('');
|
|
53
59
|
});
|
|
54
60
|
|
|
55
61
|
it('renders a labeled dot for rows with a status', () => {
|
|
@@ -131,8 +137,8 @@ describe('useTableRowStatus', () => {
|
|
|
131
137
|
|
|
132
138
|
it('renders the status header with empty data and no indicators', () => {
|
|
133
139
|
render(<Harness rows={[]} />);
|
|
134
|
-
const
|
|
135
|
-
expect(
|
|
140
|
+
const header = screen.getByRole('columnheader', {name: 'Row status'});
|
|
141
|
+
expect(header).toHaveAttribute('data-column-key', '__rowStatus');
|
|
136
142
|
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
|
137
143
|
});
|
|
138
144
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useTableRowStatus.tsx
|
|
7
|
-
* @input React, StyleX, Icon, Table types
|
|
7
|
+
* @input React, StyleX, Icon, i18n, VisuallyHidden, Table types
|
|
8
8
|
* @output Exports useTableRowStatus hook + config type
|
|
9
9
|
* @position Row-status plugin; consumed by Table via plugins prop
|
|
10
10
|
*
|
|
@@ -16,6 +16,8 @@ import {useMemo} from 'react';
|
|
|
16
16
|
import * as stylex from '@stylexjs/stylex';
|
|
17
17
|
import {Icon, type IconColor, type IconName} from '../../../Icon';
|
|
18
18
|
import {Tooltip} from '../../../Tooltip';
|
|
19
|
+
import {useTranslator} from '../../../i18n';
|
|
20
|
+
import {VisuallyHidden} from '../../../VisuallyHidden';
|
|
19
21
|
import type {TableColumn, TablePlugin} from '../../types';
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -142,6 +144,7 @@ const styles = stylex.create({
|
|
|
142
144
|
export function useTableRowStatus<T extends Record<string, unknown>>(
|
|
143
145
|
config: UseTableRowStatusConfig<T>,
|
|
144
146
|
): TablePlugin<T> {
|
|
147
|
+
const t = useTranslator();
|
|
145
148
|
const {getStatus} = config;
|
|
146
149
|
|
|
147
150
|
return useMemo(
|
|
@@ -149,7 +152,13 @@ export function useTableRowStatus<T extends Record<string, unknown>>(
|
|
|
149
152
|
transformColumns(columns) {
|
|
150
153
|
const statusColumn: TableColumn<T> = {
|
|
151
154
|
key: '__rowStatus',
|
|
152
|
-
|
|
155
|
+
// The gutter stays visually blank, but the th needs a discernible
|
|
156
|
+
// name for assistive technology (axe: empty-table-header).
|
|
157
|
+
header: (
|
|
158
|
+
<VisuallyHidden>
|
|
159
|
+
{t('@astryx.table.rowStatus.columnHeader')}
|
|
160
|
+
</VisuallyHidden>
|
|
161
|
+
),
|
|
153
162
|
width: STATUS_COLUMN_WIDTH,
|
|
154
163
|
resizable: false,
|
|
155
164
|
renderCell: (item: T) => {
|
|
@@ -184,6 +193,6 @@ export function useTableRowStatus<T extends Record<string, unknown>>(
|
|
|
184
193
|
return [statusColumn, ...columns];
|
|
185
194
|
},
|
|
186
195
|
}),
|
|
187
|
-
[getStatus],
|
|
196
|
+
[getStatus, t],
|
|
188
197
|
);
|
|
189
198
|
}
|
|
@@ -13,6 +13,7 @@ import {act, render, renderHook, screen, within} from '@testing-library/react';
|
|
|
13
13
|
import userEvent from '@testing-library/user-event';
|
|
14
14
|
import {Table} from '../../Table';
|
|
15
15
|
import {useTableSelection} from './useTableSelection';
|
|
16
|
+
import {colorVars} from '../../../theme/tokens.stylex';
|
|
16
17
|
import type {BodyRowRenderProps, TableColumn} from '../../types';
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
@@ -41,10 +42,12 @@ function SelectionTable({
|
|
|
41
42
|
getIsItemSelectable,
|
|
42
43
|
getIsItemEnabled,
|
|
43
44
|
getRowLabel,
|
|
45
|
+
hasRowHighlight,
|
|
44
46
|
}: {
|
|
45
47
|
getIsItemSelectable?: (item: SelectableUser) => boolean;
|
|
46
48
|
getIsItemEnabled?: (item: SelectableUser) => boolean;
|
|
47
49
|
getRowLabel?: (item: SelectableUser) => string;
|
|
50
|
+
hasRowHighlight?: boolean;
|
|
48
51
|
}) {
|
|
49
52
|
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
|
|
50
53
|
|
|
@@ -78,6 +81,7 @@ function SelectionTable({
|
|
|
78
81
|
getIsItemSelectable,
|
|
79
82
|
getIsItemEnabled,
|
|
80
83
|
getRowLabel,
|
|
84
|
+
hasRowHighlight,
|
|
81
85
|
});
|
|
82
86
|
|
|
83
87
|
return (
|
|
@@ -211,6 +215,78 @@ describe('useTableSelection', () => {
|
|
|
211
215
|
expect(headers).toHaveLength(3);
|
|
212
216
|
});
|
|
213
217
|
|
|
218
|
+
describe('hasRowHighlight', () => {
|
|
219
|
+
const selectedBgColor = colorVars['--color-accent-muted'];
|
|
220
|
+
|
|
221
|
+
it('paints a checked row with the accent wash by default', async () => {
|
|
222
|
+
const user = userEvent.setup();
|
|
223
|
+
render(<SelectionTable />);
|
|
224
|
+
|
|
225
|
+
await user.click(screen.getAllByLabelText('Select row')[0]);
|
|
226
|
+
|
|
227
|
+
const row = screen.getAllByRole('row')[1];
|
|
228
|
+
expect(row.style.backgroundColor).toBe(selectedBgColor);
|
|
229
|
+
expect(row).toHaveAttribute('aria-selected', 'true');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('leaves the row background alone when hasRowHighlight is false', async () => {
|
|
233
|
+
const user = userEvent.setup();
|
|
234
|
+
render(<SelectionTable hasRowHighlight={false} />);
|
|
235
|
+
|
|
236
|
+
await user.click(screen.getAllByLabelText('Select row')[0]);
|
|
237
|
+
|
|
238
|
+
const row = screen.getAllByRole('row')[1];
|
|
239
|
+
expect(row.style.backgroundColor).toBe('');
|
|
240
|
+
// The wash is opt-out; the semantics are not.
|
|
241
|
+
expect(row).toHaveAttribute('aria-selected', 'true');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('clears an already-painted row when the flag flips to false', async () => {
|
|
245
|
+
const user = userEvent.setup();
|
|
246
|
+
const {rerender} = render(<SelectionTable />);
|
|
247
|
+
|
|
248
|
+
await user.click(screen.getAllByLabelText('Select row')[0]);
|
|
249
|
+
expect(screen.getAllByRole('row')[1].style.backgroundColor).toBe(
|
|
250
|
+
selectedBgColor,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
rerender(<SelectionTable hasRowHighlight={false} />);
|
|
254
|
+
|
|
255
|
+
const row = screen.getAllByRole('row')[1];
|
|
256
|
+
expect(row.style.backgroundColor).toBe('');
|
|
257
|
+
expect(row).toHaveAttribute('aria-selected', 'true');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('repaints an already-checked row when the flag flips back to true', async () => {
|
|
261
|
+
const user = userEvent.setup();
|
|
262
|
+
const {rerender} = render(<SelectionTable hasRowHighlight={false} />);
|
|
263
|
+
|
|
264
|
+
await user.click(screen.getAllByLabelText('Select row')[0]);
|
|
265
|
+
rerender(<SelectionTable hasRowHighlight />);
|
|
266
|
+
|
|
267
|
+
expect(screen.getAllByRole('row')[1].style.backgroundColor).toBe(
|
|
268
|
+
selectedBgColor,
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('never paints unchecked rows in either mode', async () => {
|
|
273
|
+
const user = userEvent.setup();
|
|
274
|
+
const {rerender} = render(<SelectionTable />);
|
|
275
|
+
|
|
276
|
+
await user.click(screen.getAllByLabelText('Select row')[0]);
|
|
277
|
+
expect(screen.getAllByRole('row')[2].style.backgroundColor).toBe('');
|
|
278
|
+
expect(screen.getAllByRole('row')[2]).not.toHaveAttribute(
|
|
279
|
+
'aria-selected',
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
rerender(<SelectionTable hasRowHighlight={false} />);
|
|
283
|
+
expect(screen.getAllByRole('row')[2].style.backgroundColor).toBe('');
|
|
284
|
+
expect(screen.getAllByRole('row')[2]).not.toHaveAttribute(
|
|
285
|
+
'aria-selected',
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
214
290
|
it('unsubscribes detached row refs instead of accumulating listeners', () => {
|
|
215
291
|
const getIsItemSelected = vi.fn(() => false);
|
|
216
292
|
const {result, rerender} = renderHook(() =>
|
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
* DOM elements and no central element tracking. Each subscription
|
|
27
27
|
* self-cleans when the row disconnects.
|
|
28
28
|
*
|
|
29
|
+
* Because the background is an inline style, it outranks anything
|
|
30
|
+
* userland can layer on with StyleX. `hasRowHighlight: false` is the
|
|
31
|
+
* supported way to reclaim the row background; `aria-selected` is
|
|
32
|
+
* unaffected by it.
|
|
33
|
+
*
|
|
29
34
|
* SYNC: When modified, update these files to stay in sync:
|
|
35
|
+
* - /packages/core/src/Table/useTableSelection.doc.mjs (config prop table)
|
|
30
36
|
* - /packages/core/src/Table/Table.doc.mjs (selection documentation)
|
|
31
37
|
* - /packages/core/src/Table/index.ts (exports)
|
|
32
38
|
*/
|
|
@@ -81,6 +87,22 @@ export interface UseTableSelectionConfig<T extends Record<string, unknown>> {
|
|
|
81
87
|
* ```
|
|
82
88
|
*/
|
|
83
89
|
getRowLabel?: (item: T) => string;
|
|
90
|
+
/**
|
|
91
|
+
* Paint checked rows with the accent wash. Set false when the surrounding
|
|
92
|
+
* UI already uses row background to mean something else — a row that is
|
|
93
|
+
* open in a detail panel, for instance. The wash is an inline style, so
|
|
94
|
+
* it cannot be overridden from userland; this flag is the way off it.
|
|
95
|
+
*
|
|
96
|
+
* Only the background is dropped. `aria-selected` is still set on checked
|
|
97
|
+
* rows either way, so the selection stays legible to screen readers.
|
|
98
|
+
*
|
|
99
|
+
* @default true
|
|
100
|
+
* @example
|
|
101
|
+
* ```
|
|
102
|
+
* useTableSelection({...config, hasRowHighlight: false})
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
hasRowHighlight?: boolean;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
// =============================================================================
|
|
@@ -121,18 +143,24 @@ function createSelectionStore<T extends Record<string, unknown>>(
|
|
|
121
143
|
|
|
122
144
|
/**
|
|
123
145
|
* Apply or remove selection styling on a <tr> element.
|
|
146
|
+
*
|
|
147
|
+
* `aria-selected` tracks selection on its own: it is the semantic half of
|
|
148
|
+
* the state and stays correct whether or not the row is painted.
|
|
124
149
|
*/
|
|
125
150
|
function applyRowSelectionStyle(
|
|
126
151
|
el: HTMLTableRowElement,
|
|
127
152
|
isSelected: boolean,
|
|
153
|
+
hasRowHighlight: boolean,
|
|
128
154
|
): void {
|
|
129
155
|
if (isSelected) {
|
|
130
156
|
el.setAttribute('aria-selected', 'true');
|
|
131
|
-
el.style.backgroundColor = selectedBgColor;
|
|
132
157
|
} else {
|
|
133
158
|
el.removeAttribute('aria-selected');
|
|
134
|
-
el.style.backgroundColor = '';
|
|
135
159
|
}
|
|
160
|
+
// Written on every pass, not just when painting: the flag can flip while a
|
|
161
|
+
// row is already selected, and the wash has to come back off.
|
|
162
|
+
el.style.backgroundColor =
|
|
163
|
+
isSelected && hasRowHighlight ? selectedBgColor : '';
|
|
136
164
|
}
|
|
137
165
|
|
|
138
166
|
// =============================================================================
|
|
@@ -358,18 +386,23 @@ export function useTableSelection<T extends Record<string, unknown>>(
|
|
|
358
386
|
if (!el) {
|
|
359
387
|
return;
|
|
360
388
|
}
|
|
389
|
+
const applyStyle = () => {
|
|
390
|
+
const config = store.getConfig();
|
|
391
|
+
applyRowSelectionStyle(
|
|
392
|
+
el,
|
|
393
|
+
config.getIsItemSelected(item),
|
|
394
|
+
config.hasRowHighlight ?? true,
|
|
395
|
+
);
|
|
396
|
+
};
|
|
361
397
|
// Apply initial style
|
|
362
|
-
|
|
398
|
+
applyStyle();
|
|
363
399
|
// Subscribe for future changes
|
|
364
400
|
const unsub = store.subscribe(() => {
|
|
365
401
|
if (!el.isConnected) {
|
|
366
402
|
unsub();
|
|
367
403
|
return;
|
|
368
404
|
}
|
|
369
|
-
|
|
370
|
-
el,
|
|
371
|
-
store.getConfig().getIsItemSelected(item),
|
|
372
|
-
);
|
|
405
|
+
applyStyle();
|
|
373
406
|
});
|
|
374
407
|
return () => {
|
|
375
408
|
unsub();
|
package/src/Table/types.ts
CHANGED
|
@@ -322,6 +322,19 @@ export interface BodyCellRenderProps {
|
|
|
322
322
|
* sticky offsets. Optional for backward compatibility.
|
|
323
323
|
*/
|
|
324
324
|
columns?: ReadonlyArray<TableColumn<Record<string, unknown>>>;
|
|
325
|
+
/**
|
|
326
|
+
* When true, the cell renders empty: BaseTable calls neither the column's
|
|
327
|
+
* `renderCell` nor the default renderer. Set it for a row whose cells a
|
|
328
|
+
* plugin is going to replace wholesale in `transformBodyRow` (a grouped-rows
|
|
329
|
+
* section header, a summary row) — the row is not one the consumer supplied,
|
|
330
|
+
* so their renderer can only misread it or throw, and its output is
|
|
331
|
+
* discarded moments later anyway.
|
|
332
|
+
*
|
|
333
|
+
* Applies to this cell only, and is decided per render against the final
|
|
334
|
+
* column list, so it covers columns other plugins contributed regardless of
|
|
335
|
+
* where in the pipeline this plugin sits.
|
|
336
|
+
*/
|
|
337
|
+
isContentSuppressed?: boolean;
|
|
325
338
|
}
|
|
326
339
|
|
|
327
340
|
/**
|
|
@@ -414,13 +427,18 @@ export type TableContextActions =
|
|
|
414
427
|
*
|
|
415
428
|
* 1. `transformColumns` — filter, reorder, or inject columns before rendering
|
|
416
429
|
* 2. `transformTable` — transform the root `<table>` element props
|
|
417
|
-
* 3. `
|
|
418
|
-
* 4. `
|
|
419
|
-
* 5. `
|
|
420
|
-
* 6. `
|
|
430
|
+
* 3. `transformHeaderCell` — transform each `<th>` props
|
|
431
|
+
* 4. `transformHeaderRow` — transform the header `<tr>` props
|
|
432
|
+
* 5. `transformBodyCell` — transform each body `<td>` props
|
|
433
|
+
* 6. `transformBodyRow` — transform each body `<tr>` props
|
|
421
434
|
* 7. `transformScrollWrapper` — transform the scroll-container wrapper around the table
|
|
422
435
|
* 8. `transformTableContext` — wrap the table output in context providers
|
|
423
436
|
*
|
|
437
|
+
* A row's cells are built before its row transform runs, and reach it as
|
|
438
|
+
* `children`: the cell hook is the earliest per-cell interception point, and a
|
|
439
|
+
* row transform can discard cells but cannot stop them being built (see
|
|
440
|
+
* `isContentSuppressed` on `BodyCellRenderProps` for that).
|
|
441
|
+
*
|
|
424
442
|
* Plugins may also contribute right-click menu actions by appending to
|
|
425
443
|
* `contextMenuActions` in `transformHeaderCell` / `transformBodyCell`
|
|
426
444
|
* (aggregated into one menu per header cell / row).
|
|
@@ -46,6 +46,11 @@ import {devWarn} from '../utils/devWarning';
|
|
|
46
46
|
* Canonical ordering for first-party plugin names.
|
|
47
47
|
* Plugins are sorted by their position in this array.
|
|
48
48
|
* Unknown names are appended after the known set.
|
|
49
|
+
*
|
|
50
|
+
* This decides LAYOUT — which column lands left of which, who wraps whom. It
|
|
51
|
+
* must never decide whether the table works: a plugin that renders in one
|
|
52
|
+
* order and crashes in the other is broken, and adding it here buys a lucky
|
|
53
|
+
* order rather than a fix.
|
|
49
54
|
*/
|
|
50
55
|
const PLUGIN_ORDER: ReadonlyArray<string> = [
|
|
51
56
|
'columnSettings',
|