@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7
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 +65 -0
- package/README.md +30 -19
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +6 -2
- package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
- package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts +10 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -8
- package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
- package/dist/Avatar/AvatarSizeContext.js +1 -1
- package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
- package/dist/Avatar/AvatarStatusDot.js +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +2 -2
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
- package/dist/Banner/Banner.d.ts +9 -1
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +31 -2
- package/dist/Button/Button.d.ts +8 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +31 -2
- package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +30 -1
- package/dist/Card/Card.d.ts +8 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +28 -2
- package/dist/Carousel/Carousel.d.ts +6 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +55 -5
- package/dist/Chat/ChatComposer.d.ts +8 -0
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +31 -6
- package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +11 -1
- package/dist/Chat/ChatComposerInput.d.ts +20 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +19 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.d.ts +1 -1
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.d.ts +5 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +12 -3
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +10 -4
- package/dist/ClickableCard/ClickableCard.d.ts +9 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -0
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +8 -1
- package/dist/Collapsible/Collapsible.d.ts +11 -1
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +36 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/ContextMenu/index.d.ts +3 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +7 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +10 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +8 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
- package/dist/DropdownMenu/index.d.ts +5 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +10 -1
- package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemRoles.js +15 -0
- package/dist/FieldStatus/FieldStatus.d.ts +8 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +22 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +10 -2
- package/dist/HoverCard/HoverCard.d.ts +9 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/useHoverCard.d.ts +8 -0
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -2
- package/dist/Icon/Icon.d.ts +43 -13
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +62 -27
- package/dist/Layer/useLayer.d.ts +6 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +2 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +6 -1
- package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
- package/dist/MobileNav/MobileNavToggle.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +39 -12
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +16 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/Outline/Outline.d.ts +68 -3
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +128 -26
- package/dist/Outline/useScrollSpy.d.ts +27 -7
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +202 -70
- package/dist/OverflowList/OverflowList.d.ts +20 -3
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +29 -4
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts +9 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +14 -11
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +4 -2
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +16 -0
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +21 -8
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +15 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +2 -2
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +2 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +1 -1
- package/dist/Table/Table.d.ts +1 -1
- package/dist/Table/Table.js +1 -1
- package/dist/Table/index.d.ts +4 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
- package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/index.js +3 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +4 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
- package/dist/Table/plugins/tree/index.d.ts +5 -0
- package/dist/Table/plugins/tree/index.d.ts.map +1 -0
- package/dist/Table/plugins/tree/index.js +4 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +6 -3
- package/dist/Thumbnail/Thumbnail.d.ts +8 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +46 -8
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -4
- package/dist/Tokenizer/Tokenizer.d.ts +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +8 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +18 -8
- package/dist/TopNav/TopNavMegaMenu.js +10 -4
- package/dist/TopNav/TopNavMenu.d.ts +2 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +68 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +5 -14
- package/dist/astryx.css +51 -16
- package/dist/astryx.umd.js +56 -56
- package/dist/astryx.umd.js.map +4 -4
- package/dist/authoring/doc.d.ts.map +1 -1
- package/dist/hooks/computeOverflow.d.ts +47 -0
- package/dist/hooks/computeOverflow.d.ts.map +1 -0
- package/dist/hooks/computeOverflow.js +213 -0
- package/dist/hooks/useOverflow.d.ts +19 -0
- package/dist/hooks/useOverflow.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +37 -25
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +51 -0
- package/dist/theme/hct.d.ts +0 -1
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +0 -3
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/docs.mjs +32 -461
- package/locales/en.json +212 -172
- package/locales/fr-FR.json +10 -0
- package/locales/pseudo.json +30 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +93 -0
- package/src/AppShell/AppShell.tsx +6 -0
- package/src/AppShell/AppShellMobileContext.tsx +8 -0
- package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
- package/src/Avatar/Avatar.doc.mjs +6 -6
- package/src/Avatar/Avatar.tsx +16 -11
- package/src/Avatar/AvatarSizeContext.ts +1 -1
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
- package/src/AvatarGroup/AvatarGroup.tsx +3 -3
- package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
- package/src/Banner/Banner.doc.mjs +9 -0
- package/src/Banner/Banner.test.tsx +30 -0
- package/src/Banner/Banner.tsx +47 -3
- package/src/Button/Button.doc.mjs +9 -0
- package/src/Button/Button.test.tsx +49 -0
- package/src/Button/Button.tsx +25 -1
- package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
- package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
- package/src/ButtonGroup/ButtonGroup.tsx +32 -0
- package/src/Card/Card.doc.mjs +9 -0
- package/src/Card/Card.test.tsx +21 -3
- package/src/Card/Card.tsx +35 -2
- package/src/Carousel/Carousel.doc.mjs +1 -0
- package/src/Carousel/Carousel.test.tsx +149 -1
- package/src/Carousel/Carousel.tsx +62 -5
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposer.doc.mjs +9 -0
- package/src/Chat/ChatComposer.test.tsx +35 -0
- package/src/Chat/ChatComposer.tsx +58 -9
- package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
- package/src/Chat/ChatComposerDrawer.tsx +11 -1
- package/src/Chat/ChatComposerInput.doc.mjs +7 -0
- package/src/Chat/ChatComposerInput.test.tsx +87 -0
- package/src/Chat/ChatComposerInput.tsx +35 -3
- package/src/Chat/ChatLayout.tsx +3 -1
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessage.tsx +4 -2
- package/src/Chat/ChatMessageMetadata.tsx +1 -2
- package/src/Chat/ChatSendButton.tsx +5 -1
- package/src/Chat/ChatToolCalls.test.tsx +64 -0
- package/src/Chat/ChatToolCalls.tsx +13 -1
- package/src/Chat/useTriggerMenu.tsx +3 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
- package/src/CheckboxList/CheckboxList.test.tsx +9 -1
- package/src/Citation/Citation.test.tsx +51 -0
- package/src/Citation/Citation.tsx +8 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
- package/src/ClickableCard/ClickableCard.test.tsx +34 -0
- package/src/ClickableCard/ClickableCard.tsx +11 -1
- package/src/CodeBlock/CodeBlock.test.tsx +56 -0
- package/src/CodeBlock/CodeBlock.tsx +9 -1
- package/src/Collapsible/Collapsible.doc.mjs +7 -0
- package/src/Collapsible/Collapsible.test.tsx +80 -0
- package/src/Collapsible/Collapsible.tsx +58 -5
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
- package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
- package/src/ContextMenu/ContextMenu.test.tsx +75 -1
- package/src/ContextMenu/ContextMenu.tsx +10 -5
- package/src/ContextMenu/index.ts +15 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
- package/src/DateTimeInput/DateTimeInput.tsx +14 -2
- package/src/Dialog/Dialog.tsx +12 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
- package/src/DropdownMenu/DropdownMenu.tsx +11 -6
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
- package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
- package/src/DropdownMenu/index.ts +23 -3
- package/src/DropdownMenu/menuItemRoles.ts +22 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/Field/Field.test.tsx +57 -12
- package/src/Field/FieldLabel.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
- package/src/FieldStatus/FieldStatus.test.tsx +78 -40
- package/src/FieldStatus/FieldStatus.tsx +22 -4
- package/src/FileInput/FileInput.test.tsx +52 -3
- package/src/FileInput/FileInput.tsx +13 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -0
- package/src/HoverCard/HoverCard.test.tsx +21 -2
- package/src/HoverCard/HoverCard.tsx +11 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +24 -2
- package/src/Icon/Icon.doc.mjs +12 -0
- package/src/Icon/Icon.test.tsx +142 -0
- package/src/Icon/Icon.tsx +98 -30
- package/src/IconButton/IconButton.doc.mjs +8 -0
- package/src/IconButton/IconButton.test.tsx +15 -18
- package/src/Item/Item.doc.mjs +1 -1
- package/src/Layer/useLayer.tsx +8 -0
- package/src/MobileNav/MobileNav.tsx +12 -1
- package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
- package/src/MobileNav/MobileNavToggle.tsx +9 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
- package/src/MultiSelector/MultiSelector.test.tsx +162 -1
- package/src/MultiSelector/MultiSelector.tsx +55 -12
- package/src/MultiSelector/hooks.ts +17 -0
- package/src/NumberInput/NumberInput.doc.mjs +2 -2
- package/src/NumberInput/NumberInput.test.tsx +11 -7
- package/src/NumberInput/NumberInput.tsx +1 -2
- package/src/Outline/Outline.doc.mjs +123 -3
- package/src/Outline/Outline.test.tsx +735 -0
- package/src/Outline/Outline.tsx +183 -33
- package/src/Outline/useScrollSpy.ts +248 -72
- package/src/OverflowList/OverflowList.doc.mjs +39 -6
- package/src/OverflowList/OverflowList.test.tsx +111 -0
- package/src/OverflowList/OverflowList.tsx +48 -8
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/usePowerSearchSource.ts +1 -2
- package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
- package/src/SelectableCard/SelectableCard.test.tsx +46 -4
- package/src/SelectableCard/SelectableCard.tsx +27 -14
- package/src/Selector/Selector.test.tsx +69 -0
- package/src/Selector/Selector.tsx +5 -1
- package/src/Selector/SelectorOption.doc.mjs +1 -1
- package/src/Selector/hooks.ts +17 -0
- package/src/SideNav/SideNav.test.tsx +119 -3
- package/src/SideNav/SideNavCollapseButton.tsx +6 -1
- package/src/SideNav/SideNavHeading.tsx +24 -8
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +5 -1
- package/src/Slider/Slider.test.tsx +62 -2
- package/src/Slider/Slider.tsx +15 -1
- package/src/Switch/Switch.doc.mjs +1 -1
- package/src/Switch/Switch.test.tsx +32 -1
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.doc.mjs +3 -3
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/TabList.tsx +8 -0
- package/src/TabList/TabMenu.tsx +4 -1
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.tsx +1 -1
- package/src/Table/index.ts +12 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
- package/src/Table/plugins/rowStatus/index.ts +7 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
- package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
- package/src/Table/plugins/tree/index.ts +12 -0
- package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
- package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
- package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
- package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
- package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
- package/src/Table/useBaseTablePlugins.ts +6 -2
- package/src/Table/useTableRowStatus.doc.mjs +30 -0
- package/src/Table/useTableSelection.doc.mjs +65 -14
- package/src/Table/useTableTreeData.doc.mjs +75 -0
- package/src/Table/useTableTreeState.doc.mjs +98 -0
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
- package/src/Thumbnail/Thumbnail.test.tsx +38 -8
- package/src/Thumbnail/Thumbnail.tsx +28 -1
- package/src/TimeInput/TimeInput.test.tsx +76 -1
- package/src/TimeInput/TimeInput.tsx +19 -6
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Tokenizer/Tokenizer.test.tsx +137 -2
- package/src/Tokenizer/Tokenizer.tsx +16 -4
- package/src/Toolbar/Toolbar.tsx +10 -3
- package/src/TopNav/TopNav.test.tsx +80 -1
- package/src/TopNav/TopNavHeading.tsx +24 -8
- package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
- package/src/TopNav/TopNavMegaMenu.tsx +10 -4
- package/src/TopNav/TopNavMenu.test.tsx +118 -4
- package/src/TopNav/TopNavMenu.tsx +83 -5
- package/src/TreeList/TreeList.test.tsx +32 -1
- package/src/TreeList/TreeListItem.tsx +19 -10
- package/src/authoring/doc.ts +1 -3
- package/src/docs-script.test.mjs +18 -13
- package/src/hooks/computeOverflow.test.ts +422 -0
- package/src/hooks/computeOverflow.ts +290 -0
- package/src/hooks/useOverflow.test.ts +96 -4
- package/src/hooks/useOverflow.ts +77 -33
- package/src/postinstall.test.mjs +71 -0
- package/src/theme/Theme.doc.mjs +4 -4
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +1 -1
- package/src/theme/generateThemeRules.test.ts +65 -0
- package/src/theme/generateThemeRules.ts +56 -0
- package/src/theme/hct.ts +0 -4
- package/src/utils/types.ts +14 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
- package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
- package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
|
@@ -10,11 +10,26 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Drives the active outline item from scroll position. On each scroll
|
|
12
12
|
* (rAF-throttled) it reads live heading positions and marks the last heading
|
|
13
|
-
* whose top has passed its activation line
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* whose top has passed its activation line. This is stable — it never compares
|
|
14
|
+
* stale cached positions — so the indicator moves monotonically instead of
|
|
15
|
+
* jumping. Defaults to the first item at the top and the last item at the
|
|
16
|
+
* bottom so short final sections still activate.
|
|
17
|
+
*
|
|
18
|
+
* The activation line and the scroll landing are the same number, computed once
|
|
19
|
+
* in `getRestingTop`: scroll-root top + `offset` (a fixed header overlaying the
|
|
20
|
+
* root) + the heading's own `scroll-margin-top`. A heading therefore activates
|
|
21
|
+
* exactly where navigating to it puts it. `offset` and `scroll-margin-top`
|
|
22
|
+
* compose — the header, then the breathing room below it — they do not
|
|
23
|
+
* duplicate each other.
|
|
24
|
+
*
|
|
25
|
+
* It also owns the single navigation path (`scrollTo`), shared by click and
|
|
26
|
+
* keyboard activation: it announces the jump, suppresses scroll-spy for the
|
|
27
|
+
* duration of the programmatic scroll (so the indicator doesn't chase it), and
|
|
28
|
+
* resolves when the scroll settles — via `scrollend` where supported, or a
|
|
29
|
+
* timeout fallback where it is not.
|
|
30
|
+
*
|
|
31
|
+
* The scroll root is the `scrollContainerRef` element when provided, otherwise
|
|
32
|
+
* the nearest scrollable ancestor of the outline, otherwise the viewport.
|
|
18
33
|
*
|
|
19
34
|
* SYNC: When modified, update /packages/core/src/Outline/Outline.tsx
|
|
20
35
|
*/
|
|
@@ -22,6 +37,14 @@
|
|
|
22
37
|
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
23
38
|
import type {OutlineItem} from './types';
|
|
24
39
|
|
|
40
|
+
/**
|
|
41
|
+
* How long to wait for a programmatic smooth scroll to settle when the
|
|
42
|
+
* `scrollend` event never arrives — either because the browser does not
|
|
43
|
+
* support it (Safari at time of writing) or because the target was already in
|
|
44
|
+
* position, so no scroll happened at all and no events were emitted.
|
|
45
|
+
*/
|
|
46
|
+
const SCROLL_SETTLE_TIMEOUT_MS = 1200;
|
|
47
|
+
|
|
25
48
|
/** Keys that scroll the viewport — used to detect a manual scroll intent. */
|
|
26
49
|
const SCROLL_KEYS = new Set([
|
|
27
50
|
'ArrowUp',
|
|
@@ -58,26 +81,82 @@ function getScrollableAncestor(
|
|
|
58
81
|
return null;
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
/** The element's own `scroll-margin-top` in px (0 when unset). */
|
|
85
|
+
function getScrollMarginTop(element: HTMLElement): number {
|
|
86
|
+
return (
|
|
87
|
+
Number.parseFloat(window.getComputedStyle(element).scrollMarginTop) || 0
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The top of the scroll root in viewport coordinates (0 for the viewport). */
|
|
92
|
+
function getRootTop(scrollRoot: HTMLElement | null): number {
|
|
93
|
+
return scrollRoot != null ? scrollRoot.getBoundingClientRect().top : 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Where a heading comes to rest, in viewport coordinates: below the fixed
|
|
98
|
+
* header (`offset`) and below its own `scroll-margin-top` breathing room.
|
|
99
|
+
*
|
|
100
|
+
* This is the single source of truth shared by the activation line and the
|
|
101
|
+
* scroll landing — the two must agree, or a heading activates somewhere other
|
|
102
|
+
* than where navigating to it puts it.
|
|
103
|
+
*/
|
|
104
|
+
function getRestingTop(
|
|
105
|
+
target: HTMLElement,
|
|
106
|
+
scrollRoot: HTMLElement | null,
|
|
107
|
+
offset: number,
|
|
108
|
+
): number {
|
|
109
|
+
return getRootTop(scrollRoot) + offset + getScrollMarginTop(target);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Bring `target` to rest at the top of the scroll root, below any fixed header.
|
|
114
|
+
*
|
|
115
|
+
* With no `offset`, this is the CSS-native path: `scrollIntoView` already
|
|
116
|
+
* honors the heading's `scroll-margin-top`, and it walks *every* scrollable
|
|
117
|
+
* ancestor, so the browser does it better than we can.
|
|
118
|
+
*
|
|
119
|
+
* An `offset` describes a fixed header overlaying the top of the scroll root —
|
|
120
|
+
* which the browser cannot know about, so `scrollIntoView` would park the
|
|
121
|
+
* heading underneath it, hidden. Compute the landing explicitly instead, from
|
|
122
|
+
* the same {@link getRestingTop} the activation line uses.
|
|
123
|
+
*/
|
|
124
|
+
function scrollToTarget(
|
|
125
|
+
target: HTMLElement,
|
|
126
|
+
scrollRoot: HTMLElement | null,
|
|
127
|
+
scrollTarget: HTMLElement | Window,
|
|
128
|
+
offset: number,
|
|
129
|
+
): void {
|
|
130
|
+
if (offset === 0) {
|
|
131
|
+
target.scrollIntoView({behavior: 'smooth', block: 'start'});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const delta =
|
|
136
|
+
target.getBoundingClientRect().top -
|
|
137
|
+
getRestingTop(target, scrollRoot, offset);
|
|
138
|
+
scrollTarget.scrollBy({top: delta, behavior: 'smooth'});
|
|
139
|
+
}
|
|
140
|
+
|
|
61
141
|
/**
|
|
62
142
|
* Resolve the active heading id from current scroll position.
|
|
63
143
|
*
|
|
64
|
-
* A heading is "passed" once its top reaches its activation line —
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
144
|
+
* A heading is "passed" once its top reaches its activation line — exactly
|
|
145
|
+
* where navigating to it would land it ({@link getRestingTop}): the scroll
|
|
146
|
+
* root's top, plus `offset` (a fixed header overlaying the scroll root), plus
|
|
147
|
+
* the heading's own scroll-margin-top. The active heading is the last passed
|
|
148
|
+
* one (headings are in document order). When none have passed (scrolled above
|
|
149
|
+
* the first), the first item is active; at the bottom, the last item is active.
|
|
69
150
|
*/
|
|
70
151
|
function resolveActiveId(
|
|
71
152
|
items: OutlineItem[],
|
|
72
153
|
scrollRoot: HTMLElement | null,
|
|
154
|
+
offset: number,
|
|
73
155
|
): string | undefined {
|
|
74
156
|
if (items.length === 0) {
|
|
75
157
|
return undefined;
|
|
76
158
|
}
|
|
77
159
|
|
|
78
|
-
const rootTop =
|
|
79
|
-
scrollRoot != null ? scrollRoot.getBoundingClientRect().top : 0;
|
|
80
|
-
|
|
81
160
|
const atBottom =
|
|
82
161
|
scrollRoot != null
|
|
83
162
|
? scrollRoot.scrollTop + scrollRoot.clientHeight >=
|
|
@@ -94,11 +173,10 @@ function resolveActiveId(
|
|
|
94
173
|
if (element == null) {
|
|
95
174
|
continue;
|
|
96
175
|
}
|
|
97
|
-
const top = element.getBoundingClientRect().top;
|
|
98
|
-
const marginTop =
|
|
99
|
-
Number.parseFloat(window.getComputedStyle(element).scrollMarginTop) || 0;
|
|
100
176
|
|
|
101
|
-
|
|
177
|
+
// 1px of tolerance absorbs sub-pixel rounding after a scroll lands.
|
|
178
|
+
const top = element.getBoundingClientRect().top;
|
|
179
|
+
if (top <= getRestingTop(element, scrollRoot, offset) + 1) {
|
|
102
180
|
activeId = item.id;
|
|
103
181
|
} else {
|
|
104
182
|
break;
|
|
@@ -112,6 +190,20 @@ interface UseScrollSpyOptions {
|
|
|
112
190
|
items: OutlineItem[];
|
|
113
191
|
onActiveIdChange?: (id: string) => void;
|
|
114
192
|
rootRef: React.RefObject<HTMLElement | null>;
|
|
193
|
+
/**
|
|
194
|
+
* Height in px of a fixed header overlaying the top of the scroll root.
|
|
195
|
+
* Shifts both the activation line and the scroll landing by the same amount,
|
|
196
|
+
* on top of each heading's own `scroll-margin-top`.
|
|
197
|
+
*/
|
|
198
|
+
offset?: number;
|
|
199
|
+
/** Scroll container to track, instead of the nearest scrollable ancestor. */
|
|
200
|
+
scrollContainerRef?: React.RefObject<HTMLElement | null>;
|
|
201
|
+
/** Whether {@link UseScrollSpyResult.scrollTo} performs the smooth scroll. */
|
|
202
|
+
hasScrollOnClick?: boolean;
|
|
203
|
+
/** Called when a navigation begins, before the scroll starts. */
|
|
204
|
+
onNavigateStart?: (id: string) => void;
|
|
205
|
+
/** Called once per navigation, when the scroll settles or is interrupted. */
|
|
206
|
+
onNavigateEnd?: (id: string) => void;
|
|
115
207
|
}
|
|
116
208
|
|
|
117
209
|
interface UseScrollSpyResult {
|
|
@@ -119,13 +211,19 @@ interface UseScrollSpyResult {
|
|
|
119
211
|
/** Set the active id (notifies onActiveIdChange). For controlled consumers. */
|
|
120
212
|
setActiveId: (id: string) => void;
|
|
121
213
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* scroll
|
|
214
|
+
* Navigate to the item with id `id` — the single path shared by click and
|
|
215
|
+
* keyboard activation.
|
|
216
|
+
*
|
|
217
|
+
* Fires `onNavigateStart(id)`, scrolls (unless `hasScrollOnClick` is false),
|
|
218
|
+
* and fires `onNavigateEnd(id)` exactly once when the scroll settles or the
|
|
219
|
+
* user interrupts it. While uncontrolled, scroll-spy is suppressed for the
|
|
220
|
+
* duration so the indicator doesn't chase the scroll through intervening
|
|
221
|
+
* sections; it lands on the target once the scroll settles, or resumes
|
|
222
|
+
* position tracking if the user scrolls away mid-flight.
|
|
223
|
+
*
|
|
224
|
+
* Returns false (and does nothing) when no element with `id` exists.
|
|
127
225
|
*/
|
|
128
|
-
|
|
226
|
+
scrollTo: (id: string) => boolean;
|
|
129
227
|
}
|
|
130
228
|
|
|
131
229
|
export function useScrollSpy({
|
|
@@ -133,18 +231,29 @@ export function useScrollSpy({
|
|
|
133
231
|
items,
|
|
134
232
|
onActiveIdChange,
|
|
135
233
|
rootRef,
|
|
234
|
+
offset = 0,
|
|
235
|
+
scrollContainerRef,
|
|
236
|
+
hasScrollOnClick = true,
|
|
237
|
+
onNavigateStart,
|
|
238
|
+
onNavigateEnd,
|
|
136
239
|
}: UseScrollSpyOptions): UseScrollSpyResult {
|
|
137
240
|
const isControlled = activeId !== undefined;
|
|
138
241
|
const [uncontrolledActiveId, setUncontrolledActiveId] = useState<
|
|
139
242
|
string | undefined
|
|
140
243
|
>(items[0]?.id);
|
|
141
244
|
const activeIdRef = useRef<string | undefined>(activeId);
|
|
142
|
-
// While true, scroll-spy ignores scroll updates because a
|
|
143
|
-
// programmatic scroll. Released when
|
|
245
|
+
// While true, scroll-spy ignores scroll updates because a navigation is
|
|
246
|
+
// driving a programmatic scroll. Released when it settles or the user scrolls.
|
|
144
247
|
const suppressRef = useRef(false);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
248
|
+
// The navigation currently in flight, if any.
|
|
249
|
+
const navigationRef = useRef<{
|
|
250
|
+
/** End it (without resuming tracking) because a new one is starting. */
|
|
251
|
+
supersede: () => void;
|
|
252
|
+
/** Drop its listeners without firing onNavigateEnd (unmount). */
|
|
253
|
+
teardown: () => void;
|
|
254
|
+
} | null>(null);
|
|
255
|
+
// Latest scroll-position resolver, so a navigation can resume tracking when
|
|
256
|
+
// the user scrolls during the programmatic scroll.
|
|
148
257
|
const syncRef = useRef<(() => void) | null>(null);
|
|
149
258
|
// Keep latest items/callback in refs so the scroll listener effect doesn't
|
|
150
259
|
// re-subscribe on every render (items is a fresh array each render).
|
|
@@ -155,12 +264,23 @@ export function useScrollSpy({
|
|
|
155
264
|
const itemIds = items.map(item => item.id).join('\n');
|
|
156
265
|
activeIdRef.current = isControlled ? activeId : uncontrolledActiveId;
|
|
157
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The element whose scroll position drives the outline: the explicit
|
|
269
|
+
* container when scoped, else the nearest scrollable ancestor, else null
|
|
270
|
+
* (meaning the viewport).
|
|
271
|
+
*/
|
|
272
|
+
const getScrollRoot = useCallback((): HTMLElement | null => {
|
|
273
|
+
return (
|
|
274
|
+
scrollContainerRef?.current ?? getScrollableAncestor(rootRef.current)
|
|
275
|
+
);
|
|
276
|
+
}, [rootRef, scrollContainerRef]);
|
|
277
|
+
|
|
158
278
|
useEffect(() => {
|
|
159
279
|
if (isControlled || typeof window === 'undefined') {
|
|
160
280
|
return;
|
|
161
281
|
}
|
|
162
282
|
|
|
163
|
-
const scrollRoot =
|
|
283
|
+
const scrollRoot = getScrollRoot();
|
|
164
284
|
const scrollTarget: HTMLElement | Window = scrollRoot ?? window;
|
|
165
285
|
|
|
166
286
|
let frame = 0;
|
|
@@ -169,7 +289,11 @@ export function useScrollSpy({
|
|
|
169
289
|
if (suppressRef.current) {
|
|
170
290
|
return;
|
|
171
291
|
}
|
|
172
|
-
const nextActiveId = resolveActiveId(
|
|
292
|
+
const nextActiveId = resolveActiveId(
|
|
293
|
+
itemsRef.current,
|
|
294
|
+
scrollRoot,
|
|
295
|
+
offset,
|
|
296
|
+
);
|
|
173
297
|
if (nextActiveId != null && nextActiveId !== activeIdRef.current) {
|
|
174
298
|
activeIdRef.current = nextActiveId;
|
|
175
299
|
setUncontrolledActiveId(nextActiveId);
|
|
@@ -195,12 +319,12 @@ export function useScrollSpy({
|
|
|
195
319
|
cancelAnimationFrame(frame);
|
|
196
320
|
}
|
|
197
321
|
};
|
|
198
|
-
}, [isControlled, itemIds,
|
|
322
|
+
}, [isControlled, itemIds, offset, getScrollRoot]);
|
|
199
323
|
|
|
200
|
-
// Tear down any
|
|
324
|
+
// Tear down any in-flight navigation's listeners when the Outline unmounts.
|
|
201
325
|
useEffect(() => {
|
|
202
326
|
return () => {
|
|
203
|
-
|
|
327
|
+
navigationRef.current?.teardown();
|
|
204
328
|
};
|
|
205
329
|
}, []);
|
|
206
330
|
|
|
@@ -211,65 +335,117 @@ export function useScrollSpy({
|
|
|
211
335
|
onActiveIdChange?.(nextActiveId);
|
|
212
336
|
};
|
|
213
337
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
338
|
+
// Defined in render (not memoized) so it always closes over the current props
|
|
339
|
+
// — it only ever runs from an event handler, never during render.
|
|
340
|
+
const scrollTo = (id: string): boolean => {
|
|
341
|
+
const target =
|
|
342
|
+
typeof document !== 'undefined' ? document.getElementById(id) : null;
|
|
343
|
+
if (target == null) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// A second navigation replaces the first: end the old one (balancing its
|
|
348
|
+
// onNavigateStart) without resuming tracking, since we are about to
|
|
349
|
+
// suppress it again anyway.
|
|
350
|
+
navigationRef.current?.supersede();
|
|
351
|
+
|
|
352
|
+
onNavigateStart?.(id);
|
|
353
|
+
|
|
354
|
+
if (isControlled) {
|
|
355
|
+
// The consumer owns the active state — notify only.
|
|
356
|
+
setActiveId(id);
|
|
357
|
+
} else {
|
|
358
|
+
// Freeze the indicator during the programmatic scroll instead of moving
|
|
359
|
+
// it immediately: it lands on the target once the scroll settles, so it
|
|
360
|
+
// doesn't chase the scroll through intervening sections.
|
|
361
|
+
suppressRef.current = true;
|
|
220
362
|
}
|
|
221
363
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// settles, so it doesn't chase the scroll through intervening sections.
|
|
225
|
-
suppressRef.current = true;
|
|
226
|
-
// Replace any in-flight handlers from a previous click.
|
|
227
|
-
releaseSuppressionRef.current?.();
|
|
364
|
+
const scrollRoot = getScrollRoot();
|
|
365
|
+
const scrollTarget: HTMLElement | Window = scrollRoot ?? window;
|
|
228
366
|
|
|
229
367
|
let settleTimer = 0;
|
|
368
|
+
let isSettled = false;
|
|
369
|
+
|
|
230
370
|
const cleanup = () => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
371
|
+
scrollTarget.removeEventListener('scrollend', onSettle);
|
|
372
|
+
scrollTarget.removeEventListener('wheel', onManual);
|
|
373
|
+
scrollTarget.removeEventListener('touchmove', onManual);
|
|
234
374
|
window.removeEventListener('keydown', onKeyDown);
|
|
235
375
|
if (settleTimer !== 0) {
|
|
236
376
|
clearTimeout(settleTimer);
|
|
237
377
|
settleTimer = 0;
|
|
238
378
|
}
|
|
239
|
-
|
|
379
|
+
navigationRef.current = null;
|
|
240
380
|
};
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* End the navigation exactly once. `didArrive` is false when the user took
|
|
384
|
+
* over with a manual scroll mid-flight. `onNavigateEnd` fires either way,
|
|
385
|
+
* so every `onNavigateStart` is balanced and a consumer's "navigating"
|
|
386
|
+
* state can never leak.
|
|
387
|
+
*/
|
|
388
|
+
const finish = (didArrive: boolean, shouldResume = true) => {
|
|
389
|
+
if (isSettled) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
isSettled = true;
|
|
251
393
|
cleanup();
|
|
252
|
-
|
|
253
|
-
|
|
394
|
+
|
|
395
|
+
if (!isControlled) {
|
|
396
|
+
suppressRef.current = false;
|
|
397
|
+
if (didArrive) {
|
|
398
|
+
setUncontrolledActiveId(id);
|
|
399
|
+
activeIdRef.current = id;
|
|
400
|
+
onActiveIdChangeRef.current?.(id);
|
|
401
|
+
} else if (shouldResume) {
|
|
402
|
+
// Hand control back to scroll-position tracking.
|
|
403
|
+
syncRef.current?.();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
onNavigateEnd?.(id);
|
|
254
408
|
};
|
|
409
|
+
|
|
410
|
+
const onSettle = () => finish(true);
|
|
411
|
+
const onManual = () => finish(false);
|
|
255
412
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
256
|
-
|
|
257
|
-
|
|
413
|
+
// A key the outline itself consumed (arrow roving focus, Space
|
|
414
|
+
// activation) is prevented, so the browser will not scroll — it is not a
|
|
415
|
+
// manual scroll intent and must not cancel the navigation it just began.
|
|
416
|
+
if (!event.defaultPrevented && SCROLL_KEYS.has(event.key)) {
|
|
417
|
+
finish(false);
|
|
258
418
|
}
|
|
259
419
|
};
|
|
260
420
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
421
|
+
if (hasScrollOnClick) {
|
|
422
|
+
// Arm settle detection *before* scrolling so an instant jump (a target
|
|
423
|
+
// already in position, or prefers-reduced-motion collapsing the smooth
|
|
424
|
+
// scroll) cannot land before anyone is listening.
|
|
425
|
+
scrollTarget.addEventListener('scrollend', onSettle, {once: true});
|
|
426
|
+
scrollTarget.addEventListener('wheel', onManual, {passive: true});
|
|
427
|
+
scrollTarget.addEventListener('touchmove', onManual, {passive: true});
|
|
428
|
+
window.addEventListener('keydown', onKeyDown);
|
|
429
|
+
settleTimer = window.setTimeout(onSettle, SCROLL_SETTLE_TIMEOUT_MS);
|
|
430
|
+
navigationRef.current = {
|
|
431
|
+
supersede: () => finish(false, false),
|
|
432
|
+
teardown: cleanup,
|
|
433
|
+
};
|
|
434
|
+
scrollToTarget(target, scrollRoot, scrollTarget, offset);
|
|
435
|
+
} else {
|
|
436
|
+
// The consumer owns scrolling — there is no scroll to wait for, so the
|
|
437
|
+
// navigation is already at rest. Resolving immediately (instead of
|
|
438
|
+
// waiting on the settle timeout) keeps onNavigateEnd usable for arrival
|
|
439
|
+
// effects even when hasScrollOnClick is false.
|
|
440
|
+
finish(true);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return true;
|
|
444
|
+
};
|
|
269
445
|
|
|
270
446
|
return {
|
|
271
447
|
activeId: isControlled ? activeId : uncontrolledActiveId,
|
|
272
448
|
setActiveId,
|
|
273
|
-
|
|
449
|
+
scrollTo,
|
|
274
450
|
};
|
|
275
451
|
}
|
|
@@ -61,6 +61,20 @@ export const docs = {
|
|
|
61
61
|
description: 'Minimum number of items to always show, even when overflowing.',
|
|
62
62
|
default: '0',
|
|
63
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: 'maxVisibleItems',
|
|
66
|
+
type: 'number',
|
|
67
|
+
description:
|
|
68
|
+
'Maximum number of items to ever show, even when they all fit. The ceiling partner to minVisibleItems; extra items collapse into the overflow indicator. If less than minVisibleItems, the floor wins.',
|
|
69
|
+
default: 'undefined (no cap)',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'maxRows',
|
|
73
|
+
type: 'number',
|
|
74
|
+
description:
|
|
75
|
+
'Wrap items across up to this many rows before collapsing the rest into the overflow indicator. A number, not a boolean. Leave undefined (or set 1) for single-line behavior. Assumes uniform row height.',
|
|
76
|
+
default: 'undefined (single line)',
|
|
77
|
+
},
|
|
64
78
|
{
|
|
65
79
|
name: 'collapseFrom',
|
|
66
80
|
type: "'start' | 'end'",
|
|
@@ -91,8 +105,9 @@ export const docs = {
|
|
|
91
105
|
'A horizontal list that automatically hides items when they exceed the available width. Use OverflowList for breadcrumbs, toolbars, tag lists, or any row that needs to collapse gracefully at smaller sizes.',
|
|
92
106
|
bestPractices: [
|
|
93
107
|
{ guidance: true, description: 'Provide a meaningful overflowRenderer: a "+N more" badge, a dropdown, or a count indicator.' },
|
|
94
|
-
{ guidance: true, description: 'Set minVisibleItems to keep key items visible regardless of
|
|
95
|
-
{ guidance:
|
|
108
|
+
{ guidance: true, description: 'Set minVisibleItems to keep key items visible, and maxVisibleItems to cap the row at a fixed count regardless of available width.' },
|
|
109
|
+
{ guidance: true, description: 'Use maxRows to let items wrap onto a bounded number of rows (e.g. a two-row tag cloud) before collapsing the rest into the indicator.' },
|
|
110
|
+
{ guidance: false, description: 'Use OverflowList for a vertical stack; horizontal multi-row wrap is supported via maxRows, but items still flow left-to-right, not top-to-bottom.' },
|
|
96
111
|
],
|
|
97
112
|
},
|
|
98
113
|
};
|
|
@@ -127,6 +142,20 @@ export const docsZh = {
|
|
|
127
142
|
description: '溢出时始终显示的最小项目数。',
|
|
128
143
|
default: '0',
|
|
129
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: 'maxVisibleItems',
|
|
147
|
+
type: 'number',
|
|
148
|
+
description:
|
|
149
|
+
'始终显示的最大项目数(即使全部都能放下)。它是 minVisibleItems 的上限伙伴;多余项目会折叠进溢出指示器。若小于 minVisibleItems,则以下限为准。',
|
|
150
|
+
default: 'undefined(无上限)',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'maxRows',
|
|
154
|
+
type: 'number',
|
|
155
|
+
description:
|
|
156
|
+
'在将其余项目折叠进溢出指示器之前,允许项目换行到的最大行数。是数字而非布尔值。留空(或设为 1)表示单行。假定行高一致。',
|
|
157
|
+
default: 'undefined(单行)',
|
|
158
|
+
},
|
|
130
159
|
{
|
|
131
160
|
name: 'collapseFrom',
|
|
132
161
|
type: "'start' | 'end'",
|
|
@@ -152,8 +181,9 @@ export const docsZh = {
|
|
|
152
181
|
'A horizontal list that automatically hides items when they exceed the available width. Use OverflowList for breadcrumbs, toolbars, tag lists, or any row that needs to collapse gracefully at smaller sizes.',
|
|
153
182
|
bestPractices: [
|
|
154
183
|
{ guidance: true, description: 'Provide a meaningful overflowRenderer: a "+N more" badge, a dropdown, or a count indicator.' },
|
|
155
|
-
{ guidance: true, description: 'Set minVisibleItems to keep key items visible regardless of
|
|
156
|
-
{ guidance:
|
|
184
|
+
{ guidance: true, description: 'Set minVisibleItems to keep key items visible, and maxVisibleItems to cap the row at a fixed count regardless of available width.' },
|
|
185
|
+
{ guidance: true, description: 'Use maxRows to let items wrap onto a bounded number of rows (e.g. a two-row tag cloud) before collapsing the rest into the indicator.' },
|
|
186
|
+
{ guidance: false, description: 'Use OverflowList for a vertical stack; horizontal multi-row wrap is supported via maxRows, but items still flow left-to-right, not top-to-bottom.' },
|
|
157
187
|
],
|
|
158
188
|
},
|
|
159
189
|
};
|
|
@@ -166,8 +196,9 @@ export const docsDense = {
|
|
|
166
196
|
'A horizontal list that automatically hides items when they exceed the available width. Use OverflowList for breadcrumbs, toolbars, tag lists, or any row that needs to collapse gracefully at smaller sizes.',
|
|
167
197
|
bestPractices: [
|
|
168
198
|
{ guidance: true, description: 'Provide a meaningful overflowRenderer: a "+N more" badge, a dropdown, or a count indicator.' },
|
|
169
|
-
{ guidance: true, description: 'Set minVisibleItems to keep key items visible regardless of
|
|
170
|
-
{ guidance:
|
|
199
|
+
{ guidance: true, description: 'Set minVisibleItems to keep key items visible, and maxVisibleItems to cap the row at a fixed count regardless of available width.' },
|
|
200
|
+
{ guidance: true, description: 'Use maxRows to let items wrap onto a bounded number of rows (e.g. a two-row tag cloud) before collapsing the rest into the indicator.' },
|
|
201
|
+
{ guidance: false, description: 'Use OverflowList for a vertical stack; horizontal multi-row wrap is supported via maxRows, but items still flow left-to-right, not top-to-bottom.' },
|
|
171
202
|
],
|
|
172
203
|
},
|
|
173
204
|
propDescriptions: {
|
|
@@ -175,6 +206,8 @@ export const docsDense = {
|
|
|
175
206
|
overflowRenderer: 'renders overflow indicator, receives hidden items w/ index',
|
|
176
207
|
gap: 'item gap as spacing step',
|
|
177
208
|
minVisibleItems: 'min items always shown even when overflowing',
|
|
209
|
+
maxVisibleItems: 'max items ever shown (cap); extra items collapse to indicator; min wins if smaller',
|
|
210
|
+
maxRows: 'wrap items across up to N rows then collapse rest (number, not boolean); undefined = single line',
|
|
178
211
|
collapseFrom: 'which end to collapse from',
|
|
179
212
|
behavior: 'observeSelf (default) or observeParent for content-sized containers',
|
|
180
213
|
xstyle: 'StyleX styles, use stylex.create() values only',
|
|
@@ -413,4 +413,115 @@ describe('OverflowList', () => {
|
|
|
413
413
|
expect(OverflowList.displayName).toBe('OverflowList');
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
|
+
|
|
417
|
+
describe('maxVisibleItems (cap)', () => {
|
|
418
|
+
it('caps visible items even when they all fit', () => {
|
|
419
|
+
render(
|
|
420
|
+
<OverflowList
|
|
421
|
+
gap={0}
|
|
422
|
+
data-w="1000"
|
|
423
|
+
data-testid="ov"
|
|
424
|
+
maxVisibleItems={2}
|
|
425
|
+
overflowRenderer={indicator('more:')}>
|
|
426
|
+
<button type="button" data-w="40">
|
|
427
|
+
A
|
|
428
|
+
</button>
|
|
429
|
+
<button type="button" data-w="40">
|
|
430
|
+
B
|
|
431
|
+
</button>
|
|
432
|
+
<button type="button" data-w="40">
|
|
433
|
+
C
|
|
434
|
+
</button>
|
|
435
|
+
<button type="button" data-w="40">
|
|
436
|
+
D
|
|
437
|
+
</button>
|
|
438
|
+
</OverflowList>,
|
|
439
|
+
);
|
|
440
|
+
const vis = visibleContainer();
|
|
441
|
+
expect(within(vis).getByText('A')).toBeInTheDocument();
|
|
442
|
+
expect(within(vis).getByText('B')).toBeInTheDocument();
|
|
443
|
+
expect(within(vis).queryByText('C')).not.toBeInTheDocument();
|
|
444
|
+
expect(within(vis).getByText('more:2,3')).toBeInTheDocument();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('min wins over a smaller cap (D1)', () => {
|
|
448
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
449
|
+
render(
|
|
450
|
+
<OverflowList
|
|
451
|
+
gap={0}
|
|
452
|
+
data-w="1000"
|
|
453
|
+
data-testid="ov"
|
|
454
|
+
minVisibleItems={3}
|
|
455
|
+
maxVisibleItems={1}
|
|
456
|
+
overflowRenderer={indicator('more:')}>
|
|
457
|
+
<button type="button" data-w="40">
|
|
458
|
+
A
|
|
459
|
+
</button>
|
|
460
|
+
<button type="button" data-w="40">
|
|
461
|
+
B
|
|
462
|
+
</button>
|
|
463
|
+
<button type="button" data-w="40">
|
|
464
|
+
C
|
|
465
|
+
</button>
|
|
466
|
+
<button type="button" data-w="40">
|
|
467
|
+
D
|
|
468
|
+
</button>
|
|
469
|
+
</OverflowList>,
|
|
470
|
+
);
|
|
471
|
+
const vis = visibleContainer();
|
|
472
|
+
expect(within(vis).getByText('A')).toBeInTheDocument();
|
|
473
|
+
expect(within(vis).getByText('B')).toBeInTheDocument();
|
|
474
|
+
expect(within(vis).getByText('C')).toBeInTheDocument();
|
|
475
|
+
expect(within(vis).queryByText('D')).not.toBeInTheDocument();
|
|
476
|
+
expect(warn).toHaveBeenCalled();
|
|
477
|
+
warn.mockRestore();
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('maxRows (multi-row)', () => {
|
|
482
|
+
it('applies a different container class when maxRows enables wrapping', () => {
|
|
483
|
+
const {rerender} = render(
|
|
484
|
+
<OverflowList gap={0} data-w="1000" data-testid="ov">
|
|
485
|
+
<button type="button" data-w="40">
|
|
486
|
+
A
|
|
487
|
+
</button>
|
|
488
|
+
</OverflowList>,
|
|
489
|
+
);
|
|
490
|
+
const singleLine = visibleContainer().getAttribute('class');
|
|
491
|
+
|
|
492
|
+
rerender(
|
|
493
|
+
<OverflowList gap={0} data-w="1000" data-testid="ov" maxRows={2}>
|
|
494
|
+
<button type="button" data-w="40">
|
|
495
|
+
A
|
|
496
|
+
</button>
|
|
497
|
+
</OverflowList>,
|
|
498
|
+
);
|
|
499
|
+
const multiRow = visibleContainer().getAttribute('class');
|
|
500
|
+
expect(multiRow).not.toEqual(singleLine);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('keeps single-line behavior with maxRows={1}', () => {
|
|
504
|
+
render(
|
|
505
|
+
<OverflowList
|
|
506
|
+
gap={0}
|
|
507
|
+
data-w="100"
|
|
508
|
+
data-testid="ov"
|
|
509
|
+
maxRows={1}
|
|
510
|
+
overflowRenderer={indicator('more:')}>
|
|
511
|
+
<button type="button" data-w="40">
|
|
512
|
+
A
|
|
513
|
+
</button>
|
|
514
|
+
<button type="button" data-w="40">
|
|
515
|
+
B
|
|
516
|
+
</button>
|
|
517
|
+
<button type="button" data-w="40">
|
|
518
|
+
C
|
|
519
|
+
</button>
|
|
520
|
+
</OverflowList>,
|
|
521
|
+
);
|
|
522
|
+
const vis = visibleContainer();
|
|
523
|
+
expect(within(vis).getByText('A')).toBeInTheDocument();
|
|
524
|
+
expect(within(vis).queryByText('B')).not.toBeInTheDocument();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
416
527
|
});
|