@astryxdesign/core 0.7.1 → 0.7.2-canary.01eab6a
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/dist/AspectRatio/AspectRatio.d.ts +6 -8
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +44 -3
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +38 -14
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -2
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +37 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +20 -8
- package/dist/Drawer/Drawer.d.ts +191 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -0
- package/dist/Drawer/Drawer.js +382 -0
- package/dist/Drawer/DrawerHeader.d.ts +85 -0
- package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
- package/dist/Drawer/DrawerHeader.js +134 -0
- package/dist/Drawer/index.d.ts +11 -0
- package/dist/Drawer/index.d.ts.map +1 -0
- package/dist/Drawer/index.js +11 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
- package/dist/Drawer/useDrawerDialogPresence.js +236 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +47 -4
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +4 -10
- package/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +8 -4
- package/dist/Grid/GridSpan.d.ts.map +1 -1
- package/dist/Grid/GridSpan.js +1 -0
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +4 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -2
- package/dist/Item/Item.d.ts +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +19 -4
- package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
- package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
- package/dist/Layer/layerViewportInset.stylex.js +6 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +13 -7
- package/dist/Layer/useLayerDismissal.d.ts +1 -1
- package/dist/Layer/useLayerDismissal.js +1 -1
- package/dist/Layout/container.stylex.d.ts +5 -5
- package/dist/Layout/container.stylex.js +4 -4
- package/dist/List/List.d.ts +46 -19
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +28 -10
- package/dist/List/ListContext.d.ts +8 -3
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +7 -2
- package/dist/List/ListItem.d.ts +13 -5
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +21 -8
- package/dist/List/index.d.ts +1 -1
- package/dist/List/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +8 -1
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
- package/dist/Markdown/plugins/frontmatter.js +7 -3
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +5 -1
- package/dist/MetadataList/MetadataListContext.d.ts +1 -1
- package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +22 -8
- package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +3 -1
- package/dist/Pagination/Pagination.d.ts +8 -5
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +21 -8
- package/dist/Popover/Popover.js +2 -2
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/RadioList/RadioList.d.ts +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +6 -2
- package/dist/RadioList/RadioListItem.d.ts +9 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +2 -0
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +6 -2
- package/dist/SideNav/SideNavItem.d.ts +8 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +71 -9
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +13 -1
- package/dist/TabList/TabMenu.d.ts +2 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -3
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- package/dist/Text/useTruncation.d.ts +7 -5
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +16 -5
- package/dist/ToggleButton/ToggleButton.d.ts +3 -2
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +12 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -0
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +3 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +2 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -9
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -0
- package/dist/astryx.css +47 -13
- package/dist/hooks/useEntryAnimation.d.ts +2 -1
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +14 -12
- package/dist/hooks/useIsHydrating.d.ts +6 -0
- package/dist/hooks/useIsHydrating.d.ts.map +1 -0
- package/dist/hooks/useIsHydrating.js +30 -0
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +18 -13
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +112 -1
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +0 -3
- package/dist/utils/focusOutline.stylex.d.ts +4 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +4 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +15 -9
- package/dist/utils/sharedMutationObserver.d.ts +21 -0
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
- package/dist/utils/sharedMutationObserver.js +69 -0
- package/locales/en.json +444 -0
- package/locales/pseudo.json +333 -0
- package/package.json +9 -4
- package/src/AspectRatio/AspectRatio.test.tsx +44 -43
- package/src/AspectRatio/AspectRatio.tsx +38 -18
- package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
- package/src/Avatar/Avatar.doc.mjs +5 -3
- package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.test.tsx +155 -9
- package/src/Button/Button.tsx +39 -16
- package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
- package/src/Button/__tests__/Button.a11y.states.ts +0 -10
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +3 -1
- package/src/CheckboxList/CheckboxList.test.tsx +22 -0
- package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
- package/src/CheckboxList/CheckboxListItem.tsx +10 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
- package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
- package/src/ContextMenu/ContextMenu.tsx +4 -1
- package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
- package/src/Dialog/Dialog.test.tsx +46 -0
- package/src/Dialog/Dialog.tsx +20 -7
- package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
- package/src/Drawer/Drawer.doc.mjs +472 -0
- package/src/Drawer/Drawer.spec.md +357 -0
- package/src/Drawer/Drawer.test.tsx +1300 -0
- package/src/Drawer/Drawer.tsx +613 -0
- package/src/Drawer/DrawerDirection.test.tsx +127 -0
- package/src/Drawer/DrawerHeader.doc.mjs +293 -0
- package/src/Drawer/DrawerHeader.test.tsx +102 -0
- package/src/Drawer/DrawerHeader.tsx +235 -0
- package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
- package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
- package/src/Drawer/index.ts +13 -0
- package/src/Drawer/useDrawerDialogPresence.ts +284 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +62 -13
- package/src/DropdownMenu/DropdownMenu.tsx +47 -4
- package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
- package/src/EmptyState/EmptyState.doc.mjs +3 -2
- package/src/EmptyState/EmptyState.spec.md +189 -0
- package/src/EmptyState/EmptyState.test.tsx +74 -3
- package/src/EmptyState/EmptyState.tsx +4 -8
- package/src/Field/Field.doc.mjs +14 -8
- package/src/Field/Field.spec.md +217 -0
- package/src/Field/Field.test.tsx +60 -2
- package/src/Field/Field.tsx +17 -8
- package/src/Grid/GridSpan.tsx +3 -0
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.test.tsx +58 -0
- package/src/HoverCard/useHoverCard.tsx +10 -2
- package/src/IconButton/IconButton.doc.mjs +2 -2
- package/src/IconButton/IconButton.test.tsx +3 -1
- package/src/Item/Item.doc.mjs +12 -8
- package/src/Item/Item.spec.md +226 -0
- package/src/Item/Item.test.tsx +61 -0
- package/src/Item/Item.tsx +40 -4
- package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
- package/src/Layer/layerViewportInset.stylex.ts +6 -2
- package/src/Layer/layerViewportInset.test.ts +1 -0
- package/src/Layer/useLayer.tsx +24 -13
- package/src/Layer/useLayerDismissal.ts +1 -1
- package/src/Layout/container.stylex.ts +6 -6
- package/src/List/List.doc.mjs +14 -1
- package/src/List/List.spec.md +129 -77
- package/src/List/List.test.tsx +163 -94
- package/src/List/List.tsx +74 -18
- package/src/List/ListContext.tsx +9 -3
- package/src/List/ListItem.doc.mjs +24 -5
- package/src/List/ListItem.tsx +56 -27
- package/src/List/index.ts +1 -1
- package/src/Markdown/Markdown.tsx +14 -1
- package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
- package/src/Markdown/plugins/frontmatter.ts +7 -3
- package/src/MetadataList/MetadataList.doc.mjs +2 -2
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +159 -0
- package/src/MultiSelector/MultiSelector.tsx +40 -7
- package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
- package/src/MultiSelector/hooks.ts +3 -1
- package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
- package/src/Pagination/Pagination.doc.mjs +7 -7
- package/src/Pagination/Pagination.spec.md +9 -9
- package/src/Pagination/Pagination.test.tsx +242 -0
- package/src/Pagination/Pagination.tsx +27 -11
- package/src/Popover/Popover.test.tsx +2 -2
- package/src/Popover/Popover.tsx +4 -4
- package/src/PowerSearch/PowerSearch.test.tsx +85 -0
- package/src/PowerSearch/PowerSearch.tsx +2 -2
- package/src/RadioList/RadioList.fr10.test.tsx +45 -2
- package/src/RadioList/RadioList.test.tsx +22 -0
- package/src/RadioList/RadioList.tsx +4 -1
- package/src/RadioList/RadioListItem.doc.mjs +16 -0
- package/src/RadioList/RadioListItem.tsx +10 -1
- package/src/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- package/src/SideNav/SideNav.test.tsx +179 -0
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +96 -17
- package/src/Slider/Slider.spec.md +39 -18
- package/src/Switch/Switch.test.tsx +49 -0
- package/src/Switch/Switch.tsx +11 -0
- package/src/TabList/TabList.spec.md +57 -44
- package/src/TabList/TabList.test.tsx +67 -0
- package/src/TabList/TabMenu.doc.mjs +4 -3
- package/src/TabList/TabMenu.tsx +7 -4
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/Text/useTruncation.test.tsx +69 -0
- package/src/Text/useTruncation.ts +16 -5
- package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- package/src/Tooltip/Tooltip.test.tsx +31 -0
- package/src/Tooltip/useTooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +11 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
- package/src/TopNav/TopNavMegaMenu.tsx +7 -3
- package/src/TopNav/TopNavMenu.test.tsx +19 -0
- package/src/TopNav/TopNavMenu.tsx +11 -3
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/TreeList/TreeList.spec.md +29 -17
- package/src/TreeList/TreeList.test.tsx +327 -6
- package/src/TreeList/TreeList.tsx +5 -1
- package/src/TreeList/TreeListBranches.tsx +6 -0
- package/src/TreeList/TreeListItem.tsx +88 -13
- package/src/Typeahead/BaseTypeahead.tsx +26 -0
- package/src/Typeahead/Typeahead.test.tsx +260 -2
- package/src/docPropLiterals.test.ts +36 -10
- package/src/hooks/useEntryAnimation.doc.mjs +4 -4
- package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
- package/src/hooks/useEntryAnimation.ts +15 -16
- package/src/hooks/useIsHydrating.test.tsx +70 -0
- package/src/hooks/useIsHydrating.ts +31 -0
- package/src/hooks/useMenuHover.ts +18 -13
- package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
- package/src/i18n/InternationalizationProvider.spec.md +252 -0
- package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
- package/src/i18n/generated-locales/en.generated.ts +121 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
- package/src/i18n/useTranslator.test.tsx +37 -0
- package/src/index.ts +1 -0
- package/src/reset.css +0 -29
- package/src/theme/derivedVarRegistry.test.ts +7 -3
- package/src/theme/derivedVarRegistry.ts +0 -3
- package/src/utils/focusOutline.stylex.ts +4 -0
- package/src/utils/plainDate.test.ts +37 -1
- package/src/utils/plainDate.ts +15 -5
- package/src/utils/sharedMutationObserver.test.ts +53 -0
- package/src/utils/sharedMutationObserver.ts +78 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - /packages/cli/assets/templates/blocks/components/TreeList/ (showcase blocks)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {useId, useMemo, type ReactNode} from 'react';
|
|
17
|
+
import {useId, useMemo, useRef, type ReactNode} from 'react';
|
|
18
18
|
import * as stylex from '@stylexjs/stylex';
|
|
19
19
|
import {
|
|
20
20
|
colorVars,
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
28
28
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
29
29
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
30
|
+
import {
|
|
31
|
+
hasInteractiveAncestor,
|
|
32
|
+
useClickableContainer,
|
|
33
|
+
} from '../hooks/useClickableContainer';
|
|
30
34
|
import {Icon} from '../Icon';
|
|
31
35
|
import {mergeProps} from '../utils';
|
|
32
36
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
@@ -46,6 +50,13 @@ const styles = stylex.create({
|
|
|
46
50
|
padding: 0,
|
|
47
51
|
position: 'relative',
|
|
48
52
|
width: '100%',
|
|
53
|
+
// Publish whether THIS treeitem has keyboard focus, for its row's
|
|
54
|
+
// `zIndex` (see `contentWrapper`). Every nested <li> redeclares the
|
|
55
|
+
// default, so a focused ancestor never lifts its descendants' rows.
|
|
56
|
+
'--_tree-focus-z': {
|
|
57
|
+
default: 'auto',
|
|
58
|
+
':focus-visible': '2',
|
|
59
|
+
},
|
|
49
60
|
// The treeitem row is the roving-tabindex focus owner; suppress the
|
|
50
61
|
// native focus ring in favor of the row's :focus-visible outline below.
|
|
51
62
|
outline: 'none',
|
|
@@ -82,6 +93,9 @@ const styles = stylex.create({
|
|
|
82
93
|
display: 'flex',
|
|
83
94
|
alignItems: 'center',
|
|
84
95
|
gap: spacingVars['--spacing-2'],
|
|
96
|
+
// The row's own inline padding only. The per-level indent lives on
|
|
97
|
+
// `indentedContent` inside it, so a theme or caller padding here composes
|
|
98
|
+
// with the indent instead of replacing it.
|
|
85
99
|
paddingInline: spacingVars['--spacing-2'],
|
|
86
100
|
// No `outline: 'none'` here: this element receives the shared focus ring,
|
|
87
101
|
// which already defaults to none, and the shorthand would erase it.
|
|
@@ -89,12 +103,38 @@ const styles = stylex.create({
|
|
|
89
103
|
position: 'relative',
|
|
90
104
|
boxSizing: 'border-box',
|
|
91
105
|
textAlign: 'start',
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
},
|
|
107
|
+
// Guide lines draw above the rows (TreeListBranches) so a row's fill does
|
|
108
|
+
// not break them. Lift the row whose focus ring is showing above the guides
|
|
109
|
+
// too, so no guide cuts through the ring: when focus is inside the row, or
|
|
110
|
+
// when its own treeitem publishes focus (`--_tree-focus-z` on `wrapper`).
|
|
111
|
+
// The ring is the row's outline, so it cannot be lifted on its own: a
|
|
112
|
+
// focused row that is also filled (selected or hovered) covers the guides at
|
|
113
|
+
// that row. That is the accepted trade-off (DD3). Applied only with
|
|
114
|
+
// `focusWithinOrPublished`, the rows that draw the ring: a plain row shows
|
|
115
|
+
// no ring, so its fill stays under the guides.
|
|
116
|
+
focusLift: {
|
|
117
|
+
zIndex: {
|
|
118
|
+
default: 'var(--_tree-focus-z, auto)',
|
|
119
|
+
':has(:focus-visible)': 2,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
// Per-level indent, drawn INSIDE the row box: the row (`contentWrapper`)
|
|
123
|
+
// stays full width, so a nested row's hover and selected fill spans the
|
|
124
|
+
// list like a root row's, while its content sits exactly where a margin
|
|
125
|
+
// indent would put it. A child of the row rather than the row's own padding,
|
|
126
|
+
// so a theme or caller that sets the row's padding keeps the indent. It
|
|
127
|
+
// inherits the row's flex alignment and gap, so a theme that retunes those
|
|
128
|
+
// on `tree-list-item` still lays the content out. The row publishes only the
|
|
129
|
+
// computed distance as `--_tree-indent`; the per-level step is the public,
|
|
130
|
+
// themeable `--tree-list-indent` lever (see TreeList `root`).
|
|
131
|
+
indentedContent: {
|
|
132
|
+
display: 'flex',
|
|
133
|
+
flex: 1,
|
|
134
|
+
minWidth: 0,
|
|
135
|
+
alignItems: 'inherit',
|
|
136
|
+
gap: 'inherit',
|
|
137
|
+
paddingInlineStart: 'var(--_tree-indent, 0px)',
|
|
98
138
|
},
|
|
99
139
|
interactive: {
|
|
100
140
|
cursor: {
|
|
@@ -369,6 +409,7 @@ export function TreeListItem({
|
|
|
369
409
|
const labelId = useId();
|
|
370
410
|
const descriptionId = useId();
|
|
371
411
|
const LinkComponent = useLinkComponent();
|
|
412
|
+
const linkRef = useRef<HTMLAnchorElement>(null);
|
|
372
413
|
const isInteractive = onClick != null || href != null;
|
|
373
414
|
|
|
374
415
|
const handleToggle = useMemo(
|
|
@@ -388,8 +429,10 @@ export function TreeListItem({
|
|
|
388
429
|
if (isDisabled) {
|
|
389
430
|
return;
|
|
390
431
|
}
|
|
391
|
-
|
|
392
|
-
|
|
432
|
+
// Controls inside the row, native or role-based, keep their own
|
|
433
|
+
// activation (FR7): the same rule link rows get from the shared
|
|
434
|
+
// enlarged-surface hook below.
|
|
435
|
+
if (hasInteractiveAncestor(e.target as Element, e.currentTarget)) {
|
|
393
436
|
return;
|
|
394
437
|
}
|
|
395
438
|
if (onClick != null) {
|
|
@@ -402,6 +445,34 @@ export function TreeListItem({
|
|
|
402
445
|
return undefined;
|
|
403
446
|
}, [onClick, hasChildren, onToggle, id, isDisabled]);
|
|
404
447
|
|
|
448
|
+
// A link row (an `href` and no other row action) is an enlarged surface for
|
|
449
|
+
// its link: the indent is part of the row, so a click anywhere in it must
|
|
450
|
+
// follow the link, as Enter already does. Built on the shared
|
|
451
|
+
// enlarged-surface hook (family:navigation-destinations), which skips
|
|
452
|
+
// clicks on nested controls, native or role-based, and text selections.
|
|
453
|
+
// - Clicks are delegated: the hook re-dispatches the click on the anchor
|
|
454
|
+
// with its modifier keys, so the link component and the browser handle
|
|
455
|
+
// same-tab, new-tab and new-window activation, and stops the original, so
|
|
456
|
+
// ancestors see one click per press.
|
|
457
|
+
// - Middle-click has no synthetic equivalent, so it takes the hook's href
|
|
458
|
+
// path, which opens the destination in a new tab after the shared
|
|
459
|
+
// navigation rule.
|
|
460
|
+
// The container ref is attached to link rows only, so every other row stays
|
|
461
|
+
// a plain element and both hook instances stay inert on it.
|
|
462
|
+
const forwardsToLink = href != null && handleClick == null;
|
|
463
|
+
const linkRowRef = useRef<HTMLDivElement>(null);
|
|
464
|
+
const {onClick: delegateToLink} = useClickableContainer({
|
|
465
|
+
containerRef: linkRowRef,
|
|
466
|
+
interactiveRef: linkRef,
|
|
467
|
+
disabled: isDisabled || !forwardsToLink,
|
|
468
|
+
});
|
|
469
|
+
const {onMouseUp: openLinkOnMiddleClick} = useClickableContainer({
|
|
470
|
+
containerRef: linkRowRef,
|
|
471
|
+
href,
|
|
472
|
+
target,
|
|
473
|
+
disabled: isDisabled || !forwardsToLink,
|
|
474
|
+
});
|
|
475
|
+
|
|
405
476
|
// Per-level indent distance. The per-level step is the public, themeable
|
|
406
477
|
// `--tree-list-indent` lever (default `--spacing-4`, set on the tree-list
|
|
407
478
|
// root). A leaf adds a fixed chevron-column offset (chevron width + gap) so
|
|
@@ -411,8 +482,8 @@ export function TreeListItem({
|
|
|
411
482
|
// every row sits flush, like a parent at that level. That offset is tied to
|
|
412
483
|
// the chevron's own dimensions, not the indent step, so it does not scale
|
|
413
484
|
// with the lever. Published as the private `--_tree-indent` and consumed by
|
|
414
|
-
// `
|
|
415
|
-
//
|
|
485
|
+
// `indentedContent`'s stylesheet `padding-inline-start`; the step stays
|
|
486
|
+
// themeable through `--tree-list-indent` (see #4308).
|
|
416
487
|
const reservesChevronColumn = !hasChildren && hasExpandableItems;
|
|
417
488
|
const indentDistance = reservesChevronColumn
|
|
418
489
|
? `calc(${nestedLevel} * var(--tree-list-indent) + ${spacingVars['--spacing-4']} + ${spacingVars['--spacing-2']})`
|
|
@@ -521,6 +592,7 @@ export function TreeListItem({
|
|
|
521
592
|
)}
|
|
522
593
|
{href != null ? (
|
|
523
594
|
<LinkComponent
|
|
595
|
+
ref={linkRef}
|
|
524
596
|
href={href}
|
|
525
597
|
target={target}
|
|
526
598
|
aria-disabled={isDisabled || undefined}
|
|
@@ -599,6 +671,7 @@ export function TreeListItem({
|
|
|
599
671
|
isInteractive || (hasChildren && onClick == null)
|
|
600
672
|
? focusOutlineProps.focusWithinOrPublished(
|
|
601
673
|
styles.contentWrapper,
|
|
674
|
+
styles.focusLift,
|
|
602
675
|
densityStyles[density],
|
|
603
676
|
styles.interactive,
|
|
604
677
|
interactionOverlayStyles.backgroundImage,
|
|
@@ -620,8 +693,10 @@ export function TreeListItem({
|
|
|
620
693
|
className,
|
|
621
694
|
{...indentStyle, ...style},
|
|
622
695
|
)}
|
|
623
|
-
|
|
624
|
-
{
|
|
696
|
+
ref={forwardsToLink ? linkRowRef : undefined}
|
|
697
|
+
onClick={forwardsToLink ? delegateToLink : handleClick}
|
|
698
|
+
onMouseUp={forwardsToLink ? openLinkOnMiddleClick : undefined}>
|
|
699
|
+
<div {...stylex.props(styles.indentedContent)}>{innerContent}</div>
|
|
625
700
|
</div>
|
|
626
701
|
</div>
|
|
627
702
|
{isExpanded && renderedChildren != null && (
|
|
@@ -624,6 +624,32 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
624
624
|
sourceGenRef.current = ++searchGenRef.current;
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
+
// The previous source is remembered in state, not a ref, so a render React
|
|
628
|
+
// discards cannot consume the comparison without its state updates.
|
|
629
|
+
const [sourceOfState, setSourceOfState] = useState(searchSource);
|
|
630
|
+
if (sourceOfState !== searchSource) {
|
|
631
|
+
setSourceOfState(searchSource);
|
|
632
|
+
if (isLoading) {
|
|
633
|
+
setIsLoading(false);
|
|
634
|
+
setHasSearched(false);
|
|
635
|
+
}
|
|
636
|
+
loadingRef.current = false;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// When the search source is replaced, any in-flight load from the previous
|
|
640
|
+
// source is abandoned (generation bumped above). Notify the wrapper's busy lane
|
|
641
|
+
// in an effect so external store listeners run safely after commit.
|
|
642
|
+
const isInitialMountRef = useRef(true);
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
if (isInitialMountRef.current) {
|
|
645
|
+
isInitialMountRef.current = false;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!loadingRef.current) {
|
|
649
|
+
onLoadingChangeRef.current?.(false);
|
|
650
|
+
}
|
|
651
|
+
}, [searchSource]);
|
|
652
|
+
|
|
627
653
|
// Layer for dropdown
|
|
628
654
|
const handleLayerShow = useCallback(() => {
|
|
629
655
|
onOpenChange?.(true);
|
|
@@ -17,10 +17,20 @@ import {
|
|
|
17
17
|
beforeAll,
|
|
18
18
|
afterAll,
|
|
19
19
|
beforeEach,
|
|
20
|
+
afterEach,
|
|
20
21
|
} from 'vitest';
|
|
21
22
|
import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
|
|
22
23
|
import userEvent from '@testing-library/user-event';
|
|
23
|
-
import {
|
|
24
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
25
|
+
import {
|
|
26
|
+
createRef,
|
|
27
|
+
Profiler,
|
|
28
|
+
startTransition,
|
|
29
|
+
StrictMode,
|
|
30
|
+
Suspense,
|
|
31
|
+
useState,
|
|
32
|
+
type ProfilerOnRenderCallback,
|
|
33
|
+
} from 'react';
|
|
24
34
|
import {Typeahead} from './Typeahead';
|
|
25
35
|
import {readFile} from 'node:fs/promises';
|
|
26
36
|
import {resolve} from 'node:path';
|
|
@@ -70,6 +80,10 @@ afterAll(() => {
|
|
|
70
80
|
(HTMLElement.prototype as any).matches = originalMatches;
|
|
71
81
|
});
|
|
72
82
|
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
__resetLiveRegionsForTest();
|
|
85
|
+
});
|
|
86
|
+
|
|
73
87
|
// Test data
|
|
74
88
|
const fruits: SearchableItem[] = [
|
|
75
89
|
{id: '1', label: 'Apple'},
|
|
@@ -964,6 +978,193 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
964
978
|
).not.toBeInTheDocument();
|
|
965
979
|
});
|
|
966
980
|
|
|
981
|
+
it('clears the loading state when the search source is replaced during an in-flight load', async () => {
|
|
982
|
+
let settle: (items: SearchableItem[]) => void = () => {};
|
|
983
|
+
const bootstrap = vi.fn(
|
|
984
|
+
async () =>
|
|
985
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
986
|
+
settle = resolve;
|
|
987
|
+
}),
|
|
988
|
+
);
|
|
989
|
+
const initialSource: SearchSource = {search: () => [], bootstrap};
|
|
990
|
+
const replacementSource: SearchSource = {
|
|
991
|
+
search: () => [],
|
|
992
|
+
bootstrap: () => [],
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
const {rerender} = render(
|
|
996
|
+
<BaseTypeahead
|
|
997
|
+
searchSource={initialSource}
|
|
998
|
+
value={null}
|
|
999
|
+
onChange={() => {}}
|
|
1000
|
+
hasEntriesOnFocus
|
|
1001
|
+
/>,
|
|
1002
|
+
);
|
|
1003
|
+
const input = screen.getByRole('combobox');
|
|
1004
|
+
|
|
1005
|
+
fireEvent.focus(input);
|
|
1006
|
+
await waitFor(() => {
|
|
1007
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
rerender(
|
|
1011
|
+
<BaseTypeahead
|
|
1012
|
+
searchSource={replacementSource}
|
|
1013
|
+
value={null}
|
|
1014
|
+
onChange={() => {}}
|
|
1015
|
+
hasEntriesOnFocus
|
|
1016
|
+
/>,
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
await waitFor(() => {
|
|
1020
|
+
expect(
|
|
1021
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1022
|
+
).not.toBeInTheDocument();
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
await act(async () => {
|
|
1026
|
+
settle([]);
|
|
1027
|
+
await Promise.resolve();
|
|
1028
|
+
});
|
|
1029
|
+
expect(
|
|
1030
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1031
|
+
).not.toBeInTheDocument();
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
it('keeps the loading state under StrictMode for a pending autofocus bootstrap', async () => {
|
|
1035
|
+
let settle: (items: SearchableItem[]) => void = () => {};
|
|
1036
|
+
const bootstrap = vi.fn(
|
|
1037
|
+
async () =>
|
|
1038
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
1039
|
+
settle = resolve;
|
|
1040
|
+
}),
|
|
1041
|
+
);
|
|
1042
|
+
render(
|
|
1043
|
+
<StrictMode>
|
|
1044
|
+
<Typeahead
|
|
1045
|
+
label="Test"
|
|
1046
|
+
searchSource={{search: () => [], bootstrap}}
|
|
1047
|
+
value={null}
|
|
1048
|
+
onChange={() => {}}
|
|
1049
|
+
hasAutoFocus
|
|
1050
|
+
hasEntriesOnFocus
|
|
1051
|
+
/>
|
|
1052
|
+
</StrictMode>,
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1056
|
+
|
|
1057
|
+
await act(async () => {
|
|
1058
|
+
settle([]);
|
|
1059
|
+
await Promise.resolve();
|
|
1060
|
+
});
|
|
1061
|
+
expect(
|
|
1062
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1063
|
+
).not.toBeInTheDocument();
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
let settlePendingTransitionA: (items: SearchableItem[]) => void = () => {};
|
|
1067
|
+
const stableTransitionSourceA: SearchSource = {
|
|
1068
|
+
search: vi.fn(
|
|
1069
|
+
async () =>
|
|
1070
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
1071
|
+
settlePendingTransitionA = resolve;
|
|
1072
|
+
}),
|
|
1073
|
+
),
|
|
1074
|
+
bootstrap: () => [],
|
|
1075
|
+
};
|
|
1076
|
+
const stableTransitionSourceB: SearchSource = {
|
|
1077
|
+
search: vi.fn(async () => []),
|
|
1078
|
+
bootstrap: () => [],
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
it('clears loading and aria-busy when source is replaced in a transition that suspends and retries', async () => {
|
|
1082
|
+
let isSuspended = false;
|
|
1083
|
+
let resolveSuspension: () => void = () => {};
|
|
1084
|
+
let pendingSuspension: Promise<void> | null = null;
|
|
1085
|
+
|
|
1086
|
+
function SuspendingSibling({shouldSuspend}: {shouldSuspend: boolean}) {
|
|
1087
|
+
if (shouldSuspend && isSuspended && pendingSuspension) {
|
|
1088
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
1089
|
+
throw pendingSuspension;
|
|
1090
|
+
}
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
let updateState!: (source: SearchSource, shouldSuspend: boolean) => void;
|
|
1095
|
+
|
|
1096
|
+
function TransitionContainer() {
|
|
1097
|
+
const [source, setSource] = useState<SearchSource>(
|
|
1098
|
+
stableTransitionSourceA,
|
|
1099
|
+
);
|
|
1100
|
+
const [shouldSuspend, setShouldSuspend] = useState(false);
|
|
1101
|
+
updateState = (newSource, suspend) => {
|
|
1102
|
+
setSource(newSource);
|
|
1103
|
+
setShouldSuspend(suspend);
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
return (
|
|
1107
|
+
<Suspense fallback={<div>Suspended...</div>}>
|
|
1108
|
+
<Typeahead
|
|
1109
|
+
label="Test"
|
|
1110
|
+
searchSource={source}
|
|
1111
|
+
value={null}
|
|
1112
|
+
onChange={() => {}}
|
|
1113
|
+
debounceMs={0}
|
|
1114
|
+
/>
|
|
1115
|
+
<SuspendingSibling shouldSuspend={shouldSuspend} />
|
|
1116
|
+
</Suspense>
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
render(<TransitionContainer />);
|
|
1121
|
+
const input = screen.getByRole('combobox');
|
|
1122
|
+
|
|
1123
|
+
// Trigger a load so aria-busy="true" is set on the combobox
|
|
1124
|
+
fireEvent.change(input, {target: {value: 'query'}});
|
|
1125
|
+
await waitFor(() => {
|
|
1126
|
+
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
1127
|
+
});
|
|
1128
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1129
|
+
|
|
1130
|
+
// Create the pending promise for suspension
|
|
1131
|
+
isSuspended = true;
|
|
1132
|
+
pendingSuspension = new Promise<void>(resolve => {
|
|
1133
|
+
resolveSuspension = () => {
|
|
1134
|
+
isSuspended = false;
|
|
1135
|
+
resolve();
|
|
1136
|
+
};
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
// Inside startTransition, switch from source A to source B while sibling suspends
|
|
1140
|
+
act(() => {
|
|
1141
|
+
startTransition(() => {
|
|
1142
|
+
updateState(stableTransitionSourceB, true);
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
// The render suspended, so the previous committed state remains
|
|
1147
|
+
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
1148
|
+
|
|
1149
|
+
// Resolve the pending promise and let the transition commit
|
|
1150
|
+
await act(async () => {
|
|
1151
|
+
resolveSuspension();
|
|
1152
|
+
await pendingSuspension;
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// Settle the abandoned load
|
|
1156
|
+
await act(async () => {
|
|
1157
|
+
settlePendingTransitionA([]);
|
|
1158
|
+
await Promise.resolve();
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// The combobox must not keep aria-busy and the spinner must be gone
|
|
1162
|
+
expect(input).not.toHaveAttribute('aria-busy');
|
|
1163
|
+
expect(
|
|
1164
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1165
|
+
).not.toBeInTheDocument();
|
|
1166
|
+
});
|
|
1167
|
+
|
|
967
1168
|
it('shows bootstrap results on mouse click', async () => {
|
|
968
1169
|
render(
|
|
969
1170
|
<BaseTypeahead
|
|
@@ -1323,6 +1524,63 @@ describe('BaseTypeahead hasSearched reset', () => {
|
|
|
1323
1524
|
// "No results found" should disappear since hasSearched is reset
|
|
1324
1525
|
expect(screen.queryByText('No results found')).not.toBeInTheDocument();
|
|
1325
1526
|
});
|
|
1527
|
+
|
|
1528
|
+
it('resets hasSearched and does not announce "No results found" when search source is replaced during a pending search', async () => {
|
|
1529
|
+
let settle: (items: SearchableItem[]) => void = () => {};
|
|
1530
|
+
const initialSource: SearchSource = {
|
|
1531
|
+
search: vi.fn(
|
|
1532
|
+
async () =>
|
|
1533
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
1534
|
+
settle = resolve;
|
|
1535
|
+
}),
|
|
1536
|
+
),
|
|
1537
|
+
bootstrap: () => [],
|
|
1538
|
+
};
|
|
1539
|
+
const replacementSource: SearchSource = {
|
|
1540
|
+
search: vi.fn(async () => []),
|
|
1541
|
+
bootstrap: () => [],
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
const {rerender} = render(
|
|
1545
|
+
<BaseTypeahead
|
|
1546
|
+
searchSource={initialSource}
|
|
1547
|
+
value={null}
|
|
1548
|
+
onChange={() => {}}
|
|
1549
|
+
debounceMs={0}
|
|
1550
|
+
/>,
|
|
1551
|
+
);
|
|
1552
|
+
const input = screen.getByRole('combobox');
|
|
1553
|
+
|
|
1554
|
+
// Type a query that starts an asynchronous search
|
|
1555
|
+
fireEvent.change(input, {target: {value: 'abc'}});
|
|
1556
|
+
expect(initialSource.search).toHaveBeenCalledWith('abc');
|
|
1557
|
+
|
|
1558
|
+
// Replace the search source while the search is still pending
|
|
1559
|
+
rerender(
|
|
1560
|
+
<BaseTypeahead
|
|
1561
|
+
searchSource={replacementSource}
|
|
1562
|
+
value={null}
|
|
1563
|
+
onChange={() => {}}
|
|
1564
|
+
debounceMs={0}
|
|
1565
|
+
/>,
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
// Settle the abandoned search
|
|
1569
|
+
await act(async () => {
|
|
1570
|
+
settle([]);
|
|
1571
|
+
await Promise.resolve();
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
await act(
|
|
1575
|
+
async () =>
|
|
1576
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
1577
|
+
);
|
|
1578
|
+
expect(
|
|
1579
|
+
document.querySelector('[data-astryx-live-region="polite"]')
|
|
1580
|
+
?.textContent ?? '',
|
|
1581
|
+
).not.toContain('No results found');
|
|
1582
|
+
expect(screen.queryByText('No results found')).not.toBeInTheDocument();
|
|
1583
|
+
});
|
|
1326
1584
|
});
|
|
1327
1585
|
|
|
1328
1586
|
describe('BaseTypeahead popover after selection', () => {
|
|
@@ -2313,7 +2571,7 @@ describe('Typeahead consumer key handler order', () => {
|
|
|
2313
2571
|
currentTarget = event.currentTarget;
|
|
2314
2572
|
event.preventDefault();
|
|
2315
2573
|
});
|
|
2316
|
-
|
|
2574
|
+
render(
|
|
2317
2575
|
<Typeahead
|
|
2318
2576
|
label="Fruit"
|
|
2319
2577
|
searchSource={fruitSource}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
4
|
/**
|
|
5
5
|
* @file Guards doc prop types against opaque named unions (#1645).
|
|
6
|
-
* @input Every {Name}.doc.mjs plus the TypeScript sources they describe
|
|
6
|
+
* @input Every {Name}.doc.mjs plus the TypeScript sources they describe,
|
|
7
|
+
* including same-file private aliases used by exported literal unions.
|
|
7
8
|
* @output Fails when a documented prop type hides its legal literal values.
|
|
8
9
|
* @position Repo-wide doc-shape guard, sibling of docPropReferences.test.ts.
|
|
9
10
|
*
|
|
@@ -188,18 +189,34 @@ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
|
|
|
188
189
|
// markers the comment-stripped `src` has already removed.
|
|
189
190
|
const rawTypeRhs = new Map<string, string>();
|
|
190
191
|
for (const m of raw.matchAll(
|
|
191
|
-
|
|
192
|
+
/\btype ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
|
|
192
193
|
)) {
|
|
193
194
|
rawTypeRhs.set(m[1], m[2]);
|
|
194
195
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
const declarations = [
|
|
197
|
+
...src.matchAll(
|
|
198
|
+
/\b(export )?type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
|
|
199
|
+
),
|
|
200
|
+
];
|
|
201
|
+
// Resolve same-file literal aliases before registering exported types.
|
|
202
|
+
// Repeat until no new literal unions are found.
|
|
203
|
+
const fileLiterals = new Map<string, string>();
|
|
204
|
+
let previousSize: number;
|
|
205
|
+
do {
|
|
206
|
+
previousSize = fileLiterals.size;
|
|
207
|
+
for (const [, , name, rhsRaw] of declarations) {
|
|
208
|
+
const rhs = rhsRaw
|
|
209
|
+
.replace(/\s+/g, ' ')
|
|
210
|
+
.trim()
|
|
211
|
+
.split('|')
|
|
212
|
+
.map(part => fileLiterals.get(part.trim()) ?? part.trim())
|
|
213
|
+
.join(' | ');
|
|
214
|
+
|
|
215
|
+
if (!LITERAL_UNION.test(rhs)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
201
218
|
const deprecated = deprecatedLiteralMembers(rawTypeRhs.get(name) ?? '');
|
|
202
|
-
|
|
219
|
+
fileLiterals.set(
|
|
203
220
|
name,
|
|
204
221
|
rhs
|
|
205
222
|
.replace(/^\|\s*/, '')
|
|
@@ -209,8 +226,17 @@ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
|
|
|
209
226
|
.filter(member => !deprecated.has(member))
|
|
210
227
|
.join(' | '),
|
|
211
228
|
);
|
|
229
|
+
}
|
|
230
|
+
} while (fileLiterals.size !== previousSize);
|
|
231
|
+
for (const [, exported, name, rhsRaw] of declarations) {
|
|
232
|
+
if (!exported) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const literals = fileLiterals.get(name);
|
|
236
|
+
if (literals !== undefined) {
|
|
237
|
+
literalUnions.set(name, literals);
|
|
212
238
|
} else {
|
|
213
|
-
namedBodies.set(name,
|
|
239
|
+
namedBodies.set(name, rhsRaw.replace(/\s+/g, ' ').trim());
|
|
214
240
|
}
|
|
215
241
|
}
|
|
216
242
|
for (const m of src.matchAll(
|
|
@@ -18,12 +18,12 @@ export const docs = {
|
|
|
18
18
|
{
|
|
19
19
|
name: 'entryStyle',
|
|
20
20
|
type: 'StyleXStyles | null',
|
|
21
|
-
description: 'A StyleX style object for the entry animation, or null if the element was rendered on initial page load (no animation needed).',
|
|
21
|
+
description: 'A StyleX style object for the entry animation, or null if the element was rendered on initial page load or hydrated from server HTML (no animation needed).',
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
24
|
usage: {
|
|
25
25
|
description:
|
|
26
|
-
'Returns a StyleX style for animating an element on mount. Only animates when the element is dynamically inserted after the initial page paint; elements rendered on page load are not animated. Uses Astryx motion tokens (duration, easing) for consistent animation timing.
|
|
26
|
+
'Returns a StyleX style for animating an element on mount. Only animates when the element is dynamically inserted after the initial page paint; elements rendered on page load are not animated. Uses Astryx motion tokens (duration, easing) for consistent animation timing. Server-rendered elements hydrate without the entry style and never animate; elements mounted after hydration animate.',
|
|
27
27
|
bestPractices: [
|
|
28
28
|
{ guidance: true, description: 'Use for conditionally rendered elements like validation messages, toasts, or expanding sections.' },
|
|
29
29
|
{ guidance: true, description: 'Spread the returned style into stylex.props() alongside other styles.' },
|
|
@@ -39,7 +39,7 @@ export const docs = {
|
|
|
39
39
|
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
40
40
|
export const docsDense = {
|
|
41
41
|
description:
|
|
42
|
-
'Returns StyleX style for animating element on mount. Only animates when element dynamically inserted after initial page paint; elements rendered on page load not animated. Uses Astryx motion tokens (duration, easing) for consistent timing.
|
|
42
|
+
'Returns StyleX style for animating element on mount. Only animates when element dynamically inserted after initial page paint; elements rendered on page load not animated. Uses Astryx motion tokens (duration, easing) for consistent timing. Server-rendered elements hydrate without entry style, never animate; elements mounted after hydration animate.',
|
|
43
43
|
paramDescriptions: {
|
|
44
44
|
preset: 'animation preset applied on mount.',
|
|
45
45
|
},
|
|
@@ -48,7 +48,7 @@ export const docsDense = {
|
|
|
48
48
|
},
|
|
49
49
|
usage: {
|
|
50
50
|
description:
|
|
51
|
-
'Returns StyleX style for animating element on mount. Only animates when element dynamically inserted after initial page paint; elements rendered on page load not animated. Uses Astryx motion tokens (duration, easing) for consistent timing.
|
|
51
|
+
'Returns StyleX style for animating element on mount. Only animates when element dynamically inserted after initial page paint; elements rendered on page load not animated. Uses Astryx motion tokens (duration, easing) for consistent timing. Server-rendered elements hydrate without entry style, never animate; elements mounted after hydration animate.',
|
|
52
52
|
bestPractices: [
|
|
53
53
|
{ guidance: true, description: 'Use for conditionally rendered elements like validation messages, toasts, expanding sections.' },
|
|
54
54
|
{ guidance: true, description: 'Spread returned style into stylex.props() alongside other styles.' },
|