@astryxdesign/core 0.0.15 → 0.1.0-canary.08d4cf4
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 +69 -0
- package/README.md +8 -10
- package/dist/AlertDialog/AlertDialog.js +2 -2
- package/dist/AppShell/AppShell.js +5 -5
- package/dist/AspectRatio/AspectRatio.js +2 -2
- package/dist/Avatar/Avatar.js +2 -2
- package/dist/Avatar/AvatarStatusDot.js +2 -2
- package/dist/AvatarGroup/AvatarGroup.js +2 -2
- package/dist/AvatarGroup/AvatarGroupOverflow.js +3 -3
- package/dist/Badge/Badge.js +2 -2
- package/dist/Banner/Banner.d.ts +2 -2
- package/dist/Banner/Banner.js +6 -6
- package/dist/Blockquote/Blockquote.js +2 -2
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -3
- package/dist/Breadcrumbs/Breadcrumbs.js +2 -2
- package/dist/Button/Button.js +2 -2
- package/dist/ButtonGroup/ButtonGroup.js +2 -2
- package/dist/Calendar/Calendar.js +3 -3
- package/dist/Card/Card.js +2 -2
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Center/Center.js +2 -2
- package/dist/Chat/ChatComposer.js +2 -2
- package/dist/Chat/ChatComposerDrawer.js +2 -2
- package/dist/Chat/ChatComposerInput.js +2 -2
- package/dist/Chat/ChatLayout.js +2 -2
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +5 -1
- package/dist/Chat/ChatMessage.js +2 -2
- package/dist/Chat/ChatMessageBubble.js +2 -2
- package/dist/Chat/ChatMessageList.js +2 -2
- package/dist/Chat/ChatMessageMetadata.js +2 -2
- package/dist/Chat/ChatSendButton.js +2 -2
- package/dist/Chat/ChatSystemMessage.js +3 -3
- package/dist/Chat/ChatTokenizedText.js +3 -3
- package/dist/Chat/ChatToolCalls.js +3 -3
- package/dist/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.js +3 -3
- package/dist/CheckboxList/CheckboxList.js +2 -2
- package/dist/Citation/Citation.js +3 -3
- package/dist/ClickableCard/ClickableCard.js +2 -2
- package/dist/Code/Code.js +2 -2
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/Collapsible/Collapsible.js +2 -2
- package/dist/CommandPalette/CommandPaletteFooter.js +2 -2
- package/dist/CommandPalette/CommandPaletteGroup.js +2 -2
- package/dist/CommandPalette/CommandPaletteInput.js +2 -2
- package/dist/CommandPalette/CommandPaletteItem.js +2 -2
- package/dist/CommandPalette/CommandPaletteList.js +2 -2
- package/dist/ContextMenu/ContextMenu.js +4 -4
- package/dist/DateInput/DateInput.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.js +2 -2
- package/dist/DateTimeInput/DateTimeInput.js +2 -2
- package/dist/Dialog/Dialog.js +3 -3
- package/dist/Divider/Divider.js +2 -2
- package/dist/DropdownMenu/DropdownMenu.js +4 -4
- package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
- package/dist/DropdownMenu/{renderXDSDropdownItems.d.ts → renderDropdownItems.d.ts} +3 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -0
- package/dist/DropdownMenu/{renderXDSDropdownItems.js → renderDropdownItems.js} +2 -2
- package/dist/EmptyState/EmptyState.js +2 -2
- package/dist/Field/Field.js +3 -3
- package/dist/Field/FieldLabel.js +2 -2
- package/dist/FieldStatus/FieldStatus.js +2 -2
- package/dist/FileInput/FileInput.js +2 -2
- package/dist/FormLayout/FormLayout.js +2 -2
- package/dist/Grid/Grid.js +3 -3
- package/dist/Grid/GridSpan.js +2 -2
- package/dist/Heading/Heading.js +2 -2
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Icon/Icon.js +3 -3
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/InputGroup/InputGroupText.js +2 -2
- package/dist/Item/Item.js +2 -2
- package/dist/Kbd/Kbd.js +2 -2
- package/dist/Layer/useLayer.d.ts +2 -2
- package/dist/Layout/Layout.js +2 -2
- package/dist/Layout/LayoutContent.js +2 -2
- package/dist/Layout/LayoutFooter.js +2 -2
- package/dist/Layout/LayoutHeader.js +2 -2
- package/dist/Layout/LayoutPanel.js +2 -2
- package/dist/Lightbox/Lightbox.js +2 -2
- package/dist/Link/Link.js +3 -3
- package/dist/List/List.js +2 -2
- package/dist/List/ListItem.js +2 -2
- package/dist/Markdown/Markdown.js +3 -3
- package/dist/MetadataList/MetadataList.js +2 -2
- package/dist/MetadataList/MetadataListItem.js +3 -3
- package/dist/MobileNav/MobileNav.js +2 -2
- package/dist/MultiSelector/MultiSelector.js +2 -2
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NavMenu/NavHeadingMenu.js +2 -2
- package/dist/NavMenu/NavHeadingMenuItem.js +2 -2
- package/dist/NumberInput/NumberInput.js +2 -2
- package/dist/Outline/Outline.d.ts +3 -2
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +27 -8
- package/dist/Outline/useScrollSpy.d.ts +14 -1
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +161 -50
- package/dist/OverflowList/OverflowList.js +2 -2
- package/dist/Overlay/Overlay.js +2 -2
- package/dist/Overlay/OverlayScrim.js +2 -2
- package/dist/Pagination/Pagination.js +3 -3
- package/dist/Popover/Popover.js +4 -4
- package/dist/PowerSearch/PowerSearch.js +2 -2
- package/dist/ProgressBar/ProgressBar.js +5 -5
- package/dist/RadioList/RadioList.js +2 -2
- package/dist/RadioList/RadioListItem.js +4 -4
- package/dist/Resizable/ResizeHandle.js +4 -4
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +1 -5
- package/dist/Section/Section.js +2 -2
- package/dist/SegmentedControl/SegmentedControl.js +2 -2
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -2
- package/dist/SelectableCard/SelectableCard.js +2 -2
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +3 -3
- package/dist/Selector/SelectorOption.js +2 -2
- package/dist/SideNav/SideNav.js +3 -3
- package/dist/SideNav/SideNavHeading.js +9 -9
- package/dist/SideNav/SideNavItem.js +3 -3
- package/dist/SideNav/SideNavSection.js +2 -2
- package/dist/Skeleton/Skeleton.js +2 -2
- package/dist/Slider/Slider.js +4 -4
- package/dist/Spinner/Spinner.js +3 -3
- package/dist/Stack/Stack.js +2 -2
- package/dist/Stack/StackItem.js +2 -2
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/Switch/Switch.js +4 -4
- package/dist/TabList/Tab.js +3 -3
- package/dist/TabList/TabList.js +2 -2
- package/dist/TabList/TabMenu.js +5 -5
- package/dist/Table/BaseTable.js +2 -2
- package/dist/Table/Table.js +3 -3
- package/dist/Table/TableBody.js +2 -2
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableFooter.js +2 -2
- package/dist/Table/TableHeader.js +2 -2
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/TableRow.js +3 -3
- package/dist/Text/Text.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/Thumbnail/Thumbnail.js +2 -2
- package/dist/TimeInput/TimeInput.js +2 -2
- package/dist/Timestamp/Timestamp.js +2 -2
- package/dist/Toast/Toast.js +2 -2
- package/dist/ToggleButton/ToggleButton.d.ts +10 -3
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +66 -20
- package/dist/ToggleButton/ToggleButtonGroup.js +2 -2
- package/dist/Token/Token.js +4 -4
- package/dist/Tokenizer/Tokenizer.js +3 -3
- package/dist/Toolbar/Toolbar.js +2 -2
- package/dist/Tooltip/useTooltip.js +2 -2
- package/dist/TopNav/TopNav.js +3 -3
- package/dist/TopNav/TopNavHeading.js +7 -7
- package/dist/TopNav/TopNavItem.js +3 -3
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +2 -2
- package/dist/TopNav/TopNavMegaMenuItem.js +3 -3
- package/dist/TopNav/TopNavMenu.js +2 -2
- package/dist/TreeList/TreeList.js +2 -2
- package/dist/TreeList/TreeListItem.js +2 -2
- package/dist/Typeahead/BaseTypeahead.js +2 -2
- package/dist/Typeahead/Typeahead.js +2 -2
- package/dist/Typeahead/TypeaheadItem.js +2 -2
- package/dist/docs-types.d.ts +3 -3
- package/dist/docs-types.d.ts.map +1 -1
- package/dist/naming.d.ts +1 -1
- package/dist/naming.js +1 -1
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +3 -3
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +2 -2
- package/dist/theme/syntax/defineSyntaxTheme.js +1 -1
- package/dist/theme/tokens.d.ts +1 -1
- package/dist/theme/tokens.js +4 -4
- package/dist/theme/useTheme.d.ts +2 -2
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +15 -2
- package/dist/utils/mergeProps.d.ts +3 -3
- package/dist/utils/mergeProps.js +4 -4
- package/dist/utils/parseStyleKey.d.ts +2 -2
- package/dist/utils/parseStyleKey.js +2 -2
- package/dist/utils/{xdsThemeProps.d.ts → themeProps.d.ts} +4 -4
- package/dist/utils/themeProps.d.ts.map +1 -0
- package/dist/utils/{xdsThemeProps.js → themeProps.js} +5 -5
- package/package.json +2 -2
- package/src/AlertDialog/AlertDialog.tsx +2 -2
- package/src/AppShell/AppShell.tsx +5 -5
- package/src/AspectRatio/AspectRatio.tsx +2 -2
- package/src/Avatar/Avatar.tsx +2 -2
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.tsx +2 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -3
- package/src/Badge/Badge.tsx +2 -2
- package/src/Banner/Banner.tsx +6 -6
- package/src/Blockquote/Blockquote.tsx +2 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +3 -3
- package/src/Breadcrumbs/Breadcrumbs.tsx +2 -2
- package/src/Button/Button.tsx +2 -2
- package/src/ButtonGroup/ButtonGroup.tsx +2 -2
- package/src/Calendar/Calendar.tsx +3 -3
- package/src/Card/Card.tsx +2 -2
- package/src/Carousel/Carousel.tsx +2 -2
- package/src/Center/Center.tsx +2 -2
- package/src/Chat/ChatComposer.tsx +2 -2
- package/src/Chat/ChatComposerDrawer.tsx +2 -2
- package/src/Chat/ChatComposerInput.tsx +2 -2
- package/src/Chat/ChatLayout.tsx +2 -2
- package/src/Chat/ChatLayoutScrollButton.tsx +7 -1
- package/src/Chat/ChatMessage.tsx +2 -2
- package/src/Chat/ChatMessageBubble.tsx +2 -2
- package/src/Chat/ChatMessageList.tsx +2 -2
- package/src/Chat/ChatMessageMetadata.tsx +2 -2
- package/src/Chat/ChatSendButton.tsx +2 -2
- package/src/Chat/ChatSystemMessage.tsx +3 -3
- package/src/Chat/ChatTokenizedText.tsx +3 -3
- package/src/Chat/ChatToolCalls.tsx +3 -3
- package/src/Chat/useTriggerMenu.tsx +2 -2
- package/src/CheckboxInput/CheckboxInput.tsx +3 -3
- package/src/CheckboxList/CheckboxList.tsx +2 -2
- package/src/Citation/Citation.tsx +3 -3
- package/src/ClickableCard/ClickableCard.tsx +2 -2
- package/src/Code/Code.tsx +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/Collapsible/Collapsible.tsx +2 -2
- package/src/Collapsible/useCollapsible.doc.mjs +2 -2
- package/src/CommandPalette/CommandPaletteFooter.tsx +2 -2
- package/src/CommandPalette/CommandPaletteGroup.tsx +2 -2
- package/src/CommandPalette/CommandPaletteInput.tsx +2 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +2 -2
- package/src/CommandPalette/CommandPaletteList.tsx +2 -2
- package/src/ContextMenu/ContextMenu.tsx +4 -4
- package/src/DateInput/DateInput.test.tsx +68 -20
- package/src/DateInput/DateInput.tsx +2 -2
- package/src/DateRangeInput/DateRangeInput.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.tsx +2 -2
- package/src/Dialog/Dialog.tsx +3 -3
- package/src/Divider/Divider.doc.mjs +1 -1
- package/src/Divider/Divider.tsx +2 -2
- package/src/DropdownMenu/DropdownMenu.tsx +4 -4
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -2
- package/src/DropdownMenu/{renderXDSDropdownItems.tsx → renderDropdownItems.tsx} +2 -2
- package/src/EmptyState/EmptyState.tsx +2 -2
- package/src/Field/Field.tsx +3 -3
- package/src/Field/FieldLabel.tsx +2 -2
- package/src/FieldStatus/FieldStatus.tsx +2 -2
- package/src/FileInput/FileInput.tsx +2 -2
- package/src/FormLayout/FormLayout.doc.mjs +3 -3
- package/src/FormLayout/FormLayout.tsx +2 -2
- package/src/Grid/Grid.tsx +3 -3
- package/src/Grid/GridSpan.tsx +2 -2
- package/src/Heading/Heading.tsx +2 -2
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Icon/Icon.doc.mjs +4 -4
- package/src/Icon/Icon.tsx +3 -3
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/InputGroup/InputGroupText.tsx +2 -2
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Item/Item.tsx +2 -2
- package/src/Kbd/Kbd.tsx +2 -2
- package/src/Layer/useLayer.tsx +2 -2
- package/src/Layout/Layout.tsx +2 -2
- package/src/Layout/LayoutContent.tsx +2 -2
- package/src/Layout/LayoutFooter.tsx +2 -2
- package/src/Layout/LayoutHeader.tsx +2 -2
- package/src/Layout/LayoutPanel.tsx +2 -2
- package/src/Lightbox/Lightbox.tsx +2 -2
- package/src/Link/Link.doc.mjs +3 -3
- package/src/Link/Link.tsx +3 -3
- package/src/Link/LinkProvider.doc.mjs +3 -3
- package/src/List/List.tsx +2 -2
- package/src/List/ListItem.tsx +2 -2
- package/src/Markdown/Markdown.doc.mjs +4 -4
- package/src/Markdown/Markdown.tsx +3 -3
- package/src/MetadataList/MetadataList.tsx +2 -2
- package/src/MetadataList/MetadataListItem.tsx +3 -3
- package/src/MobileNav/MobileNav.tsx +2 -2
- package/src/MoreMenu/MoreMenu.doc.mjs +0 -13
- package/src/MultiSelector/MultiSelector.tsx +2 -2
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NavMenu/NavHeadingMenu.tsx +2 -2
- package/src/NavMenu/NavHeadingMenuItem.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +2 -2
- package/src/Outline/Outline.doc.mjs +1 -1
- package/src/Outline/Outline.test.tsx +76 -38
- package/src/Outline/Outline.tsx +27 -8
- package/src/Outline/useScrollSpy.ts +196 -63
- package/src/OverflowList/OverflowList.tsx +2 -2
- package/src/Overlay/Overlay.tsx +2 -2
- package/src/Overlay/OverlayScrim.tsx +2 -2
- package/src/Pagination/Pagination.tsx +3 -3
- package/src/Popover/Popover.tsx +4 -4
- package/src/PowerSearch/PowerSearch.tsx +2 -2
- package/src/ProgressBar/ProgressBar.tsx +5 -5
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +4 -4
- package/src/Resizable/Resizable.doc.mjs +2 -2
- package/src/Resizable/ResizeHandle.tsx +4 -4
- package/src/Resizable/useResizable.ts +1 -7
- package/src/Section/Section.tsx +2 -2
- package/src/SegmentedControl/SegmentedControl.tsx +2 -2
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -2
- package/src/SelectableCard/SelectableCard.tsx +2 -2
- package/src/Selector/Selector.tsx +7 -8
- package/src/Selector/SelectorOption.tsx +2 -2
- package/src/SideNav/SideNav.tsx +3 -3
- package/src/SideNav/SideNavHeading.tsx +9 -9
- package/src/SideNav/SideNavItem.tsx +3 -3
- package/src/SideNav/SideNavSection.tsx +2 -2
- package/src/Skeleton/Skeleton.tsx +2 -2
- package/src/Slider/Slider.tsx +4 -4
- package/src/Spinner/Spinner.tsx +3 -3
- package/src/Stack/Stack.tsx +2 -2
- package/src/Stack/StackItem.tsx +2 -2
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/Switch/Switch.tsx +4 -4
- package/src/TabList/Tab.tsx +3 -3
- package/src/TabList/TabList.tsx +2 -2
- package/src/TabList/TabMenu.tsx +5 -5
- package/src/Table/BaseTable.tsx +2 -2
- package/src/Table/Table.doc.mjs +3 -3
- package/src/Table/Table.tsx +3 -3
- package/src/Table/TableBody.tsx +2 -2
- package/src/Table/TableCell.tsx +3 -3
- package/src/Table/TableFooter.tsx +2 -2
- package/src/Table/TableHeader.tsx +2 -2
- package/src/Table/TableHeaderCell.tsx +3 -3
- package/src/Table/TableRow.tsx +3 -3
- package/src/Text/Text.tsx +2 -2
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.tsx +2 -2
- package/src/Thumbnail/Thumbnail.tsx +2 -2
- package/src/TimeInput/TimeInput.tsx +2 -2
- package/src/Timestamp/Timestamp.tsx +2 -2
- package/src/Toast/Toast.tsx +2 -2
- package/src/ToggleButton/ToggleButton.doc.mjs +2 -2
- package/src/ToggleButton/ToggleButton.test.tsx +148 -6
- package/src/ToggleButton/ToggleButton.tsx +85 -22
- package/src/ToggleButton/ToggleButtonGroup.tsx +2 -2
- package/src/Token/Token.tsx +4 -4
- package/src/Tokenizer/Tokenizer.tsx +3 -3
- package/src/Toolbar/Toolbar.doc.mjs +8 -7
- package/src/Toolbar/Toolbar.tsx +2 -2
- package/src/Tooltip/useTooltip.tsx +2 -2
- package/src/TopNav/TopNav.tsx +3 -3
- package/src/TopNav/TopNavHeading.tsx +7 -7
- package/src/TopNav/TopNavItem.tsx +3 -3
- package/src/TopNav/TopNavMegaMenu.tsx +3 -3
- package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +2 -2
- package/src/TopNav/TopNavMegaMenuItem.tsx +3 -3
- package/src/TopNav/TopNavMenu.tsx +2 -2
- package/src/TreeList/TreeList.tsx +2 -2
- package/src/TreeList/TreeListItem.tsx +2 -2
- package/src/Typeahead/BaseTypeahead.tsx +2 -2
- package/src/Typeahead/Typeahead.tsx +2 -2
- package/src/Typeahead/TypeaheadItem.tsx +2 -2
- package/src/docs-types.ts +3 -3
- package/src/hooks/useEntryAnimation.doc.mjs +3 -3
- package/src/hooks/useMediaQuery.doc.mjs +2 -2
- package/src/hooks/useStreamingText.doc.mjs +3 -3
- package/src/naming.ts +1 -1
- package/src/reset.css +1 -1
- package/src/tailwind-theme.css +1 -1
- package/src/theme/Theme.doc.mjs +2 -2
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +3 -3
- package/src/theme/index.ts +2 -2
- package/src/theme/syntax/defineSyntaxTheme.ts +1 -1
- package/src/theme/tokens.ts +4 -4
- package/src/theme/useTheme.ts +2 -2
- package/src/utils/dateParser.test.ts +26 -0
- package/src/utils/dateParser.ts +16 -2
- package/src/utils/mergeProps.ts +4 -4
- package/src/utils/parseStyleKey.ts +2 -2
- package/src/utils/{xdsThemeProps.test.ts → themeProps.test.ts} +12 -12
- package/src/utils/{xdsThemeProps.ts → themeProps.ts} +5 -5
- package/dist/DropdownMenu/renderXDSDropdownItems.d.ts.map +0 -1
- package/dist/utils/xdsThemeProps.d.ts.map +0 -1
|
@@ -37,7 +37,13 @@ describe('parseOutlineFromMarkdown', () => {
|
|
|
37
37
|
parseOutlineFromMarkdown(
|
|
38
38
|
'## **Install** `@astryxdesign/core`\n\n```\n# Not a heading\n```',
|
|
39
39
|
),
|
|
40
|
-
).toEqual([
|
|
40
|
+
).toEqual([
|
|
41
|
+
{
|
|
42
|
+
id: 'install-astryxdesign-core',
|
|
43
|
+
label: 'Install @astryxdesign/core',
|
|
44
|
+
level: 2,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
41
47
|
});
|
|
42
48
|
|
|
43
49
|
it('deduplicates generated ids', () => {
|
|
@@ -87,7 +93,7 @@ describe('Outline', () => {
|
|
|
87
93
|
).not.toHaveAttribute('aria-current');
|
|
88
94
|
});
|
|
89
95
|
|
|
90
|
-
it('smooth-scrolls and
|
|
96
|
+
it('smooth-scrolls and defers the indicator until the scroll settles when uncontrolled', async () => {
|
|
91
97
|
const user = userEvent.setup();
|
|
92
98
|
const target = document.createElement('h2');
|
|
93
99
|
target.id = 'install';
|
|
@@ -101,6 +107,47 @@ describe('Outline', () => {
|
|
|
101
107
|
behavior: 'smooth',
|
|
102
108
|
block: 'start',
|
|
103
109
|
});
|
|
110
|
+
// Uncontrolled: the indicator is deferred during the programmatic scroll,
|
|
111
|
+
// so it has not moved to the clicked item yet.
|
|
112
|
+
expect(
|
|
113
|
+
screen.getByRole('link', {name: 'Installation'}),
|
|
114
|
+
).not.toHaveAttribute('aria-current', 'true');
|
|
115
|
+
|
|
116
|
+
// When the scroll settles, the indicator lands on the clicked item.
|
|
117
|
+
act(() => {
|
|
118
|
+
window.dispatchEvent(new Event('scrollend'));
|
|
119
|
+
});
|
|
120
|
+
expect(onActiveIdChange).toHaveBeenCalledWith('install');
|
|
121
|
+
expect(screen.getByRole('link', {name: 'Installation'})).toHaveAttribute(
|
|
122
|
+
'aria-current',
|
|
123
|
+
'true',
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
document.body.removeChild(target);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('reports active id on click when controlled', async () => {
|
|
130
|
+
const user = userEvent.setup();
|
|
131
|
+
const target = document.createElement('h2');
|
|
132
|
+
target.id = 'install';
|
|
133
|
+
document.body.appendChild(target);
|
|
134
|
+
const onActiveIdChange = vi.fn();
|
|
135
|
+
|
|
136
|
+
render(
|
|
137
|
+
<Outline
|
|
138
|
+
items={items}
|
|
139
|
+
activeId="intro"
|
|
140
|
+
onActiveIdChange={onActiveIdChange}
|
|
141
|
+
/>,
|
|
142
|
+
);
|
|
143
|
+
await user.click(screen.getByRole('link', {name: 'Installation'}));
|
|
144
|
+
|
|
145
|
+
expect(target.scrollIntoView).toHaveBeenCalledWith({
|
|
146
|
+
behavior: 'smooth',
|
|
147
|
+
block: 'start',
|
|
148
|
+
});
|
|
149
|
+
// Controlled: there is no built-in scroll-spy, so the consumer owns the
|
|
150
|
+
// active state and must be notified on click.
|
|
104
151
|
expect(onActiveIdChange).toHaveBeenCalledWith('install');
|
|
105
152
|
|
|
106
153
|
document.body.removeChild(target);
|
|
@@ -122,11 +169,7 @@ describe('Outline', () => {
|
|
|
122
169
|
|
|
123
170
|
it('renders with density="compact"', () => {
|
|
124
171
|
render(
|
|
125
|
-
<Outline
|
|
126
|
-
items={items}
|
|
127
|
-
density="compact"
|
|
128
|
-
data-testid="outline-compact"
|
|
129
|
-
/>,
|
|
172
|
+
<Outline items={items} density="compact" data-testid="outline-compact" />,
|
|
130
173
|
);
|
|
131
174
|
expect(screen.getByTestId('outline-compact').className).toContain(
|
|
132
175
|
'compact',
|
|
@@ -201,50 +244,45 @@ describe('Outline', () => {
|
|
|
201
244
|
).not.toHaveAttribute('aria-current');
|
|
202
245
|
});
|
|
203
246
|
|
|
204
|
-
it('updates uncontrolled active id from
|
|
205
|
-
let observerCallback: IntersectionObserverCallback | undefined;
|
|
206
|
-
|
|
207
|
-
class MockIntersectionObserver {
|
|
208
|
-
observe = vi.fn();
|
|
209
|
-
disconnect = vi.fn();
|
|
210
|
-
|
|
211
|
-
constructor(callback: IntersectionObserverCallback) {
|
|
212
|
-
observerCallback = callback;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
vi.stubGlobal('IntersectionObserver', MockIntersectionObserver);
|
|
217
|
-
|
|
247
|
+
it('updates uncontrolled active id from scroll position', () => {
|
|
218
248
|
const intro = document.createElement('h2');
|
|
219
249
|
intro.id = 'intro';
|
|
250
|
+
const install = document.createElement('h3');
|
|
251
|
+
install.id = 'install';
|
|
220
252
|
const api = document.createElement('h3');
|
|
221
253
|
api.id = 'api';
|
|
222
|
-
document.body.append(intro, api);
|
|
254
|
+
document.body.append(intro, install, api);
|
|
223
255
|
|
|
224
|
-
|
|
225
|
-
|
|
256
|
+
// Not at the bottom of the page.
|
|
257
|
+
Object.defineProperty(document.documentElement, 'scrollHeight', {
|
|
258
|
+
value: 4000,
|
|
259
|
+
configurable: true,
|
|
260
|
+
});
|
|
226
261
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
// intro + install have scrolled above the activation line (top <= 0);
|
|
263
|
+
// api is still below it, so install is the last passed heading.
|
|
264
|
+
vi.spyOn(intro, 'getBoundingClientRect').mockReturnValue({
|
|
265
|
+
top: -200,
|
|
266
|
+
} as DOMRect);
|
|
267
|
+
vi.spyOn(install, 'getBoundingClientRect').mockReturnValue({
|
|
268
|
+
top: -10,
|
|
269
|
+
} as DOMRect);
|
|
270
|
+
vi.spyOn(api, 'getBoundingClientRect').mockReturnValue({
|
|
271
|
+
top: 400,
|
|
272
|
+
} as DOMRect);
|
|
236
273
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
});
|
|
274
|
+
const onActiveIdChange = vi.fn();
|
|
275
|
+
// The hook resolves the active id from scroll position on mount.
|
|
276
|
+
render(<Outline items={items} onActiveIdChange={onActiveIdChange} />);
|
|
240
277
|
|
|
241
|
-
expect(screen.getByRole('link', {name: '
|
|
278
|
+
expect(screen.getByRole('link', {name: 'Installation'})).toHaveAttribute(
|
|
242
279
|
'aria-current',
|
|
243
280
|
'true',
|
|
244
281
|
);
|
|
245
|
-
expect(onActiveIdChange).toHaveBeenCalledWith('
|
|
282
|
+
expect(onActiveIdChange).toHaveBeenCalledWith('install');
|
|
246
283
|
|
|
247
284
|
document.body.removeChild(intro);
|
|
285
|
+
document.body.removeChild(install);
|
|
248
286
|
document.body.removeChild(api);
|
|
249
287
|
});
|
|
250
288
|
});
|
package/src/Outline/Outline.tsx
CHANGED
|
@@ -36,7 +36,7 @@ import {mergeProps, mergeRefs} from '../utils';
|
|
|
36
36
|
import type {BaseProps} from '../BaseProps';
|
|
37
37
|
import {useScrollSpy} from './useScrollSpy';
|
|
38
38
|
import type {OutlineItem} from './types';
|
|
39
|
-
import {
|
|
39
|
+
import {themeProps} from '../utils/themeProps';
|
|
40
40
|
|
|
41
41
|
export type {OutlineItem} from './types';
|
|
42
42
|
|
|
@@ -217,8 +217,9 @@ function getIndentStyle(level: number) {
|
|
|
217
217
|
* indentation based on each heading level. Features a sliding indicator
|
|
218
218
|
* track that animates to the active item.
|
|
219
219
|
*
|
|
220
|
-
* When `activeId` is omitted, it
|
|
221
|
-
*
|
|
220
|
+
* When `activeId` is omitted, it tracks scroll position and marks the last
|
|
221
|
+
* heading whose top has passed its activation line (its scroll-margin-top)
|
|
222
|
+
* active — defaulting to the first item at the top and the last at the bottom.
|
|
222
223
|
*
|
|
223
224
|
* @example
|
|
224
225
|
* ```
|
|
@@ -246,7 +247,12 @@ export function Outline({
|
|
|
246
247
|
}: OutlineProps) {
|
|
247
248
|
const rootRef = useRef<HTMLElement | null>(null);
|
|
248
249
|
const LinkComponent = useLinkComponent();
|
|
249
|
-
const
|
|
250
|
+
const isControlled = activeId !== undefined;
|
|
251
|
+
const {
|
|
252
|
+
activeId: resolvedActiveId,
|
|
253
|
+
setActiveId,
|
|
254
|
+
lockActiveId,
|
|
255
|
+
} = useScrollSpy({
|
|
250
256
|
activeId,
|
|
251
257
|
items,
|
|
252
258
|
onActiveIdChange,
|
|
@@ -256,8 +262,9 @@ export function Outline({
|
|
|
256
262
|
const handleClick =
|
|
257
263
|
(id: string) => (event: React.MouseEvent<HTMLElement>) => {
|
|
258
264
|
const target = document.getElementById(id);
|
|
259
|
-
setActiveId(id);
|
|
260
265
|
|
|
266
|
+
// Let the browser handle modified clicks (open in new tab, etc.) and
|
|
267
|
+
// missing targets without touching the active state.
|
|
261
268
|
if (
|
|
262
269
|
target == null ||
|
|
263
270
|
event.defaultPrevented ||
|
|
@@ -271,6 +278,18 @@ export function Outline({
|
|
|
271
278
|
|
|
272
279
|
event.preventDefault();
|
|
273
280
|
window.history.pushState(null, '', `#${id}`);
|
|
281
|
+
|
|
282
|
+
// Move the indicator to the clicked item in a single step. Controlled
|
|
283
|
+
// consumers own the active state (notify only); uncontrolled mode pins
|
|
284
|
+
// the active id and suppresses scroll-spy until the next manual scroll,
|
|
285
|
+
// so the click is honored — even for short/last sections — and the
|
|
286
|
+
// indicator doesn't chase the smooth scroll through other sections.
|
|
287
|
+
if (isControlled) {
|
|
288
|
+
setActiveId(id);
|
|
289
|
+
} else {
|
|
290
|
+
lockActiveId(id);
|
|
291
|
+
}
|
|
292
|
+
|
|
274
293
|
target.scrollIntoView({behavior: 'smooth', block: 'start'});
|
|
275
294
|
};
|
|
276
295
|
|
|
@@ -281,7 +300,7 @@ export function Outline({
|
|
|
281
300
|
aria-label={label}
|
|
282
301
|
data-testid={testId}
|
|
283
302
|
{...mergeProps(
|
|
284
|
-
|
|
303
|
+
themeProps('outline', {density}),
|
|
285
304
|
stylex.props(styles.root, xstyle),
|
|
286
305
|
className,
|
|
287
306
|
style,
|
|
@@ -297,7 +316,7 @@ export function Outline({
|
|
|
297
316
|
aria-current={isActive ? 'true' : undefined}
|
|
298
317
|
onClick={handleClick(item.id)}
|
|
299
318
|
{...mergeProps(
|
|
300
|
-
|
|
319
|
+
themeProps('outline-item', {
|
|
301
320
|
active: isActive ? 'active' : null,
|
|
302
321
|
level: item.level,
|
|
303
322
|
}),
|
|
@@ -323,7 +342,7 @@ export function Outline({
|
|
|
323
342
|
</div>
|
|
324
343
|
<span
|
|
325
344
|
{...mergeProps(
|
|
326
|
-
|
|
345
|
+
themeProps('outline-indicator'),
|
|
327
346
|
stylex.props(styles.indicator),
|
|
328
347
|
)}
|
|
329
348
|
aria-hidden="true"
|
|
@@ -4,17 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useScrollSpy.ts
|
|
7
|
-
* @input Uses React,
|
|
7
|
+
* @input Uses React, scroll position of heading elements, OutlineItem type
|
|
8
8
|
* @output Exports internal useScrollSpy hook
|
|
9
9
|
* @position Internal behavior hook; consumed by Outline.tsx
|
|
10
10
|
*
|
|
11
|
+
* Drives the active outline item from scroll position. On each scroll
|
|
12
|
+
* (rAF-throttled) it reads live heading positions and marks the last heading
|
|
13
|
+
* whose top has passed its activation line (its own scroll-margin-top, i.e.
|
|
14
|
+
* where it lands when navigated to). This is stable — it never compares stale
|
|
15
|
+
* cached positions — so the indicator moves monotonically instead of jumping.
|
|
16
|
+
* Defaults to the first item at the top and the last item at the bottom so
|
|
17
|
+
* short final sections still activate.
|
|
18
|
+
*
|
|
11
19
|
* SYNC: When modified, update /packages/core/src/Outline/Outline.tsx
|
|
12
20
|
*/
|
|
13
21
|
|
|
14
|
-
import {useEffect, useRef, useState} from 'react';
|
|
22
|
+
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
15
23
|
import type {OutlineItem} from './types';
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
/** Keys that scroll the viewport — used to detect a manual scroll intent. */
|
|
26
|
+
const SCROLL_KEYS = new Set([
|
|
27
|
+
'ArrowUp',
|
|
28
|
+
'ArrowDown',
|
|
29
|
+
'PageUp',
|
|
30
|
+
'PageDown',
|
|
31
|
+
'Home',
|
|
32
|
+
'End',
|
|
33
|
+
' ',
|
|
34
|
+
'Spacebar',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function getScrollableAncestor(
|
|
38
|
+
element: HTMLElement | null,
|
|
39
|
+
): HTMLElement | null {
|
|
18
40
|
let current = element?.parentElement ?? null;
|
|
19
41
|
|
|
20
42
|
while (current != null) {
|
|
@@ -36,6 +58,55 @@ function getScrollableAncestor(element: HTMLElement | null): Element | null {
|
|
|
36
58
|
return null;
|
|
37
59
|
}
|
|
38
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the active heading id from current scroll position.
|
|
63
|
+
*
|
|
64
|
+
* A heading is "passed" once its top reaches its activation line — the scroll
|
|
65
|
+
* root's top plus the heading's own scroll-margin-top. The active heading is
|
|
66
|
+
* the last passed one (headings are in document order). When none have passed
|
|
67
|
+
* (scrolled above the first), the first item is active; at the bottom, the
|
|
68
|
+
* last item is active.
|
|
69
|
+
*/
|
|
70
|
+
function resolveActiveId(
|
|
71
|
+
items: OutlineItem[],
|
|
72
|
+
scrollRoot: HTMLElement | null,
|
|
73
|
+
): string | undefined {
|
|
74
|
+
if (items.length === 0) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rootTop =
|
|
79
|
+
scrollRoot != null ? scrollRoot.getBoundingClientRect().top : 0;
|
|
80
|
+
|
|
81
|
+
const atBottom =
|
|
82
|
+
scrollRoot != null
|
|
83
|
+
? scrollRoot.scrollTop + scrollRoot.clientHeight >=
|
|
84
|
+
scrollRoot.scrollHeight - 2
|
|
85
|
+
: window.innerHeight + window.scrollY >=
|
|
86
|
+
document.documentElement.scrollHeight - 2;
|
|
87
|
+
if (atBottom) {
|
|
88
|
+
return items[items.length - 1].id;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let activeId = items[0].id;
|
|
92
|
+
for (const item of items) {
|
|
93
|
+
const element = document.getElementById(item.id);
|
|
94
|
+
if (element == null) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const top = element.getBoundingClientRect().top;
|
|
98
|
+
const marginTop =
|
|
99
|
+
Number.parseFloat(window.getComputedStyle(element).scrollMarginTop) || 0;
|
|
100
|
+
|
|
101
|
+
if (top <= rootTop + marginTop + 1) {
|
|
102
|
+
activeId = item.id;
|
|
103
|
+
} else {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return activeId;
|
|
108
|
+
}
|
|
109
|
+
|
|
39
110
|
interface UseScrollSpyOptions {
|
|
40
111
|
activeId?: string;
|
|
41
112
|
items: OutlineItem[];
|
|
@@ -43,93 +114,95 @@ interface UseScrollSpyOptions {
|
|
|
43
114
|
rootRef: React.RefObject<HTMLElement | null>;
|
|
44
115
|
}
|
|
45
116
|
|
|
117
|
+
interface UseScrollSpyResult {
|
|
118
|
+
activeId: string | undefined;
|
|
119
|
+
/** Set the active id (notifies onActiveIdChange). For controlled consumers. */
|
|
120
|
+
setActiveId: (id: string) => void;
|
|
121
|
+
/**
|
|
122
|
+
* Handle a click on the outline item with id `id`. Delays moving the
|
|
123
|
+
* indicator: scroll-spy is suppressed during the programmatic smooth scroll
|
|
124
|
+
* so the indicator doesn't chase it, then the indicator moves once to the
|
|
125
|
+
* clicked item when the scroll settles. If the user scrolls manually mid-way,
|
|
126
|
+
* scroll-position tracking resumes immediately instead.
|
|
127
|
+
*/
|
|
128
|
+
lockActiveId: (id: string) => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
46
131
|
export function useScrollSpy({
|
|
47
132
|
activeId,
|
|
48
133
|
items,
|
|
49
134
|
onActiveIdChange,
|
|
50
135
|
rootRef,
|
|
51
|
-
}: UseScrollSpyOptions):
|
|
136
|
+
}: UseScrollSpyOptions): UseScrollSpyResult {
|
|
52
137
|
const isControlled = activeId !== undefined;
|
|
53
138
|
const [uncontrolledActiveId, setUncontrolledActiveId] = useState<
|
|
54
139
|
string | undefined
|
|
55
140
|
>(items[0]?.id);
|
|
56
|
-
const visibleHeadingIdsRef = useRef<Set<string>>(new Set());
|
|
57
|
-
const headingTopRef = useRef<Map<string, number>>(new Map());
|
|
58
141
|
const activeIdRef = useRef<string | undefined>(activeId);
|
|
142
|
+
// While true, scroll-spy ignores scroll updates because a click is driving a
|
|
143
|
+
// programmatic scroll. Released when that scroll settles or the user scrolls.
|
|
144
|
+
const suppressRef = useRef(false);
|
|
145
|
+
const releaseSuppressionRef = useRef<(() => void) | null>(null);
|
|
146
|
+
// Latest scroll-position resolver, so the click handler can resume tracking
|
|
147
|
+
// when the user scrolls during a programmatic scroll.
|
|
148
|
+
const syncRef = useRef<(() => void) | null>(null);
|
|
149
|
+
// Keep latest items/callback in refs so the scroll listener effect doesn't
|
|
150
|
+
// re-subscribe on every render (items is a fresh array each render).
|
|
151
|
+
const itemsRef = useRef(items);
|
|
152
|
+
itemsRef.current = items;
|
|
153
|
+
const onActiveIdChangeRef = useRef(onActiveIdChange);
|
|
154
|
+
onActiveIdChangeRef.current = onActiveIdChange;
|
|
59
155
|
const itemIds = items.map(item => item.id).join('\n');
|
|
60
156
|
activeIdRef.current = isControlled ? activeId : uncontrolledActiveId;
|
|
61
157
|
|
|
62
158
|
useEffect(() => {
|
|
63
|
-
if (isControlled || typeof
|
|
159
|
+
if (isControlled || typeof window === 'undefined') {
|
|
64
160
|
return;
|
|
65
161
|
}
|
|
66
162
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
.filter((element): element is HTMLElement => element != null);
|
|
163
|
+
const scrollRoot = getScrollableAncestor(rootRef.current);
|
|
164
|
+
const scrollTarget: HTMLElement | Window = scrollRoot ?? window;
|
|
70
165
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const visibleHeadingIds = visibleHeadingIdsRef.current;
|
|
76
|
-
const headingTop = headingTopRef.current;
|
|
77
|
-
|
|
78
|
-
const setNextActiveId = (nextActiveId: string) => {
|
|
79
|
-
if (activeIdRef.current === nextActiveId) {
|
|
166
|
+
let frame = 0;
|
|
167
|
+
const update = () => {
|
|
168
|
+
frame = 0;
|
|
169
|
+
if (suppressRef.current) {
|
|
80
170
|
return;
|
|
81
171
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
172
|
+
const nextActiveId = resolveActiveId(itemsRef.current, scrollRoot);
|
|
173
|
+
if (nextActiveId != null && nextActiveId !== activeIdRef.current) {
|
|
174
|
+
activeIdRef.current = nextActiveId;
|
|
175
|
+
setUncontrolledActiveId(nextActiveId);
|
|
176
|
+
onActiveIdChangeRef.current?.(nextActiveId);
|
|
177
|
+
}
|
|
85
178
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let nextTop = Number.POSITIVE_INFINITY;
|
|
90
|
-
|
|
91
|
-
for (const id of visibleHeadingIds) {
|
|
92
|
-
const top = headingTop.get(id) ?? Number.POSITIVE_INFINITY;
|
|
93
|
-
if (top < nextTop) {
|
|
94
|
-
nextTop = top;
|
|
95
|
-
nextActiveId = id;
|
|
96
|
-
}
|
|
179
|
+
const onScroll = () => {
|
|
180
|
+
if (frame === 0) {
|
|
181
|
+
frame = requestAnimationFrame(update);
|
|
97
182
|
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
syncRef.current = update;
|
|
186
|
+
update();
|
|
187
|
+
scrollTarget.addEventListener('scroll', onScroll, {passive: true});
|
|
188
|
+
window.addEventListener('resize', onScroll, {passive: true});
|
|
98
189
|
|
|
99
|
-
|
|
100
|
-
|
|
190
|
+
return () => {
|
|
191
|
+
syncRef.current = null;
|
|
192
|
+
scrollTarget.removeEventListener('scroll', onScroll);
|
|
193
|
+
window.removeEventListener('resize', onScroll);
|
|
194
|
+
if (frame !== 0) {
|
|
195
|
+
cancelAnimationFrame(frame);
|
|
101
196
|
}
|
|
102
197
|
};
|
|
198
|
+
}, [isControlled, itemIds, rootRef]);
|
|
103
199
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for (const entry of entries) {
|
|
107
|
-
const id = entry.target.id;
|
|
108
|
-
headingTop.set(id, entry.boundingClientRect.top);
|
|
109
|
-
if (entry.isIntersecting) {
|
|
110
|
-
visibleHeadingIds.add(id);
|
|
111
|
-
} else {
|
|
112
|
-
visibleHeadingIds.delete(id);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
chooseActiveHeading();
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
root: getScrollableAncestor(rootRef.current),
|
|
119
|
-
threshold: 0,
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
for (const headingElement of headingElements) {
|
|
124
|
-
observer.observe(headingElement);
|
|
125
|
-
}
|
|
126
|
-
|
|
200
|
+
// Tear down any pending suppression listeners when the Outline unmounts.
|
|
201
|
+
useEffect(() => {
|
|
127
202
|
return () => {
|
|
128
|
-
|
|
129
|
-
visibleHeadingIds.clear();
|
|
130
|
-
headingTop.clear();
|
|
203
|
+
releaseSuppressionRef.current?.();
|
|
131
204
|
};
|
|
132
|
-
}, [
|
|
205
|
+
}, []);
|
|
133
206
|
|
|
134
207
|
const setActiveId = (nextActiveId: string) => {
|
|
135
208
|
if (!isControlled) {
|
|
@@ -138,5 +211,65 @@ export function useScrollSpy({
|
|
|
138
211
|
onActiveIdChange?.(nextActiveId);
|
|
139
212
|
};
|
|
140
213
|
|
|
141
|
-
|
|
214
|
+
const lockActiveId = useCallback((clickedId: string) => {
|
|
215
|
+
if (typeof window === 'undefined') {
|
|
216
|
+
setUncontrolledActiveId(clickedId);
|
|
217
|
+
activeIdRef.current = clickedId;
|
|
218
|
+
onActiveIdChangeRef.current?.(clickedId);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Freeze the indicator during the programmatic smooth scroll instead of
|
|
223
|
+
// moving it immediately — it lands on the clicked item once the scroll
|
|
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?.();
|
|
228
|
+
|
|
229
|
+
let settleTimer = 0;
|
|
230
|
+
const cleanup = () => {
|
|
231
|
+
window.removeEventListener('scrollend', onSettle);
|
|
232
|
+
window.removeEventListener('wheel', onManual);
|
|
233
|
+
window.removeEventListener('touchmove', onManual);
|
|
234
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
235
|
+
if (settleTimer !== 0) {
|
|
236
|
+
clearTimeout(settleTimer);
|
|
237
|
+
settleTimer = 0;
|
|
238
|
+
}
|
|
239
|
+
releaseSuppressionRef.current = null;
|
|
240
|
+
};
|
|
241
|
+
// Programmatic scroll finished: move the indicator to the clicked item.
|
|
242
|
+
const onSettle = () => {
|
|
243
|
+
cleanup();
|
|
244
|
+
suppressRef.current = false;
|
|
245
|
+
setUncontrolledActiveId(clickedId);
|
|
246
|
+
activeIdRef.current = clickedId;
|
|
247
|
+
onActiveIdChangeRef.current?.(clickedId);
|
|
248
|
+
};
|
|
249
|
+
// User scrolled mid-flight: hand control back to scroll-position tracking.
|
|
250
|
+
const onManual = () => {
|
|
251
|
+
cleanup();
|
|
252
|
+
suppressRef.current = false;
|
|
253
|
+
syncRef.current?.();
|
|
254
|
+
};
|
|
255
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
256
|
+
if (SCROLL_KEYS.has(event.key)) {
|
|
257
|
+
onManual();
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
window.addEventListener('scrollend', onSettle, {once: true});
|
|
262
|
+
window.addEventListener('wheel', onManual, {passive: true});
|
|
263
|
+
window.addEventListener('touchmove', onManual, {passive: true});
|
|
264
|
+
window.addEventListener('keydown', onKeyDown);
|
|
265
|
+
// Fallback when scrollend is unsupported or no scroll is needed.
|
|
266
|
+
settleTimer = window.setTimeout(onSettle, 1200);
|
|
267
|
+
releaseSuppressionRef.current = cleanup;
|
|
268
|
+
}, []);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
activeId: isControlled ? activeId : uncontrolledActiveId,
|
|
272
|
+
setActiveId,
|
|
273
|
+
lockActiveId,
|
|
274
|
+
};
|
|
142
275
|
}
|
|
@@ -24,7 +24,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
24
24
|
import {mergeProps, mergeRefs} from '../utils';
|
|
25
25
|
import {useOverflow} from '../hooks/useOverflow';
|
|
26
26
|
import {spacingVars} from '../theme/tokens.stylex';
|
|
27
|
-
import {
|
|
27
|
+
import {themeProps} from '../utils/themeProps';
|
|
28
28
|
|
|
29
29
|
const styles = stylex.create({
|
|
30
30
|
container: {
|
|
@@ -246,7 +246,7 @@ export function OverflowList({
|
|
|
246
246
|
<div
|
|
247
247
|
ref={mergeRefs(ref, containerRef)}
|
|
248
248
|
{...mergeProps(
|
|
249
|
-
|
|
249
|
+
themeProps('overflow-list'),
|
|
250
250
|
stylex.props(
|
|
251
251
|
styles.container,
|
|
252
252
|
gapStyles[gap],
|
package/src/Overlay/Overlay.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import {mergeProps, mergeRefs} from '../utils';
|
|
|
21
21
|
import {useOverlay} from './useOverlay';
|
|
22
22
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
23
23
|
import {overlayScope, overlayContainerStyles} from './overlay.markers.stylex';
|
|
24
|
-
import {
|
|
24
|
+
import {themeProps} from '../utils/themeProps';
|
|
25
25
|
import type {BaseProps} from '../BaseProps';
|
|
26
26
|
import type {
|
|
27
27
|
OverlayScrimMode,
|
|
@@ -115,7 +115,7 @@ export function Overlay({
|
|
|
115
115
|
<div
|
|
116
116
|
ref={mergeRefs(ref, overlay.containerRef)}
|
|
117
117
|
{...mergeProps(
|
|
118
|
-
|
|
118
|
+
themeProps('overlay'),
|
|
119
119
|
stylex.props(overlayScope, overlayContainerStyles.root, xstyle),
|
|
120
120
|
className,
|
|
121
121
|
style,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import {MediaTheme} from '../theme/MediaTheme';
|
|
24
24
|
import {mergeProps} from '../utils';
|
|
25
25
|
import {overlayScope} from './overlay.markers.stylex';
|
|
26
|
-
import {
|
|
26
|
+
import {themeProps} from '../utils/themeProps';
|
|
27
27
|
|
|
28
28
|
// =============================================================================
|
|
29
29
|
// Types (re-exported from index.ts for consumers)
|
|
@@ -276,7 +276,7 @@ export function OverlayScrim({
|
|
|
276
276
|
return (
|
|
277
277
|
<div
|
|
278
278
|
{...mergeProps(
|
|
279
|
-
|
|
279
|
+
themeProps('overlay-scrim', {position}),
|
|
280
280
|
stylex.props(
|
|
281
281
|
styles.base,
|
|
282
282
|
positionMap[position],
|
|
@@ -36,7 +36,7 @@ import {Selector} from '../Selector';
|
|
|
36
36
|
import {Text} from '../Text';
|
|
37
37
|
import {mergeProps} from '../utils';
|
|
38
38
|
import type {BaseProps} from '../BaseProps';
|
|
39
|
-
import {
|
|
39
|
+
import {themeProps} from '../utils/themeProps';
|
|
40
40
|
|
|
41
41
|
// =============================================================================
|
|
42
42
|
// Types
|
|
@@ -507,7 +507,7 @@ export function Pagination({
|
|
|
507
507
|
onClick={() => handlePageChange(i + 1)}
|
|
508
508
|
disabled={isDisabled}
|
|
509
509
|
{...mergeProps(
|
|
510
|
-
|
|
510
|
+
themeProps('pagination-dot', {
|
|
511
511
|
active: i + 1 === page ? 'active' : null,
|
|
512
512
|
size,
|
|
513
513
|
}),
|
|
@@ -536,7 +536,7 @@ export function Pagination({
|
|
|
536
536
|
aria-label={label}
|
|
537
537
|
data-testid={testId}
|
|
538
538
|
{...mergeProps(
|
|
539
|
-
|
|
539
|
+
themeProps('pagination', {variant, size}),
|
|
540
540
|
stylex.props(styles.root, xstyle),
|
|
541
541
|
className,
|
|
542
542
|
style,
|