@astryxdesign/core 0.1.1 → 0.1.2-canary.04e71b2
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 +105 -0
- package/README.md +131 -13
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
- package/dist/Button/Button.d.ts +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +53 -17
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +82 -43
- package/dist/Calendar/hooks/index.d.ts +0 -2
- package/dist/Calendar/hooks/index.d.ts.map +1 -1
- package/dist/Calendar/hooks/index.js +1 -2
- package/dist/Calendar/index.d.ts +3 -3
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/Calendar/index.js +1 -1
- package/dist/Calendar/styles.d.ts +3 -9
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +3 -11
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +17 -3
- package/dist/Center/Center.d.ts +11 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +9 -3
- package/dist/Chat/ChatMessageList.d.ts +14 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +2 -0
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +4 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +51 -9
- package/dist/CheckboxList/CheckboxList.d.ts +13 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +39 -9
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +49 -37
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -5
- package/dist/ContextMenu/ContextMenu.d.ts +4 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +93 -11
- package/dist/DateInput/DateInput.d.ts +23 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +59 -8
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +42 -9
- package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +78 -15
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +42 -14
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- package/dist/Field/Field.d.ts +19 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -0
- package/dist/Field/FieldLabel.d.ts +21 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +41 -26
- package/dist/FileInput/FileInput.d.ts +22 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +56 -10
- package/dist/Grid/Grid.d.ts +11 -1
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +8 -0
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -3
- package/dist/InputGroup/InputGroup.d.ts +2 -2
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +14 -5
- package/dist/InputGroup/InputGroupContext.d.ts +4 -0
- package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
- package/dist/InputGroup/InputGroupContext.js +2 -2
- package/dist/InputGroup/index.d.ts +1 -0
- package/dist/InputGroup/index.d.ts.map +1 -1
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +34 -2
- package/dist/Layer/anchorName.d.ts +20 -0
- package/dist/Layer/anchorName.d.ts.map +1 -0
- package/dist/Layer/anchorName.js +36 -0
- package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +8 -4
- package/dist/Layer/useLayer.d.ts +29 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +55 -11
- package/dist/Layout/Layout.d.ts +4 -11
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +10 -11
- package/dist/Layout/LayoutFooter.d.ts +2 -2
- package/dist/Layout/LayoutFooter.d.ts.map +1 -1
- package/dist/Layout/LayoutHeader.d.ts +2 -2
- package/dist/Layout/LayoutHeader.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.d.ts +2 -2
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.d.ts +51 -0
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +63 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -2
- package/dist/Link/Link.d.ts +17 -2
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +20 -9
- package/dist/Link/LinkProvider.d.ts +1 -0
- package/dist/Link/LinkProvider.d.ts.map +1 -1
- package/dist/Link/LinkProvider.js +1 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +18 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -0
- package/dist/MoreMenu/MoreMenu.d.ts +1 -7
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +0 -2
- package/dist/MultiSelector/MultiSelector.d.ts +25 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +103 -22
- package/dist/MultiSelector/hooks.d.ts +12 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +14 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +34 -3
- package/dist/NumberInput/NumberInput.d.ts +23 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +62 -12
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +46 -28
- package/dist/Popover/usePopover.d.ts +25 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +18 -6
- package/dist/PowerSearch/PowerSearch.d.ts +11 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +6 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +3 -5
- package/dist/RadioList/RadioList.d.ts +20 -2
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +105 -9
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +78 -44
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
- package/dist/Selector/Selector.d.ts +22 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +84 -23
- package/dist/Selector/hooks.d.ts +8 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +14 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +1 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +8 -2
- package/dist/Slider/Slider.d.ts +21 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +38 -11
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +1 -1
- package/dist/Stack/Stack.d.ts +38 -23
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +25 -1
- package/dist/Stack/StackItem.d.ts +12 -23
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +8 -1
- package/dist/Switch/Switch.d.ts +23 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +41 -11
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +6 -0
- package/dist/TabList/TabList.d.ts +16 -4
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +86 -10
- package/dist/TabList/TabListContext.d.ts +5 -0
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +5 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +38 -16
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +36 -7
- package/dist/Table/TableCell.d.ts +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +10 -10
- package/dist/Table/TableHeaderCell.d.ts +8 -1
- package/dist/Table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Table/TableHeaderCell.js +16 -16
- package/dist/Table/TableRow.d.ts +6 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +15 -7
- package/dist/Table/index.d.ts +4 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +3 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +8 -3
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
- package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
- package/dist/Table/tableContextMenu.d.ts +33 -0
- package/dist/Table/tableContextMenu.d.ts.map +1 -0
- package/dist/Table/tableContextMenu.js +133 -0
- package/dist/Table/types.d.ts +161 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +1 -1
- package/dist/Text/text.stylex.d.ts +2 -2
- package/dist/Text/text.stylex.js +2 -2
- package/dist/TextArea/TextArea.d.ts +22 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +41 -9
- package/dist/TextInput/TextInput.d.ts +23 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +55 -10
- package/dist/TimeInput/TimeInput.d.ts +23 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +53 -10
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +14 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +132 -3
- package/dist/ToggleButton/ToggleButton.d.ts +7 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +34 -57
- package/dist/Tokenizer/Tokenizer.d.ts +12 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +27 -5
- package/dist/Toolbar/Toolbar.d.ts +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +51 -7
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +57 -9
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +72 -2
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +51 -12
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
- package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
- package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +67 -6
- package/dist/Typeahead/Typeahead.d.ts +24 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +29 -5
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
- package/dist/VisuallyHidden/index.d.ts +11 -0
- package/dist/VisuallyHidden/index.d.ts.map +1 -0
- package/dist/VisuallyHidden/index.js +13 -0
- package/dist/astryx.css +40 -1
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- package/dist/hooks/focusableSelector.d.ts +22 -0
- package/dist/hooks/focusableSelector.d.ts.map +1 -0
- package/dist/hooks/focusableSelector.js +24 -0
- package/dist/hooks/index.d.ts +12 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +7 -2
- package/dist/hooks/useAnnounce.d.ts +40 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -0
- package/dist/hooks/useAnnounce.js +142 -0
- package/dist/hooks/useClickableContainer.d.ts +16 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +16 -4
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +4 -4
- package/dist/hooks/useFocusTrap.d.ts +18 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +87 -3
- package/dist/hooks/useGridFocus.d.ts +88 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +261 -62
- package/dist/hooks/useKeyboardHint.d.ts +82 -0
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHint.js +220 -0
- package/dist/hooks/useListFocus.d.ts +78 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +305 -52
- package/dist/hooks/useLongPress.d.ts +21 -0
- package/dist/hooks/useLongPress.d.ts.map +1 -0
- package/dist/hooks/useLongPress.js +89 -0
- package/dist/hooks/useTreeFocus.d.ts +157 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -0
- package/dist/hooks/useTreeFocus.js +337 -0
- package/dist/hooks/useTypeahead.d.ts +63 -0
- package/dist/hooks/useTypeahead.d.ts.map +1 -0
- package/dist/hooks/useTypeahead.js +117 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
- package/dist/theme/syntax/presets.d.ts +1 -1
- package/dist/theme/syntax/presets.d.ts.map +1 -1
- package/dist/theme/syntax/presets.js +1 -1
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.js +1 -1
- package/dist/theme/types.d.ts +1 -1
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/inputAria.d.ts +29 -0
- package/dist/utils/inputAria.d.ts.map +1 -0
- package/dist/utils/inputAria.js +30 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -2
- package/package.json +15 -5
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Button/Button.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +76 -21
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +168 -30
- package/src/Calendar/Calendar.tsx +121 -66
- package/src/Calendar/hooks/index.ts +0 -6
- package/src/Calendar/index.ts +1 -3
- package/src/Calendar/styles.ts +4 -9
- package/src/Carousel/Carousel.doc.mjs +7 -1
- package/src/Carousel/Carousel.test.tsx +20 -0
- package/src/Carousel/Carousel.tsx +16 -1
- package/src/Center/Center.doc.mjs +12 -2
- package/src/Center/Center.tsx +28 -3
- package/src/Chat/ChatComposer.doc.mjs +3 -0
- package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
- package/src/Chat/ChatMessageList.doc.mjs +8 -0
- package/src/Chat/ChatMessageList.test.tsx +18 -0
- package/src/Chat/ChatMessageList.tsx +16 -0
- package/src/Chat/useTriggerMenu.tsx +3 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
- package/src/CheckboxInput/CheckboxInput.tsx +78 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
- package/src/CheckboxList/CheckboxList.test.tsx +169 -16
- package/src/CheckboxList/CheckboxList.tsx +56 -3
- package/src/CheckboxList/CheckboxListContext.tsx +7 -0
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +117 -0
- package/src/Citation/Citation.tsx +9 -1
- package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
- package/src/CodeBlock/CodeBlock.test.tsx +115 -0
- package/src/CodeBlock/CodeBlock.tsx +61 -33
- package/src/CommandPalette/CommandPalette.test.tsx +47 -1
- package/src/CommandPalette/CommandPalette.tsx +9 -21
- package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
- package/src/ContextMenu/ContextMenu.test.tsx +155 -1
- package/src/ContextMenu/ContextMenu.tsx +113 -16
- package/src/DateInput/DateInput.doc.mjs +281 -56
- package/src/DateInput/DateInput.test.tsx +188 -2
- package/src/DateInput/DateInput.tsx +89 -10
- package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
- package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
- package/src/DateRangeInput/DateRangeInput.tsx +63 -5
- package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
- package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
- package/src/DateTimeInput/DateTimeInput.tsx +125 -17
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/Dialog/Dialog.tsx +12 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
- package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
- package/src/DropdownMenu/DropdownMenu.tsx +52 -18
- package/src/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/Field/Field.test.tsx +32 -0
- package/src/Field/Field.tsx +23 -5
- package/src/Field/FieldLabel.tsx +55 -17
- package/src/FileInput/FileInput.doc.mjs +17 -0
- package/src/FileInput/FileInput.test.tsx +283 -49
- package/src/FileInput/FileInput.tsx +76 -4
- package/src/Grid/Grid.doc.mjs +14 -4
- package/src/Grid/Grid.tsx +20 -0
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +13 -10
- package/src/Icon/Icon.doc.mjs +8 -0
- package/src/InputGroup/InputGroup.doc.mjs +2 -2
- package/src/InputGroup/InputGroup.test.tsx +98 -60
- package/src/InputGroup/InputGroup.tsx +21 -4
- package/src/InputGroup/InputGroupContext.ts +9 -4
- package/src/InputGroup/index.ts +1 -0
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +37 -12
- package/src/Layer/anchorName.test.ts +146 -0
- package/src/Layer/anchorName.ts +46 -0
- package/src/Layer/layerAnimations.stylex.ts +20 -4
- package/src/Layer/useLayer.doc.mjs +12 -2
- package/src/Layer/useLayer.tsx +100 -15
- package/src/Layout/Layout.doc.mjs +1 -1
- package/src/Layout/Layout.tsx +8 -16
- package/src/Layout/LayoutFooter.doc.mjs +4 -4
- package/src/Layout/LayoutFooter.tsx +3 -3
- package/src/Layout/LayoutHeader.doc.mjs +4 -4
- package/src/Layout/LayoutHeader.tsx +3 -3
- package/src/Layout/LayoutPanel.tsx +3 -3
- package/src/Layout/padding.stylex.ts +52 -0
- package/src/Lightbox/Lightbox.doc.mjs +9 -2
- package/src/Lightbox/Lightbox.test.tsx +53 -5
- package/src/Lightbox/Lightbox.tsx +9 -4
- package/src/Link/Link.doc.mjs +14 -0
- package/src/Link/Link.test.tsx +52 -2
- package/src/Link/Link.tsx +42 -7
- package/src/Link/LinkProvider.tsx +1 -0
- package/src/List/List.doc.mjs +7 -0
- package/src/Markdown/Markdown.doc.mjs +2 -0
- package/src/Markdown/Markdown.test.tsx +28 -26
- package/src/Markdown/Markdown.tsx +21 -6
- package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +25 -0
- package/src/MultiSelector/MultiSelector.test.tsx +375 -8
- package/src/MultiSelector/MultiSelector.tsx +145 -21
- package/src/MultiSelector/hooks.ts +27 -0
- package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
- package/src/NavMenu/NavHeadingMenu.tsx +50 -3
- package/src/NumberInput/NumberInput.doc.mjs +17 -0
- package/src/NumberInput/NumberInput.test.tsx +242 -40
- package/src/NumberInput/NumberInput.tsx +89 -11
- package/src/OverflowList/OverflowList.doc.mjs +17 -0
- package/src/Pagination/Pagination.doc.mjs +3 -3
- package/src/Pagination/Pagination.test.tsx +250 -14
- package/src/Pagination/Pagination.tsx +54 -29
- package/src/Popover/usePopover.doc.mjs +7 -3
- package/src/Popover/usePopover.tsx +49 -6
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +97 -1
- package/src/PowerSearch/PowerSearch.tsx +16 -3
- package/src/ProgressBar/ProgressBar.test.tsx +45 -27
- package/src/ProgressBar/ProgressBar.tsx +3 -4
- package/src/RadioList/RadioList.doc.mjs +9 -0
- package/src/RadioList/RadioList.test.tsx +252 -16
- package/src/RadioList/RadioList.tsx +166 -10
- package/src/RadioList/RadioListItem.tsx +14 -2
- package/src/Resizable/Resizable.doc.mjs +1 -1
- package/src/Section/Section.doc.mjs +11 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
- package/src/SegmentedControl/SegmentedControl.tsx +102 -54
- package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
- package/src/Selector/Selector.doc.mjs +25 -0
- package/src/Selector/Selector.test.tsx +322 -15
- package/src/Selector/Selector.tsx +126 -40
- package/src/Selector/hooks.ts +23 -0
- package/src/SideNav/SideNav.test.tsx +16 -0
- package/src/SideNav/SideNavItem.tsx +2 -4
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Slider/Slider.doc.mjs +19 -0
- package/src/Slider/Slider.test.tsx +158 -18
- package/src/Slider/Slider.tsx +54 -6
- package/src/Spinner/Spinner.tsx +7 -1
- package/src/Stack/Stack.doc.mjs +77 -0
- package/src/Stack/Stack.test.tsx +55 -5
- package/src/Stack/Stack.tsx +73 -24
- package/src/Stack/StackItem.test.tsx +23 -0
- package/src/Stack/StackItem.tsx +20 -23
- package/src/Switch/Switch.doc.mjs +15 -0
- package/src/Switch/Switch.test.tsx +162 -27
- package/src/Switch/Switch.tsx +61 -21
- package/src/TabList/Tab.tsx +6 -0
- package/src/TabList/TabList.doc.mjs +12 -0
- package/src/TabList/TabList.test.tsx +208 -0
- package/src/TabList/TabList.tsx +102 -12
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +6 -3
- package/src/Table/BaseTable.tsx +72 -41
- package/src/Table/Table.doc.mjs +14 -0
- package/src/Table/Table.test.tsx +39 -30
- package/src/Table/Table.tsx +27 -1
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/TableRow.tsx +37 -7
- package/src/Table/index.ts +6 -2
- package/src/Table/plugins/pagination/paginateData.ts +10 -3
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
- package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
- package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
- package/src/Table/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +142 -0
- package/src/Table/types.ts +175 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/Table/useTableColumnResize.doc.mjs +96 -0
- package/src/Table/useTableStickyColumns.doc.mjs +59 -0
- package/src/Text/Heading.doc.mjs +15 -2
- package/src/Text/Text.doc.mjs +7 -1
- package/src/Text/text.stylex.ts +2 -2
- package/src/TextArea/TextArea.doc.mjs +17 -0
- package/src/TextArea/TextArea.test.tsx +167 -35
- package/src/TextArea/TextArea.tsx +62 -15
- package/src/TextInput/TextInput.doc.mjs +17 -0
- package/src/TextInput/TextInput.test.tsx +168 -34
- package/src/TextInput/TextInput.tsx +77 -9
- package/src/TimeInput/TimeInput.doc.mjs +335 -60
- package/src/TimeInput/TimeInput.test.tsx +144 -7
- package/src/TimeInput/TimeInput.tsx +74 -5
- package/src/Timestamp/Timestamp.doc.mjs +9 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +204 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
- package/src/ToggleButton/ToggleButton.test.tsx +39 -18
- package/src/ToggleButton/ToggleButton.tsx +46 -83
- package/src/Tokenizer/Tokenizer.doc.mjs +15 -0
- package/src/Tokenizer/Tokenizer.test.tsx +105 -1
- package/src/Tokenizer/Tokenizer.tsx +41 -2
- package/src/Toolbar/Toolbar.doc.mjs +65 -5
- package/src/Toolbar/Toolbar.test.tsx +109 -6
- package/src/Toolbar/Toolbar.tsx +59 -5
- package/src/Tooltip/Tooltip.test.tsx +81 -0
- package/src/Tooltip/useTooltip.tsx +57 -11
- package/src/TreeList/TreeList.test.tsx +295 -6
- package/src/TreeList/TreeList.tsx +73 -1
- package/src/TreeList/TreeListItem.tsx +51 -5
- package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
- package/src/Typeahead/BaseTypeahead.tsx +86 -9
- package/src/Typeahead/Typeahead.doc.mjs +136 -30
- package/src/Typeahead/Typeahead.test.tsx +331 -1
- package/src/Typeahead/Typeahead.tsx +52 -1
- package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
- package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
- package/src/VisuallyHidden/index.ts +15 -0
- package/src/hooks/focusableSelector.ts +25 -0
- package/src/hooks/index.ts +28 -2
- package/src/hooks/useAnnounce.test.tsx +101 -0
- package/src/hooks/useAnnounce.ts +168 -0
- package/src/hooks/useClickableContainer.ts +16 -4
- package/src/hooks/useEntryAnimation.ts +16 -4
- package/src/hooks/useFocusTrap.test.tsx +132 -0
- package/src/hooks/useFocusTrap.ts +98 -4
- package/src/hooks/useGridFocus.doc.mjs +40 -2
- package/src/hooks/useGridFocus.test.tsx +132 -0
- package/src/hooks/useGridFocus.ts +343 -67
- package/src/hooks/useImageMode.test.ts +133 -0
- package/src/hooks/useKeyboardHint.doc.mjs +101 -0
- package/src/hooks/useKeyboardHint.test.tsx +70 -0
- package/src/hooks/useKeyboardHint.tsx +332 -0
- package/src/hooks/useListFocus.doc.mjs +50 -10
- package/src/hooks/useListFocus.test.tsx +375 -0
- package/src/hooks/useListFocus.ts +395 -57
- package/src/hooks/useLongPress.test.tsx +134 -0
- package/src/hooks/useLongPress.ts +119 -0
- package/src/hooks/useTreeFocus.doc.mjs +140 -0
- package/src/hooks/useTreeFocus.test.tsx +214 -0
- package/src/hooks/useTreeFocus.ts +559 -0
- package/src/hooks/useTypeahead.test.tsx +109 -0
- package/src/hooks/useTypeahead.ts +183 -0
- package/src/index.ts +1 -0
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +7 -8
- package/src/theme/generateThemeRules.ts +1 -1
- package/src/theme/syntax/SyntaxTheme.tsx +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
- package/src/theme/syntax/presets.ts +5 -2
- package/src/theme/syntax/tokens.ts +1 -1
- package/src/theme/types.ts +1 -1
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/inputAria.test.ts +39 -0
- package/src/utils/inputAria.ts +58 -0
- package/src/utils/timeParser.test.ts +10 -0
- package/src/utils/timeParser.ts +5 -2
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
- package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
- package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTableStickyColumns.tsx
|
|
7
|
+
* @input React, StyleX, theme tokens, Table types
|
|
8
|
+
* @output Exports useTableStickyColumns hook and UseTableStickyColumnsConfig type
|
|
9
|
+
* @position Sticky-columns plugin; consumed by Table via plugins prop
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/Table.doc.mjs (sticky-columns documentation)
|
|
13
|
+
* - /packages/core/src/Table/index.ts (exports)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {useCallback, useMemo, useRef, type CSSProperties} from 'react';
|
|
17
|
+
import * as stylex from '@stylexjs/stylex';
|
|
18
|
+
import {colorVars, durationVars, easeVars} from '../../../theme/tokens.stylex';
|
|
19
|
+
import type {
|
|
20
|
+
TableColumn,
|
|
21
|
+
TablePlugin,
|
|
22
|
+
HeaderCellRenderProps,
|
|
23
|
+
BodyCellRenderProps,
|
|
24
|
+
ScrollWrapperRenderProps,
|
|
25
|
+
} from '../../types';
|
|
26
|
+
import {DEFAULT_MIN_COLUMN_WIDTH} from '../../columnUtils';
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Config
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Config for {@link useTableStickyColumns}. Provide at least one of
|
|
34
|
+
* `startKeys` / `endKeys` to pin columns.
|
|
35
|
+
*
|
|
36
|
+
* @remarks Every field is optional by design, so `useTableStickyColumns({})`
|
|
37
|
+
* compiles and is an intentional no-op that pins nothing — the hook returns a
|
|
38
|
+
* plugin that passes every cell through untouched. This lets callers compute
|
|
39
|
+
* the config conditionally (e.g. `endKeys: enabled ? ['notes'] : undefined`)
|
|
40
|
+
* without branching on whether to install the plugin at all.
|
|
41
|
+
*/
|
|
42
|
+
export interface UseTableStickyColumnsConfig {
|
|
43
|
+
/**
|
|
44
|
+
* Column keys pinned to the START (inline-start / left in LTR) edge — the
|
|
45
|
+
* contiguous run from the first column through the last listed key.
|
|
46
|
+
*/
|
|
47
|
+
startKeys?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Column keys pinned to the END (inline-end / right in LTR) edge — the
|
|
50
|
+
* contiguous run from the first listed key through the last column.
|
|
51
|
+
*/
|
|
52
|
+
endKeys?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Width helpers
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a column's pixel width for cumulative offset math. Mirrors the
|
|
61
|
+
* resize plugin's fallback so offsets line up with rendered widths:
|
|
62
|
+
* pixel columns use their value; proportional columns use their declared
|
|
63
|
+
* minWidth (or the default); unknown widths use the default.
|
|
64
|
+
*/
|
|
65
|
+
function getColumnWidth(col: TableColumn<Record<string, unknown>>): number {
|
|
66
|
+
const w = col.width;
|
|
67
|
+
if (!w) {
|
|
68
|
+
return DEFAULT_MIN_COLUMN_WIDTH;
|
|
69
|
+
}
|
|
70
|
+
if (w.type === 'pixel') {
|
|
71
|
+
return w.value;
|
|
72
|
+
}
|
|
73
|
+
// proportional
|
|
74
|
+
return w.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Columns pinned to the START edge → cumulative inline offset (px). The pinned
|
|
79
|
+
* block is the contiguous run from column 0 through the last key in `startKeys`
|
|
80
|
+
* (inclusive of any synthetic columns before it). `null` if none / no context.
|
|
81
|
+
*/
|
|
82
|
+
function computeStartOffsets(
|
|
83
|
+
columns: ReadonlyArray<TableColumn<Record<string, unknown>>> | undefined,
|
|
84
|
+
startKeys: string[],
|
|
85
|
+
): Map<string, number> | null {
|
|
86
|
+
if (!columns || columns.length === 0 || startKeys.length === 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
let lastStickyIndex = -1;
|
|
90
|
+
for (let i = 0; i < columns.length; i++) {
|
|
91
|
+
if (startKeys.includes(columns[i].key)) {
|
|
92
|
+
lastStickyIndex = i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (lastStickyIndex === -1) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const offsets = new Map<string, number>();
|
|
99
|
+
let cumulative = 0;
|
|
100
|
+
for (let i = 0; i <= lastStickyIndex; i++) {
|
|
101
|
+
offsets.set(columns[i].key, cumulative);
|
|
102
|
+
cumulative += getColumnWidth(columns[i]);
|
|
103
|
+
}
|
|
104
|
+
return offsets;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mirror of {@link computeStartOffsets} for the END edge — contiguous run from
|
|
109
|
+
* the first key in `endKeys` through the last column, offsets accumulating from
|
|
110
|
+
* the end (last column = 0). `null` if none.
|
|
111
|
+
*/
|
|
112
|
+
function computeEndOffsets(
|
|
113
|
+
columns: ReadonlyArray<TableColumn<Record<string, unknown>>> | undefined,
|
|
114
|
+
endKeys: string[],
|
|
115
|
+
): Map<string, number> | null {
|
|
116
|
+
if (!columns || columns.length === 0 || endKeys.length === 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
let firstStickyIndex = -1;
|
|
120
|
+
for (let i = 0; i < columns.length; i++) {
|
|
121
|
+
if (endKeys.includes(columns[i].key)) {
|
|
122
|
+
firstStickyIndex = i;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (firstStickyIndex === -1) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const offsets = new Map<string, number>();
|
|
130
|
+
let cumulative = 0;
|
|
131
|
+
for (let i = columns.length - 1; i >= firstStickyIndex; i--) {
|
|
132
|
+
offsets.set(columns[i].key, cumulative);
|
|
133
|
+
cumulative += getColumnWidth(columns[i]);
|
|
134
|
+
}
|
|
135
|
+
return offsets;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type StickySide = {edge: 'start' | 'end'; offset: number};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve how a single column should be pinned given the start/end configs and
|
|
142
|
+
* the full column list. A key (mis)configured on both edges resolves to start.
|
|
143
|
+
* Returns `null` for columns that should not be pinned.
|
|
144
|
+
*/
|
|
145
|
+
function resolveStickySide(
|
|
146
|
+
columns: ReadonlyArray<TableColumn<Record<string, unknown>>> | undefined,
|
|
147
|
+
columnKey: string,
|
|
148
|
+
startKeys: string[],
|
|
149
|
+
endKeys: string[],
|
|
150
|
+
): StickySide | null {
|
|
151
|
+
const startOffsets = computeStartOffsets(columns, startKeys);
|
|
152
|
+
if (startOffsets?.has(columnKey)) {
|
|
153
|
+
return {edge: 'start', offset: startOffsets.get(columnKey) ?? 0};
|
|
154
|
+
}
|
|
155
|
+
const endOffsets = computeEndOffsets(columns, endKeys);
|
|
156
|
+
if (endOffsets?.has(columnKey)) {
|
|
157
|
+
return {edge: 'end', offset: endOffsets.get(columnKey) ?? 0};
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Styles
|
|
164
|
+
// =============================================================================
|
|
165
|
+
|
|
166
|
+
// Scroll-state CSS variables set on the scroll container by the layout ref;
|
|
167
|
+
// the cell ::after shadows read them so each edge's shadow only shows when
|
|
168
|
+
// content is hidden behind it. (A variable, not stylex.when.ancestor, because
|
|
169
|
+
// StyleX ancestor selectors are pseudo-class only — not scroll state.)
|
|
170
|
+
const SHADOW_VAR_START = '--table-sticky-shadow-start';
|
|
171
|
+
const SHADOW_VAR_END = '--table-sticky-shadow-end';
|
|
172
|
+
|
|
173
|
+
const stickyStyles = stylex.create({
|
|
174
|
+
cell: {
|
|
175
|
+
position: 'sticky',
|
|
176
|
+
// Opaque base (overridable; defaults to the card token, the common
|
|
177
|
+
// container) so scrolled content doesn't show through the pinned column.
|
|
178
|
+
backgroundColor: `var(--table-sticky-background, ${colorVars['--color-background-card']})`,
|
|
179
|
+
// Replay the row's overlay on a ::before layer (above the base, below text)
|
|
180
|
+
// so the pinned column mirrors the row's striping/hover exactly via
|
|
181
|
+
// --table-row-overlay. Transitions background-COLOR (not background-image)
|
|
182
|
+
// so hover stays smooth at rest, not just when GPU-promoted while scrolled.
|
|
183
|
+
'::before': {
|
|
184
|
+
content: '""',
|
|
185
|
+
position: 'absolute',
|
|
186
|
+
inset: 0,
|
|
187
|
+
zIndex: -1,
|
|
188
|
+
pointerEvents: 'none',
|
|
189
|
+
backgroundColor: 'var(--table-row-overlay, transparent)',
|
|
190
|
+
transitionProperty: 'background-color',
|
|
191
|
+
transitionDuration: durationVars['--duration-fast'],
|
|
192
|
+
transitionTimingFunction: easeVars['--ease-standard'],
|
|
193
|
+
},
|
|
194
|
+
// padding-box keeps the base off the cell's collapsed divider border.
|
|
195
|
+
backgroundClip: 'padding-box',
|
|
196
|
+
// Sticky cells need visible overflow so the shadow ::after can bleed out.
|
|
197
|
+
overflow: 'visible',
|
|
198
|
+
},
|
|
199
|
+
headerCell: {
|
|
200
|
+
// Header cells stack above body cells; both stack above non-sticky cells.
|
|
201
|
+
zIndex: 3,
|
|
202
|
+
},
|
|
203
|
+
bodyCell: {
|
|
204
|
+
zIndex: 1,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Soft drop shadow over the scrolling region (matches the EPS sticky treatment).
|
|
209
|
+
// border-collapse tables don't paint box-shadow on cells in Chromium, so it's a
|
|
210
|
+
// ::after gradient strip just past the pinned edge (visible thanks to the cell's
|
|
211
|
+
// overflow: visible). Opacity is gated by the scroll-state variable above.
|
|
212
|
+
const SHADOW_WIDTH = '6px';
|
|
213
|
+
// --color-shadow is only ~10% alpha (too faint here); use a slightly stronger soft tint.
|
|
214
|
+
const SHADOW_TINT = 'light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.32))';
|
|
215
|
+
const shadowStyles = stylex.create({
|
|
216
|
+
// start-pinned: shadow falls to the right (inline-end), over scrolled content
|
|
217
|
+
start: {
|
|
218
|
+
'::after': {
|
|
219
|
+
content: '""',
|
|
220
|
+
position: 'absolute',
|
|
221
|
+
top: 0,
|
|
222
|
+
bottom: 0,
|
|
223
|
+
insetInlineEnd: 0,
|
|
224
|
+
width: SHADOW_WIDTH,
|
|
225
|
+
transform: 'translateX(100%)',
|
|
226
|
+
pointerEvents: 'none',
|
|
227
|
+
transition: 'opacity 150ms ease',
|
|
228
|
+
opacity: `var(${SHADOW_VAR_START}, 0)`,
|
|
229
|
+
backgroundImage: `linear-gradient(to right, ${SHADOW_TINT}, transparent)`,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
// end-pinned: shadow falls to the left (inline-start), over scrolled content
|
|
233
|
+
end: {
|
|
234
|
+
'::after': {
|
|
235
|
+
content: '""',
|
|
236
|
+
position: 'absolute',
|
|
237
|
+
top: 0,
|
|
238
|
+
bottom: 0,
|
|
239
|
+
insetInlineStart: 0,
|
|
240
|
+
width: SHADOW_WIDTH,
|
|
241
|
+
transform: 'translateX(-100%)',
|
|
242
|
+
pointerEvents: 'none',
|
|
243
|
+
transition: 'opacity 150ms ease',
|
|
244
|
+
opacity: `var(${SHADOW_VAR_END}, 0)`,
|
|
245
|
+
backgroundImage: `linear-gradient(to left, ${SHADOW_TINT}, transparent)`,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Stable empty default so an unset start/end doesn't allocate a fresh array
|
|
251
|
+
// (and bust the memo) on every render.
|
|
252
|
+
const EMPTY: string[] = [];
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Hook
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
export function useTableStickyColumns<T extends Record<string, unknown>>(
|
|
259
|
+
config: UseTableStickyColumnsConfig,
|
|
260
|
+
): TablePlugin<T> {
|
|
261
|
+
const {startKeys, endKeys} = config;
|
|
262
|
+
const start = startKeys ?? EMPTY;
|
|
263
|
+
const end = endKeys ?? EMPTY;
|
|
264
|
+
|
|
265
|
+
const hasStart = start.length > 0;
|
|
266
|
+
const hasEnd = end.length > 0;
|
|
267
|
+
|
|
268
|
+
// Live snapshot of the resolved config, read inside the (memoized) transforms
|
|
269
|
+
// and the scroll-shadow callback. Keeping these in a ref lets the plugin memo
|
|
270
|
+
// be computed once (deps: only the stable scroll-shadow callback) while never
|
|
271
|
+
// reading stale config — consumers typically pass fresh array literals each
|
|
272
|
+
// render, so we never want array identity in the deps.
|
|
273
|
+
const stateRef = useRef({start, end, hasStart, hasEnd});
|
|
274
|
+
stateRef.current = {start, end, hasStart, hasEnd};
|
|
275
|
+
|
|
276
|
+
// Scroll-aware shadows: toggle CSS variables on the scroll container so each
|
|
277
|
+
// edge's shadow only paints when there is hidden, horizontally-scrolled
|
|
278
|
+
// content behind that edge. Implemented with a callback ref + scroll/resize
|
|
279
|
+
// listeners (synchronizing with the DOM) rather than React state, so
|
|
280
|
+
// scrolling never triggers re-renders.
|
|
281
|
+
const detachRef = useRef<(() => void) | null>(null);
|
|
282
|
+
const attachScrollShadow = useCallback((el: HTMLDivElement | null) => {
|
|
283
|
+
detachRef.current?.();
|
|
284
|
+
detachRef.current = null;
|
|
285
|
+
if (!el) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const update = () => {
|
|
289
|
+
const {hasStart: hs, hasEnd: he} = stateRef.current;
|
|
290
|
+
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
291
|
+
const hasOverflow = maxScroll > 1;
|
|
292
|
+
if (hs) {
|
|
293
|
+
el.style.setProperty(
|
|
294
|
+
SHADOW_VAR_START,
|
|
295
|
+
hasOverflow && el.scrollLeft > 1 ? '1' : '0',
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (he) {
|
|
299
|
+
el.style.setProperty(
|
|
300
|
+
SHADOW_VAR_END,
|
|
301
|
+
hasOverflow && el.scrollLeft < maxScroll - 1 ? '1' : '0',
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
el.addEventListener('scroll', update, {passive: true});
|
|
306
|
+
const resizeObserver =
|
|
307
|
+
typeof ResizeObserver !== 'undefined' ? new ResizeObserver(update) : null;
|
|
308
|
+
resizeObserver?.observe(el);
|
|
309
|
+
update();
|
|
310
|
+
detachRef.current = () => {
|
|
311
|
+
el.removeEventListener('scroll', update);
|
|
312
|
+
resizeObserver?.disconnect();
|
|
313
|
+
};
|
|
314
|
+
}, []);
|
|
315
|
+
|
|
316
|
+
return useMemo(
|
|
317
|
+
(): TablePlugin<T> => ({
|
|
318
|
+
transformHeaderCell(
|
|
319
|
+
props: HeaderCellRenderProps,
|
|
320
|
+
column: TableColumn<T>,
|
|
321
|
+
): HeaderCellRenderProps {
|
|
322
|
+
const {start: s, end: e} = stateRef.current;
|
|
323
|
+
const side = resolveStickySide(props.columns, column.key, s, e);
|
|
324
|
+
if (!side) {
|
|
325
|
+
return props;
|
|
326
|
+
}
|
|
327
|
+
// position/inline-offset are runtime values → set via inline style so
|
|
328
|
+
// they are authoritative regardless of plugin composition order (the
|
|
329
|
+
// resize plugin also writes inline style on header cells).
|
|
330
|
+
const offsetStyle: CSSProperties =
|
|
331
|
+
side.edge === 'start'
|
|
332
|
+
? {insetInlineStart: `${side.offset}px`}
|
|
333
|
+
: {insetInlineEnd: `${side.offset}px`};
|
|
334
|
+
return {
|
|
335
|
+
...props,
|
|
336
|
+
htmlProps: {
|
|
337
|
+
...props.htmlProps,
|
|
338
|
+
style: {...props.htmlProps.style, ...offsetStyle},
|
|
339
|
+
},
|
|
340
|
+
styles: [
|
|
341
|
+
...props.styles,
|
|
342
|
+
stickyStyles.cell,
|
|
343
|
+
stickyStyles.headerCell,
|
|
344
|
+
side.edge === 'start' ? shadowStyles.start : shadowStyles.end,
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
transformBodyCell(
|
|
350
|
+
props: BodyCellRenderProps,
|
|
351
|
+
column: TableColumn<T>,
|
|
352
|
+
): BodyCellRenderProps {
|
|
353
|
+
const {start: s, end: e} = stateRef.current;
|
|
354
|
+
const side = resolveStickySide(props.columns, column.key, s, e);
|
|
355
|
+
if (!side) {
|
|
356
|
+
return props;
|
|
357
|
+
}
|
|
358
|
+
const offsetStyle: CSSProperties =
|
|
359
|
+
side.edge === 'start'
|
|
360
|
+
? {insetInlineStart: `${side.offset}px`}
|
|
361
|
+
: {insetInlineEnd: `${side.offset}px`};
|
|
362
|
+
return {
|
|
363
|
+
...props,
|
|
364
|
+
htmlProps: {
|
|
365
|
+
...props.htmlProps,
|
|
366
|
+
style: {...props.htmlProps.style, ...offsetStyle},
|
|
367
|
+
},
|
|
368
|
+
styles: [
|
|
369
|
+
...props.styles,
|
|
370
|
+
stickyStyles.cell,
|
|
371
|
+
stickyStyles.bodyCell,
|
|
372
|
+
side.edge === 'start' ? shadowStyles.start : shadowStyles.end,
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
transformScrollWrapper(
|
|
378
|
+
props: ScrollWrapperRenderProps,
|
|
379
|
+
): ScrollWrapperRenderProps {
|
|
380
|
+
// No pinned edges → nothing to gate; leave the wrapper untouched.
|
|
381
|
+
if (!stateRef.current.hasStart && !stateRef.current.hasEnd) {
|
|
382
|
+
return props;
|
|
383
|
+
}
|
|
384
|
+
// Compose with any ref a prior plugin (e.g. virtualization) set on the
|
|
385
|
+
// scroll container.
|
|
386
|
+
const existingRef = props.htmlProps.ref;
|
|
387
|
+
const mergedRef = (node: HTMLDivElement | null) => {
|
|
388
|
+
attachScrollShadow(node);
|
|
389
|
+
if (typeof existingRef === 'function') {
|
|
390
|
+
existingRef(node);
|
|
391
|
+
} else if (existingRef != null) {
|
|
392
|
+
// RefObject — assign through its writable `.current`.
|
|
393
|
+
existingRef.current = node;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
return {
|
|
397
|
+
...props,
|
|
398
|
+
htmlProps: {...props.htmlProps, ref: mergedRef},
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
// The returned plugin's transforms read live config from `stateRef` and
|
|
403
|
+
// `attachScrollShadow` is stable (empty deps), so the plugin object can be
|
|
404
|
+
// computed once and reused across renders.
|
|
405
|
+
[attachScrollShadow],
|
|
406
|
+
);
|
|
407
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file tableContextMenu.test.tsx
|
|
5
|
+
* @output Tests for the Table context-menu system
|
|
6
|
+
* @position Validates contextMenuActions aggregation + header context menus
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
10
|
+
import {render, screen, fireEvent} from '@testing-library/react';
|
|
11
|
+
import {Table} from './Table';
|
|
12
|
+
import type {TablePlugin, TableColumn} from './types';
|
|
13
|
+
import {resolveContextActions} from './tableContextMenu';
|
|
14
|
+
|
|
15
|
+
// jsdom doesn't implement the Popover API; mirror the ContextMenu test's mock
|
|
16
|
+
// so the menu can "open" and its items become queryable (as hidden).
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
19
|
+
this.setAttribute('popover-open', '');
|
|
20
|
+
const event = new Event('toggle', {bubbles: false});
|
|
21
|
+
Object.defineProperty(event, 'newState', {value: 'open'});
|
|
22
|
+
this.dispatchEvent(event);
|
|
23
|
+
});
|
|
24
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
25
|
+
this.removeAttribute('popover-open');
|
|
26
|
+
const event = new Event('toggle', {bubbles: false});
|
|
27
|
+
Object.defineProperty(event, 'newState', {value: 'closed'});
|
|
28
|
+
this.dispatchEvent(event);
|
|
29
|
+
});
|
|
30
|
+
const originalMatches = HTMLElement.prototype.matches;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
(HTMLElement.prototype as any).matches = function (
|
|
33
|
+
selector: string,
|
|
34
|
+
): boolean {
|
|
35
|
+
if (selector === ':popover-open') {
|
|
36
|
+
return this.hasAttribute('popover-open');
|
|
37
|
+
}
|
|
38
|
+
return originalMatches.call(this, selector);
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
interface Row extends Record<string, unknown> {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const data: Row[] = [
|
|
48
|
+
{id: '1', name: 'Alice'},
|
|
49
|
+
{id: '2', name: 'Bob'},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const columns: TableColumn<Row>[] = [
|
|
53
|
+
{key: 'name', header: 'Name'},
|
|
54
|
+
{key: 'id', header: 'ID'},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// contextMenuActions via transformHeaderCell
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
describe('Table header context menu', () => {
|
|
62
|
+
it('renders a menu from a plugin that sets contextMenuActions', () => {
|
|
63
|
+
const onSelect = vi.fn();
|
|
64
|
+
const plugin: TablePlugin<Row> = {
|
|
65
|
+
transformHeaderCell: props => ({
|
|
66
|
+
...props,
|
|
67
|
+
contextMenuActions: [{id: 'pin', label: 'Pin column', onSelect}],
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
render(
|
|
71
|
+
<Table data={data} columns={columns} idKey="id" plugins={{plugin}} />,
|
|
72
|
+
);
|
|
73
|
+
fireEvent.contextMenu(screen.getByText('Name'));
|
|
74
|
+
const items = screen.getAllByRole('menuitem', {
|
|
75
|
+
name: 'Pin column',
|
|
76
|
+
hidden: true,
|
|
77
|
+
});
|
|
78
|
+
expect(items.length).toBeGreaterThan(0);
|
|
79
|
+
fireEvent.click(items[0]);
|
|
80
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('concatenates actions from multiple plugins (never overridden)', () => {
|
|
84
|
+
const a: TablePlugin<Row> = {
|
|
85
|
+
transformHeaderCell: props => ({
|
|
86
|
+
...props,
|
|
87
|
+
contextMenuActions: [
|
|
88
|
+
...resolveContextActions(props.contextMenuActions),
|
|
89
|
+
{id: 'a', label: 'Action A', onSelect: () => {}},
|
|
90
|
+
],
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
const b: TablePlugin<Row> = {
|
|
94
|
+
transformHeaderCell: props => ({
|
|
95
|
+
...props,
|
|
96
|
+
contextMenuActions: [
|
|
97
|
+
...resolveContextActions(props.contextMenuActions),
|
|
98
|
+
{id: 'b', label: 'Action B', onSelect: () => {}},
|
|
99
|
+
],
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
render(
|
|
103
|
+
<Table data={data} columns={columns} idKey="id" plugins={{a, b}} />,
|
|
104
|
+
);
|
|
105
|
+
fireEvent.contextMenu(screen.getByText('Name'));
|
|
106
|
+
expect(
|
|
107
|
+
screen.getAllByRole('menuitem', {name: 'Action A', hidden: true}).length,
|
|
108
|
+
).toBeGreaterThan(0);
|
|
109
|
+
expect(
|
|
110
|
+
screen.getAllByRole('menuitem', {name: 'Action B', hidden: true}).length,
|
|
111
|
+
).toBeGreaterThan(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not render a menu when no plugin contributes actions', () => {
|
|
115
|
+
render(<Table data={data} columns={columns} idKey="id" />);
|
|
116
|
+
fireEvent.contextMenu(screen.getByText('Name'));
|
|
117
|
+
expect(
|
|
118
|
+
screen.queryByRole('menuitem', {hidden: true}),
|
|
119
|
+
).not.toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// =============================================================================
|
|
124
|
+
// Body / row context menu
|
|
125
|
+
// =============================================================================
|
|
126
|
+
|
|
127
|
+
describe('Table body context menu', () => {
|
|
128
|
+
it('renders row actions from a plugin that sets contextMenuActions in transformBodyCell', () => {
|
|
129
|
+
const onSelect = vi.fn();
|
|
130
|
+
const plugin: TablePlugin<Row> = {
|
|
131
|
+
transformBodyCell: (props, _column, item) => ({
|
|
132
|
+
...props,
|
|
133
|
+
contextMenuActions: [
|
|
134
|
+
{
|
|
135
|
+
id: `delete-${item.id}`,
|
|
136
|
+
label: 'Delete row',
|
|
137
|
+
onSelect: () => {
|
|
138
|
+
onSelect(item.id);
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
render(
|
|
145
|
+
<Table data={data} columns={columns} idKey="id" plugins={{plugin}} />,
|
|
146
|
+
);
|
|
147
|
+
// Right-click the first body cell (Alice).
|
|
148
|
+
fireEvent.contextMenu(screen.getByText('Alice'));
|
|
149
|
+
const items = screen.getAllByRole('menuitem', {
|
|
150
|
+
name: 'Delete row',
|
|
151
|
+
hidden: true,
|
|
152
|
+
});
|
|
153
|
+
expect(items.length).toBeGreaterThan(0);
|
|
154
|
+
fireEvent.click(items[0]);
|
|
155
|
+
expect(onSelect).toHaveBeenCalledWith('1');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file tableContextMenu.tsx
|
|
7
|
+
* @input React, ContextMenu, Icon, types
|
|
8
|
+
* @output wrapInTableContextMenu helper
|
|
9
|
+
* @position Renders the aggregated `contextMenuActions` for a header cell / row
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/BaseTable.tsx (call sites)
|
|
13
|
+
* - /packages/core/src/Table/types.ts (TableContextAction)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {useState, type ReactNode} from 'react';
|
|
17
|
+
import {ContextMenu, type ContextMenuOption} from '../ContextMenu';
|
|
18
|
+
import {Icon} from '../Icon';
|
|
19
|
+
import type {TableContextAction, TableContextActions} from './types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a `contextMenuActions` value (array or getter) to a plain array.
|
|
23
|
+
* Use this when composing actions from a prior plugin's render props so you
|
|
24
|
+
* don't have to branch on the array-vs-getter form:
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* contextMenuActions: () => [
|
|
28
|
+
* ...resolveContextActions(props.contextMenuActions),
|
|
29
|
+
* ...myActions,
|
|
30
|
+
* ]
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function resolveContextActions(
|
|
34
|
+
actions: TableContextActions | undefined,
|
|
35
|
+
): TableContextAction[] {
|
|
36
|
+
if (typeof actions === 'function') {
|
|
37
|
+
return actions();
|
|
38
|
+
}
|
|
39
|
+
return actions ?? [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert the flat action list into ContextMenu options, inserting a divider
|
|
44
|
+
* between groups (first-seen group order). Ungrouped actions form a trailing
|
|
45
|
+
* group. A `checked` action shows a trailing check icon.
|
|
46
|
+
*/
|
|
47
|
+
function toContextMenuOptions(
|
|
48
|
+
actions: TableContextAction[],
|
|
49
|
+
): ContextMenuOption[] {
|
|
50
|
+
const order: string[] = [];
|
|
51
|
+
const buckets = new Map<string, TableContextAction[]>();
|
|
52
|
+
for (const action of actions) {
|
|
53
|
+
const key = action.group ?? '__ungrouped__';
|
|
54
|
+
let bucket = buckets.get(key);
|
|
55
|
+
if (!bucket) {
|
|
56
|
+
bucket = [];
|
|
57
|
+
buckets.set(key, bucket);
|
|
58
|
+
order.push(key);
|
|
59
|
+
}
|
|
60
|
+
bucket.push(action);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const options: ContextMenuOption[] = [];
|
|
64
|
+
order.forEach((key, groupIndex) => {
|
|
65
|
+
if (groupIndex > 0) {
|
|
66
|
+
options.push({type: 'divider'});
|
|
67
|
+
}
|
|
68
|
+
for (const action of buckets.get(key) ?? []) {
|
|
69
|
+
options.push({
|
|
70
|
+
label: typeof action.label === 'string' ? action.label : action.id,
|
|
71
|
+
// A checked action (e.g. the active sort direction) shows a checkmark;
|
|
72
|
+
// otherwise the action's own icon. ContextMenu's data form takes a
|
|
73
|
+
// single leading icon, so checked state replaces the icon.
|
|
74
|
+
icon: action.checked ? (
|
|
75
|
+
<Icon icon="check" size="xsm" aria-hidden />
|
|
76
|
+
) : (
|
|
77
|
+
action.icon
|
|
78
|
+
),
|
|
79
|
+
isDisabled: action.disabled,
|
|
80
|
+
onClick: action.onSelect,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return options;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Wrap a table element (a header cell's content or a row) in a right-click
|
|
89
|
+
* context menu rendering the aggregated `actions`. When no plugin contributes
|
|
90
|
+
* actions the element is returned untouched so the native browser menu passes
|
|
91
|
+
* through.
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* A context menu whose actions are resolved lazily — only when the user opens
|
|
95
|
+
* the menu (right-click). Deferring the work means plugins that pass a getter
|
|
96
|
+
* don't build an action array (with its closures) for every cell on every
|
|
97
|
+
* render; it's computed on demand and memoized until the menu closes.
|
|
98
|
+
*/
|
|
99
|
+
function LazyTableContextMenu({
|
|
100
|
+
element,
|
|
101
|
+
getActions,
|
|
102
|
+
}: {
|
|
103
|
+
element: ReactNode;
|
|
104
|
+
getActions: () => TableContextAction[];
|
|
105
|
+
}): ReactNode {
|
|
106
|
+
const [options, setOptions] = useState<ContextMenuOption[] | null>(null);
|
|
107
|
+
return (
|
|
108
|
+
<ContextMenu
|
|
109
|
+
items={options ?? []}
|
|
110
|
+
onOpenChange={open => {
|
|
111
|
+
// Resolve actions when opening; clear on close so state derived later
|
|
112
|
+
// (e.g. current sort direction) is always fresh next open.
|
|
113
|
+
setOptions(open ? toContextMenuOptions(getActions()) : null);
|
|
114
|
+
}}>
|
|
115
|
+
{element}
|
|
116
|
+
</ContextMenu>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Wrap a table element (a header cell's content or a body cell) in a right-click
|
|
122
|
+
* context menu rendering the aggregated `actions`. Accepts a static array or a
|
|
123
|
+
* getter (resolved lazily on open). When there are no actions the element is
|
|
124
|
+
* returned untouched so the native browser menu passes through.
|
|
125
|
+
*/
|
|
126
|
+
export function wrapInTableContextMenu(
|
|
127
|
+
element: ReactNode,
|
|
128
|
+
actions: TableContextActions | undefined,
|
|
129
|
+
): ReactNode {
|
|
130
|
+
// Getter form → resolve lazily on open.
|
|
131
|
+
if (typeof actions === 'function') {
|
|
132
|
+
return <LazyTableContextMenu element={element} getActions={actions} />;
|
|
133
|
+
}
|
|
134
|
+
if (!actions || actions.length === 0) {
|
|
135
|
+
return element;
|
|
136
|
+
}
|
|
137
|
+
// item (it looked like "ascending" was always selected). Focus moves only
|
|
138
|
+
// when the user arrow-keys into the menu.
|
|
139
|
+
return (
|
|
140
|
+
<ContextMenu items={toContextMenuOptions(actions)}>{element}</ContextMenu>
|
|
141
|
+
);
|
|
142
|
+
}
|