@astryxdesign/core 0.7.1 → 0.7.2-canary.020e1fe
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/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -1
- 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 +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +83 -11
- 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/DropdownMenuGroup.d.ts +6 -4
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
- 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/MobileNav/MobileNav.d.ts +9 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +30 -3
- 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/scripts/agent-doc-state.mjs +38 -0
- package/scripts/postinstall.mjs +1 -1
- package/src/AppShell/AppShell.tsx +45 -1
- 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/Card/Card.doc.mjs +1 -1
- 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/Code/Code.doc.mjs +1 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +2 -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 +473 -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.ssr.test.tsx +57 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +79 -18
- package/src/DropdownMenu/DropdownMenu.tsx +83 -12
- 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/DropdownMenuGroup.doc.mjs +169 -10
- package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
- package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
- 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__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -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/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -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/Field/FieldLabel.doc.mjs +133 -26
- package/src/Field/FieldLabel.spec.md +172 -0
- package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
- 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/Icon/Icon.doc.mjs +36 -0
- 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/LayoutHeader.doc.mjs +5 -0
- 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 +3 -3
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MobileNav/MobileNav.tsx +30 -3
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
- package/src/MoreMenu/MoreMenu.test.tsx +5 -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/ProgressBar/ProgressBar.doc.mjs +1 -0
- 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/Resizable/useResizable.doc.mjs +2 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
- package/src/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- package/src/SideNav/SideNav.spec.md +227 -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/Spinner/Spinner.doc.mjs +1 -1
- 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/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- package/src/Toolbar/Toolbar.test.tsx +0 -8
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
- 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/postinstall.test.mjs +46 -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
|
@@ -13,25 +13,86 @@ export const docs = {
|
|
|
13
13
|
defaults: {title: 'Version history'},
|
|
14
14
|
},
|
|
15
15
|
description:
|
|
16
|
-
'A
|
|
16
|
+
'A group of rows in a compound menu. A visible title names role="group" through aria-labelledby; an omitted title leaves the group unnamed. The heading is not a menuitem, so arrow keys and typeahead skip it. Keep title content non-interactive and place controls in DropdownMenu header or footer. The data-driven equivalent is the `{type: "section", title, items}` entry in `items`; both forms share the heading treatment and theme target.',
|
|
17
|
+
usage: {
|
|
18
|
+
description:
|
|
19
|
+
'Use DropdownMenuGroup to give related compound-menu rows one visible and programmatic name. Use an untitled group only when a separate name would not help people understand the rows.',
|
|
20
|
+
bestPractices: [
|
|
21
|
+
{
|
|
22
|
+
guidance: true,
|
|
23
|
+
description:
|
|
24
|
+
'Use short, descriptive title content that explains what the grouped rows have in common.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
guidance: true,
|
|
28
|
+
description:
|
|
29
|
+
'Keep the title non-interactive so every available action remains in the menu arrow-key order.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
guidance: false,
|
|
33
|
+
description:
|
|
34
|
+
'Place buttons, links, fields, or other controls in title. Put them in DropdownMenu header or footer instead.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
guidance: false,
|
|
38
|
+
description:
|
|
39
|
+
'Wrap rows that do not form a meaningful set; a group adds structure only when its relationship helps users.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
anatomy: [
|
|
43
|
+
{
|
|
44
|
+
name: 'Group',
|
|
45
|
+
required: true,
|
|
46
|
+
description:
|
|
47
|
+
'The role="group" wrapper containing the heading and menu rows.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Heading',
|
|
51
|
+
required: false,
|
|
52
|
+
description:
|
|
53
|
+
'Visible non-interactive title content that names the group and carries the dropdown-menu-section-heading theme target.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'Rows',
|
|
57
|
+
required: false,
|
|
58
|
+
description:
|
|
59
|
+
'Menu items, selectable items, radio groups, or submenus owned by DropdownMenu.',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
17
63
|
props: [
|
|
18
64
|
{
|
|
19
65
|
name: 'title',
|
|
20
66
|
type: 'ReactNode',
|
|
21
67
|
description:
|
|
22
|
-
"
|
|
68
|
+
"Non-interactive heading content shown above the rows and used as the group's accessible name. Omit for an unnamed group; place controls in DropdownMenu header or footer.",
|
|
23
69
|
},
|
|
24
70
|
{
|
|
25
71
|
name: 'children',
|
|
26
72
|
type: 'ReactNode',
|
|
27
73
|
description:
|
|
28
|
-
'The rows
|
|
74
|
+
'The grouped rows: DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, or DropdownMenuSubMenu.',
|
|
29
75
|
},
|
|
30
76
|
{
|
|
31
77
|
name: 'xstyle',
|
|
32
78
|
type: 'StyleXStyles',
|
|
33
79
|
description:
|
|
34
|
-
'StyleX styles applied to the group wrapper. Must be a stylex.create() value
|
|
80
|
+
'StyleX styles applied to the role="group" wrapper. Must be a stylex.create() value, not an inline style object.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'className',
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'CSS class name applied to the role="group" wrapper.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'style',
|
|
89
|
+
type: 'React.CSSProperties',
|
|
90
|
+
description: 'Inline styles applied to the role="group" wrapper.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'ref',
|
|
94
|
+
type: 'React.Ref<HTMLDivElement>',
|
|
95
|
+
description: 'Ref forwarded to the role="group" wrapper.',
|
|
35
96
|
},
|
|
36
97
|
],
|
|
37
98
|
theming: {
|
|
@@ -39,15 +100,113 @@ export const docs = {
|
|
|
39
100
|
},
|
|
40
101
|
};
|
|
41
102
|
|
|
42
|
-
|
|
103
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
104
|
+
export const docsZh = {
|
|
43
105
|
name: 'DropdownMenuGroup',
|
|
44
|
-
|
|
106
|
+
subComponentOf: 'DropdownMenu',
|
|
45
107
|
displayName: 'Dropdown Menu Group',
|
|
108
|
+
isHiddenFromOverview: true,
|
|
109
|
+
description:
|
|
110
|
+
'复合菜单中的一组菜单行。可见标题通过 aria-labelledby 为 role="group" 命名;省略标题时该组没有名称。标题不是 menuitem,因此方向键和预输入会跳过它。标题内容应保持非交互;控件应放在 DropdownMenu 的 header 或 footer 中。',
|
|
111
|
+
usage: {
|
|
112
|
+
description:
|
|
113
|
+
'使用 DropdownMenuGroup 为相关的复合菜单行提供同一个可见名称和无障碍名称。只有当名称能帮助理解这些菜单行时才使用分组。',
|
|
114
|
+
bestPractices: [
|
|
115
|
+
{guidance: true, description: '使用简短且能说明菜单行关系的标题。'},
|
|
116
|
+
{
|
|
117
|
+
guidance: true,
|
|
118
|
+
description: '保持标题非交互,让所有操作都位于菜单的方向键顺序中。',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
guidance: false,
|
|
122
|
+
description:
|
|
123
|
+
'在标题中放置按钮、链接或输入控件;应改用 DropdownMenu 的 header 或 footer。',
|
|
124
|
+
},
|
|
125
|
+
{guidance: false, description: '对没有共同含义的菜单行进行分组。'},
|
|
126
|
+
],
|
|
127
|
+
anatomy: [
|
|
128
|
+
{
|
|
129
|
+
name: '组',
|
|
130
|
+
required: true,
|
|
131
|
+
description: '包含标题和菜单行的 role="group" 包装元素。',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: '标题',
|
|
135
|
+
required: false,
|
|
136
|
+
description: '用于命名分组的可见非交互标题内容。',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: '菜单行',
|
|
140
|
+
required: false,
|
|
141
|
+
description: '由 DropdownMenu 持有的菜单项、可选项、单选组或子菜单。',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
props: [
|
|
146
|
+
{
|
|
147
|
+
name: 'title',
|
|
148
|
+
type: 'ReactNode',
|
|
149
|
+
description:
|
|
150
|
+
'显示在菜单行上方并作为分组无障碍名称的非交互标题内容。省略时分组无名称;控件应放在 DropdownMenu 的 header 或 footer 中。',
|
|
151
|
+
},
|
|
152
|
+
{name: 'children', type: 'ReactNode', description: '该分组中的菜单行。'},
|
|
153
|
+
{
|
|
154
|
+
name: 'xstyle',
|
|
155
|
+
type: 'StyleXStyles',
|
|
156
|
+
description: '应用于 role="group" 包装元素的 StyleX 样式。',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'className',
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: '应用于 role="group" 包装元素的 CSS 类名。',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'style',
|
|
165
|
+
type: 'React.CSSProperties',
|
|
166
|
+
description: '应用于 role="group" 包装元素的内联样式。',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'ref',
|
|
170
|
+
type: 'React.Ref<HTMLDivElement>',
|
|
171
|
+
description: '转发到 role="group" 包装元素的 ref。',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
theming: {
|
|
175
|
+
targets: [{className: 'astryx-dropdown-menu-section-heading'}],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
|
|
180
|
+
export const docsDense = {
|
|
46
181
|
description:
|
|
47
|
-
'
|
|
182
|
+
'named role="group" of compound-menu rows; compound peer of the data API\'s {type: "section"}',
|
|
183
|
+
usage: {
|
|
184
|
+
description:
|
|
185
|
+
'Use to give related compound-menu rows one visible and programmatic name.',
|
|
186
|
+
bestPractices: [
|
|
187
|
+
{
|
|
188
|
+
guidance: true,
|
|
189
|
+
description: "Short title that explains the rows' relationship.",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
guidance: true,
|
|
193
|
+
description:
|
|
194
|
+
'Non-interactive title keeps every action in menu arrow navigation.',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
guidance: false,
|
|
198
|
+
description:
|
|
199
|
+
'Controls in title. Put them in DropdownMenu header or footer instead.',
|
|
200
|
+
},
|
|
201
|
+
{guidance: false, description: 'Group unrelated rows.'},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
48
204
|
propDescriptions: {
|
|
49
|
-
title: 'heading above the rows; names the group',
|
|
50
|
-
children: 'the
|
|
51
|
-
xstyle: 'StyleX styles
|
|
205
|
+
title: 'non-interactive heading above the rows; names the group',
|
|
206
|
+
children: 'the grouped menu rows',
|
|
207
|
+
xstyle: 'StyleX styles on the role="group" wrapper',
|
|
208
|
+
className: 'CSS class on the role="group" wrapper',
|
|
209
|
+
style: 'inline styles on the role="group" wrapper',
|
|
210
|
+
ref: 'ref forwarded to the role="group" wrapper',
|
|
52
211
|
},
|
|
53
212
|
};
|
|
@@ -53,7 +53,8 @@ export interface DropdownMenuGroupProps extends Pick<
|
|
|
53
53
|
> {
|
|
54
54
|
/**
|
|
55
55
|
* Heading shown above the group's rows and used as the group's accessible
|
|
56
|
-
* name (`aria-labelledby`). Omit it for an unnamed group.
|
|
56
|
+
* name (`aria-labelledby`). Omit it for an unnamed group. Keep this content
|
|
57
|
+
* non-interactive; controls belong in DropdownMenu's `header` or `footer`.
|
|
57
58
|
*/
|
|
58
59
|
title?: ReactNode;
|
|
59
60
|
/** The rows of the group: menu items, selectable items, sub-menus. */
|
|
@@ -65,9 +66,10 @@ export interface DropdownMenuGroupProps extends Pick<
|
|
|
65
66
|
/**
|
|
66
67
|
* A titled group of menu rows.
|
|
67
68
|
*
|
|
68
|
-
* Renders `role="group"` named by its heading. The heading is
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
* Renders `role="group"` named by its heading. The heading is not a
|
|
70
|
+
* `menuitem`, so arrow-key navigation and typeahead skip it. Keep heading
|
|
71
|
+
* content non-interactive; place controls in DropdownMenu's `header` or
|
|
72
|
+
* `footer`. The compound peer of
|
|
71
73
|
* `{type: 'section', title, items}` in the `items` data API.
|
|
72
74
|
*
|
|
73
75
|
* @example
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file DropdownMenuLazySheet.test.tsx
|
|
5
|
+
* @input Uses DropdownMenu, Testing Library, and a held module load
|
|
6
|
+
* @output Tests DropdownMenu while its on-demand touch sheet is still loading
|
|
7
|
+
* @position Test file for DropdownMenu
|
|
8
|
+
*
|
|
9
|
+
* This file never warms the sheet, and it checks that a popover menu never
|
|
10
|
+
* requests it: each test holds the sheet modules until it
|
|
11
|
+
* releases them, so it exercises the path a real page takes while the sheet's
|
|
12
|
+
* code is still downloading.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {describe, it, expect, vi, beforeAll, beforeEach} from 'vitest';
|
|
16
|
+
import {render, screen, waitFor, act} from '@testing-library/react';
|
|
17
|
+
import userEvent from '@testing-library/user-event';
|
|
18
|
+
import type {DropdownMenu as DropdownMenuComponent} from './DropdownMenu';
|
|
19
|
+
|
|
20
|
+
const sheetLoad = vi.hoisted(() => {
|
|
21
|
+
let release: () => void = () => {};
|
|
22
|
+
const gate = new Promise<void>(resolve => {
|
|
23
|
+
release = resolve;
|
|
24
|
+
});
|
|
25
|
+
return {gate, release: () => release(), requested: [] as string[]};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
vi.mock('./MenuBottomSheet', async importOriginal => {
|
|
29
|
+
sheetLoad.requested.push('MenuBottomSheet');
|
|
30
|
+
await sheetLoad.gate;
|
|
31
|
+
return importOriginal();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
vi.mock('./MenuBottomSheetActionList', async importOriginal => {
|
|
35
|
+
sheetLoad.requested.push('MenuBottomSheetActionList');
|
|
36
|
+
await sheetLoad.gate;
|
|
37
|
+
return importOriginal();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let DropdownMenu: typeof DropdownMenuComponent;
|
|
41
|
+
|
|
42
|
+
// If any module the dropdown loads imported the sheet eagerly, this import
|
|
43
|
+
// would wait on the held sheet and the hook would time out.
|
|
44
|
+
beforeAll(async () => {
|
|
45
|
+
({DropdownMenu} = await import('./DropdownMenu'));
|
|
46
|
+
}, 10000);
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
50
|
+
this: HTMLDialogElement,
|
|
51
|
+
) {
|
|
52
|
+
this.setAttribute('open', '');
|
|
53
|
+
});
|
|
54
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
55
|
+
this.removeAttribute('open');
|
|
56
|
+
});
|
|
57
|
+
vi.stubGlobal(
|
|
58
|
+
'matchMedia',
|
|
59
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
60
|
+
matches: false,
|
|
61
|
+
media: query,
|
|
62
|
+
onchange: null,
|
|
63
|
+
addEventListener: vi.fn(),
|
|
64
|
+
removeEventListener: vi.fn(),
|
|
65
|
+
addListener: vi.fn(),
|
|
66
|
+
removeListener: vi.fn(),
|
|
67
|
+
dispatchEvent: vi.fn(),
|
|
68
|
+
})),
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('DropdownMenu touch sheet loaded on demand', () => {
|
|
73
|
+
// Runs first: nothing in this file has rendered a sheet yet.
|
|
74
|
+
it('never loads the sheet for a popover menu', async () => {
|
|
75
|
+
const user = userEvent.setup();
|
|
76
|
+
HTMLElement.prototype.showPopover = vi.fn();
|
|
77
|
+
HTMLElement.prototype.hidePopover = vi.fn();
|
|
78
|
+
render(
|
|
79
|
+
<DropdownMenu
|
|
80
|
+
button={{label: 'Actions'}}
|
|
81
|
+
items={[{label: 'Edit project'}, {label: 'Delete'}]}
|
|
82
|
+
/>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await user.click(screen.getByRole('button', {name: /Actions/}));
|
|
86
|
+
await act(async () => {
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Importing DropdownMenu and opening a popover menu requested neither
|
|
91
|
+
// sheet module, so no other module the dropdown loads imports it eagerly.
|
|
92
|
+
expect(sheetLoad.requested).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('focuses the first action when a keyboard open beats the sheet load', async () => {
|
|
96
|
+
const user = userEvent.setup();
|
|
97
|
+
render(
|
|
98
|
+
<DropdownMenu
|
|
99
|
+
button={{label: 'Actions'}}
|
|
100
|
+
presentation="bottom-sheet"
|
|
101
|
+
items={[{label: 'Edit project'}, {label: 'Delete'}]}
|
|
102
|
+
/>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const trigger = screen.getByRole('button', {name: /Actions/});
|
|
106
|
+
trigger.focus();
|
|
107
|
+
await user.keyboard('{Enter}');
|
|
108
|
+
// The open has committed, but the sheet's code has not arrived yet.
|
|
109
|
+
await act(async () => {
|
|
110
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
111
|
+
});
|
|
112
|
+
expect(screen.queryByRole('button', {name: 'Edit project'})).toBeNull();
|
|
113
|
+
|
|
114
|
+
await act(async () => {
|
|
115
|
+
sheetLoad.release();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await waitFor(
|
|
119
|
+
() =>
|
|
120
|
+
expect(
|
|
121
|
+
screen.getByRole('button', {name: 'Edit project'}),
|
|
122
|
+
).toHaveFocus(),
|
|
123
|
+
{timeout: 15000},
|
|
124
|
+
);
|
|
125
|
+
}, 20000);
|
|
126
|
+
});
|
|
@@ -1494,3 +1494,188 @@ describe('DropdownMenuSubMenu drill-in on a phone', () => {
|
|
|
1494
1494
|
screen.getAllByRole('menu', {name, ...h}).find(isShown);
|
|
1495
1495
|
});
|
|
1496
1496
|
});
|
|
1497
|
+
|
|
1498
|
+
describe('DropdownMenuSubMenu light dismiss of the root menu (#6893)', () => {
|
|
1499
|
+
// The browser closes the root `popover=\"auto\"` element itself on an outside
|
|
1500
|
+
// tap; that path never reaches the submenu, whose flyout is
|
|
1501
|
+
// `popover=\"manual\"`. Simulate it by hiding the root popover element
|
|
1502
|
+
// directly, exactly what light dismiss does to it.
|
|
1503
|
+
function lightDismissRootMenu() {
|
|
1504
|
+
const menu = screen.getByRole('menu', {name: /Actions/, hidden: true});
|
|
1505
|
+
const rootPopover = menu.closest<HTMLElement>('[popover]');
|
|
1506
|
+
expect(rootPopover).not.toBeNull();
|
|
1507
|
+
act(() => {
|
|
1508
|
+
rootPopover!.hidePopover();
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function getFlyoutPopover(name: RegExp) {
|
|
1513
|
+
const flyout = screen.getByRole('menu', {name, hidden: true});
|
|
1514
|
+
return flyout.closest<HTMLElement>('[popover]');
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
it('closes an open flyout when the root menu is light-dismissed', async () => {
|
|
1518
|
+
const user = userEvent.setup();
|
|
1519
|
+
render(<MoveMenu />);
|
|
1520
|
+
await user.click(screen.getByRole('button', {name: /Actions/}));
|
|
1521
|
+
const trigger = screen.getByRole('menuitem', {
|
|
1522
|
+
name: /Move to/,
|
|
1523
|
+
hidden: true,
|
|
1524
|
+
});
|
|
1525
|
+
await user.click(trigger);
|
|
1526
|
+
await waitFor(() => {
|
|
1527
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
1528
|
+
});
|
|
1529
|
+
const flyoutPopover = getFlyoutPopover(/Move to/);
|
|
1530
|
+
expect(flyoutPopover).toHaveAttribute('popover-open', '');
|
|
1531
|
+
|
|
1532
|
+
lightDismissRootMenu();
|
|
1533
|
+
|
|
1534
|
+
await waitFor(() => {
|
|
1535
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1536
|
+
});
|
|
1537
|
+
expect(flyoutPopover).not.toHaveAttribute('popover-open');
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
it('leaves the submenu collapsed when the menu reopens after a light dismiss', async () => {
|
|
1541
|
+
const user = userEvent.setup();
|
|
1542
|
+
render(<MoveMenu />);
|
|
1543
|
+
await user.click(screen.getByRole('button', {name: /Actions/}));
|
|
1544
|
+
const trigger = screen.getByRole('menuitem', {
|
|
1545
|
+
name: /Move to/,
|
|
1546
|
+
hidden: true,
|
|
1547
|
+
});
|
|
1548
|
+
await user.click(trigger);
|
|
1549
|
+
await waitFor(() => {
|
|
1550
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
lightDismissRootMenu();
|
|
1554
|
+
await user.click(screen.getByRole('button', {name: /Actions/}));
|
|
1555
|
+
|
|
1556
|
+
await waitFor(() => {
|
|
1557
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1558
|
+
});
|
|
1559
|
+
expect(getFlyoutPopover(/Move to/)).not.toHaveAttribute('popover-open');
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it('closes an open flyout when the root menu closes via controlled isMenuOpen', async () => {
|
|
1563
|
+
const user = userEvent.setup();
|
|
1564
|
+
function ControlledMenu() {
|
|
1565
|
+
const [open, setOpen] = useState(true);
|
|
1566
|
+
return (
|
|
1567
|
+
<>
|
|
1568
|
+
<DropdownMenu
|
|
1569
|
+
button={{label: 'Actions'}}
|
|
1570
|
+
isMenuOpen={open}
|
|
1571
|
+
onOpenChange={setOpen}>
|
|
1572
|
+
<DropdownMenuSubMenu label="Move to">
|
|
1573
|
+
<DropdownMenuItem label="Folder A" onClick={() => {}} />
|
|
1574
|
+
</DropdownMenuSubMenu>
|
|
1575
|
+
</DropdownMenu>
|
|
1576
|
+
<button type="button" onClick={() => setOpen(false)}>
|
|
1577
|
+
close it
|
|
1578
|
+
</button>
|
|
1579
|
+
</>
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
render(<ControlledMenu />);
|
|
1583
|
+
const trigger = screen.getByRole('menuitem', {
|
|
1584
|
+
name: /Move to/,
|
|
1585
|
+
hidden: true,
|
|
1586
|
+
});
|
|
1587
|
+
await user.click(trigger);
|
|
1588
|
+
await waitFor(() => {
|
|
1589
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
1590
|
+
});
|
|
1591
|
+
const flyoutPopover = getFlyoutPopover(/Move to/);
|
|
1592
|
+
|
|
1593
|
+
await user.click(screen.getByRole('button', {name: /close it/}));
|
|
1594
|
+
|
|
1595
|
+
await waitFor(() => {
|
|
1596
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1597
|
+
});
|
|
1598
|
+
expect(flyoutPopover).not.toHaveAttribute('popover-open');
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
it('closes flyouts nested two levels deep on light dismiss', async () => {
|
|
1602
|
+
const user = userEvent.setup();
|
|
1603
|
+
render(
|
|
1604
|
+
<DropdownMenu button={{label: 'Actions'}}>
|
|
1605
|
+
<DropdownMenuSubMenu label="Move to">
|
|
1606
|
+
<DropdownMenuSubMenu label="Deeper">
|
|
1607
|
+
<DropdownMenuItem label="Leaf" onClick={() => {}} />
|
|
1608
|
+
</DropdownMenuSubMenu>
|
|
1609
|
+
</DropdownMenuSubMenu>
|
|
1610
|
+
</DropdownMenu>,
|
|
1611
|
+
);
|
|
1612
|
+
await user.click(screen.getByRole('button', {name: /Actions/}));
|
|
1613
|
+
const first = screen.getByRole('menuitem', {
|
|
1614
|
+
name: /Move to/,
|
|
1615
|
+
hidden: true,
|
|
1616
|
+
});
|
|
1617
|
+
await user.click(first);
|
|
1618
|
+
await waitFor(() => {
|
|
1619
|
+
expect(first).toHaveAttribute('aria-expanded', 'true');
|
|
1620
|
+
});
|
|
1621
|
+
const deeper = screen.getByRole('menuitem', {
|
|
1622
|
+
name: /Deeper/,
|
|
1623
|
+
hidden: true,
|
|
1624
|
+
});
|
|
1625
|
+
await user.click(deeper);
|
|
1626
|
+
await waitFor(() => {
|
|
1627
|
+
expect(deeper).toHaveAttribute('aria-expanded', 'true');
|
|
1628
|
+
});
|
|
1629
|
+
const firstPopover = getFlyoutPopover(/Move to/);
|
|
1630
|
+
const deeperPopover = getFlyoutPopover(/Deeper/);
|
|
1631
|
+
|
|
1632
|
+
lightDismissRootMenu();
|
|
1633
|
+
|
|
1634
|
+
await waitFor(() => {
|
|
1635
|
+
expect(first).toHaveAttribute('aria-expanded', 'false');
|
|
1636
|
+
});
|
|
1637
|
+
await waitFor(() => {
|
|
1638
|
+
expect(deeper).toHaveAttribute('aria-expanded', 'false');
|
|
1639
|
+
});
|
|
1640
|
+
expect(firstPopover).not.toHaveAttribute('popover-open');
|
|
1641
|
+
expect(deeperPopover).not.toHaveAttribute('popover-open');
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
// Regression for the stale hover timer: a hover over the submenu trigger
|
|
1645
|
+
// schedules an open after the show delay. A press on the same trigger
|
|
1646
|
+
// cancels its own scheduled hover-open (that is the click guard), so the
|
|
1647
|
+
// way a timer is still pending at close time is a close that comes from
|
|
1648
|
+
// elsewhere — here the root menu's light dismiss, the #6893 case. The
|
|
1649
|
+
// close must cancel the scheduled hover-open: once the delay elapses the
|
|
1650
|
+
// flyout stays closed instead of reopening behind the dismissed menu.
|
|
1651
|
+
// Time is driven manually so the dismiss lands inside the show delay;
|
|
1652
|
+
// auto-advancing timers would let the hover-open fire before it.
|
|
1653
|
+
it('cancels a hover-open scheduled before the menu was closed from elsewhere', async () => {
|
|
1654
|
+
vi.useFakeTimers();
|
|
1655
|
+
try {
|
|
1656
|
+
render(<MoveMenu />);
|
|
1657
|
+
fireEvent.click(screen.getByRole('button', {name: /Actions/}));
|
|
1658
|
+
const trigger = screen.getByRole('menuitem', {
|
|
1659
|
+
name: /Move to/,
|
|
1660
|
+
hidden: true,
|
|
1661
|
+
});
|
|
1662
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1663
|
+
|
|
1664
|
+
// Schedules the hover-open (default show delay 150ms).
|
|
1665
|
+
fireEvent.mouseEnter(trigger);
|
|
1666
|
+
|
|
1667
|
+
// Close from elsewhere while the hover-open is still pending.
|
|
1668
|
+
lightDismissRootMenu();
|
|
1669
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1670
|
+
|
|
1671
|
+
// The scheduled delay passes; the flyout must stay closed.
|
|
1672
|
+
await act(async () => {
|
|
1673
|
+
vi.advanceTimersByTime(300);
|
|
1674
|
+
});
|
|
1675
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1676
|
+
expect(getFlyoutPopover(/Move to/)).not.toHaveAttribute('popover-open');
|
|
1677
|
+
} finally {
|
|
1678
|
+
vi.useRealTimers();
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
@@ -97,7 +97,9 @@ import {
|
|
|
97
97
|
} from './menuItemRoles';
|
|
98
98
|
import {
|
|
99
99
|
DropdownMenuContext,
|
|
100
|
+
DropdownMenuRootOpenContext,
|
|
100
101
|
useDropdownMenuContext,
|
|
102
|
+
useDropdownMenuRootOpen,
|
|
101
103
|
type DropdownMenuContextValue,
|
|
102
104
|
} from './DropdownMenuContext';
|
|
103
105
|
import {focusMenuItemOnHover} from './menuItemHover';
|
|
@@ -166,8 +168,8 @@ const flyoutStyles = stylex.create({
|
|
|
166
168
|
flexDirection: 'column',
|
|
167
169
|
gap: spacingVars['--spacing-0-5'],
|
|
168
170
|
maxInlineSize: stylex.firstThatWorks(
|
|
169
|
-
layerViewportInset.maxInlineSize
|
|
170
|
-
layerViewportInset.maxInlineSizeFallback
|
|
171
|
+
`calc(${layerViewportInset.maxInlineSize})`,
|
|
172
|
+
`calc(${layerViewportInset.maxInlineSizeFallback})`,
|
|
171
173
|
),
|
|
172
174
|
maxHeight: stylex.firstThatWorks(
|
|
173
175
|
MENU_MAX_BLOCK_SIZE,
|
|
@@ -451,13 +453,17 @@ export function DropdownMenuSubMenu(
|
|
|
451
453
|
// Hover intent and the shared hover→click guard only: this level owns its own
|
|
452
454
|
// click handling, roving focus and typeahead. popover="manual", so the
|
|
453
455
|
// invoker wiring other consumers need does not apply. A drill-in row has no
|
|
454
|
-
// hover: a finger drives it, and a press opens it.
|
|
456
|
+
// hover: a finger drives it, and a press opens it. Hover is also gated on
|
|
457
|
+
// the root menu being open, so a hover-open scheduled before the menu
|
|
458
|
+
// closed from elsewhere is cancelled instead of firing into the hidden
|
|
459
|
+
// menu (#6893).
|
|
460
|
+
const isRootMenuOpen = useDropdownMenuRootOpen();
|
|
455
461
|
const {triggerProps, contentProps, confirmHoverOpen} =
|
|
456
462
|
useMenuHover<HTMLDivElement>({
|
|
457
463
|
show: showLayer,
|
|
458
464
|
hide: hideLayer,
|
|
459
465
|
isOpen: isFlyoutOpen,
|
|
460
|
-
isEnabled: canOpen && !isDrillIn,
|
|
466
|
+
isEnabled: canOpen && !isDrillIn && isRootMenuOpen,
|
|
461
467
|
// The safe triangle toward the flyout's near edge is built from the
|
|
462
468
|
// flyout this component renders, not the hook's own list ref.
|
|
463
469
|
flyoutRef: menuRef,
|
|
@@ -770,7 +776,7 @@ export function DropdownMenuSubMenu(
|
|
|
770
776
|
: flyoutStyles.popoverCustomWidth(resolvedMenuWidth.value)
|
|
771
777
|
: flyoutStyles.popover;
|
|
772
778
|
|
|
773
|
-
const
|
|
779
|
+
const nestedRows = (
|
|
774
780
|
<DropdownMenuContext value={nestedMenuContext}>
|
|
775
781
|
{isDrillIn && (
|
|
776
782
|
<DropdownMenuItem
|
|
@@ -790,6 +796,14 @@ export function DropdownMenuSubMenu(
|
|
|
790
796
|
{children}
|
|
791
797
|
</DropdownMenuContext>
|
|
792
798
|
);
|
|
799
|
+
// Nested rows live in this level's flyout, a layer of their own where the
|
|
800
|
+
// layer-scoped signal resets; re-provide it so their pending hover intent
|
|
801
|
+
// dies with the root menu too.
|
|
802
|
+
const nestedContent = (
|
|
803
|
+
<DropdownMenuRootOpenContext value={isRootMenuOpen}>
|
|
804
|
+
{nestedRows}
|
|
805
|
+
</DropdownMenuRootOpenContext>
|
|
806
|
+
);
|
|
793
807
|
|
|
794
808
|
return (
|
|
795
809
|
<>
|