@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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Menu.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built menu stories and MENU_PATTERN in real Chromium
|
|
6
|
+
* @output Browser evidence for every menu expectation across the DropdownMenu pointer-menu states
|
|
7
|
+
* @position Chromium lane of the DropdownMenu menu-surface binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
expect,
|
|
12
|
+
test,
|
|
13
|
+
type CDPSession,
|
|
14
|
+
type Locator,
|
|
15
|
+
type Page,
|
|
16
|
+
} from '@playwright/test';
|
|
17
|
+
import {
|
|
18
|
+
MENU_PATTERN,
|
|
19
|
+
blockingResults,
|
|
20
|
+
checkAccessibilitySpec,
|
|
21
|
+
formatFailures,
|
|
22
|
+
neverExercised,
|
|
23
|
+
summarize,
|
|
24
|
+
unmatchedKnownFailures,
|
|
25
|
+
type BindingResult,
|
|
26
|
+
} from '@astryxdesign/a11y-spec';
|
|
27
|
+
import {
|
|
28
|
+
createChromiumHarness,
|
|
29
|
+
holdMotionStill,
|
|
30
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
31
|
+
import {
|
|
32
|
+
DEFAULT_STORYBOOK_DIR,
|
|
33
|
+
serveStorybook,
|
|
34
|
+
type StaticServer,
|
|
35
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
36
|
+
import {MENU_KNOWN_FAILURES} from './Menu.a11y.known-failures';
|
|
37
|
+
import {MENU_BINDING_STATES, type MenuBindingState} from './Menu.a11y.states';
|
|
38
|
+
|
|
39
|
+
let storybook: StaticServer;
|
|
40
|
+
|
|
41
|
+
test.beforeAll(async () => {
|
|
42
|
+
storybook = await serveStorybook(
|
|
43
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test.afterAll(async () => {
|
|
48
|
+
await storybook?.close();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function storyUrl(storyId: string): string {
|
|
52
|
+
return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Opens the menu the way a keyboard user does — Enter on the focused trigger —
|
|
57
|
+
* and waits until focus is on the first item, so every expectation starts from
|
|
58
|
+
* a displayed menu with focus inside it.
|
|
59
|
+
*/
|
|
60
|
+
async function mountState(
|
|
61
|
+
page: Page,
|
|
62
|
+
state: MenuBindingState,
|
|
63
|
+
): Promise<{subject: Locator; related: Record<string, Locator>}> {
|
|
64
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
65
|
+
await holdMotionStill(page);
|
|
66
|
+
const opener = page
|
|
67
|
+
.locator('#storybook-root')
|
|
68
|
+
.getByRole('button', {name: state.label});
|
|
69
|
+
await opener.focus();
|
|
70
|
+
await page.keyboard.press('Enter');
|
|
71
|
+
const id = await opener.getAttribute('aria-controls');
|
|
72
|
+
if (id == null) {
|
|
73
|
+
throw new Error(`menu state "${state.id}" has no aria-controls`);
|
|
74
|
+
}
|
|
75
|
+
const related: Record<string, Locator> = {opener};
|
|
76
|
+
for (const [relation, [role, name]] of Object.entries(state.items)) {
|
|
77
|
+
related[relation] = page.getByRole(
|
|
78
|
+
role as Parameters<Page['getByRole']>[0],
|
|
79
|
+
{
|
|
80
|
+
name,
|
|
81
|
+
exact: true,
|
|
82
|
+
includeHidden: true,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
await expect(related['first-item']).toBeFocused();
|
|
87
|
+
return {subject: page.locator(`[id="${id}"]`), related};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* How many item actions the story's handlers have run. The story renders the
|
|
92
|
+
* count as an attribute, so this reads a fact the page shows rather than
|
|
93
|
+
* instrumenting it from the test.
|
|
94
|
+
*/
|
|
95
|
+
async function activationsOn(page: Page): Promise<number> {
|
|
96
|
+
const raw = await page
|
|
97
|
+
.locator('[data-a11y-activations]')
|
|
98
|
+
.first()
|
|
99
|
+
.getAttribute('data-a11y-activations');
|
|
100
|
+
return Number(raw ?? '0');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function runState(
|
|
104
|
+
page: Page,
|
|
105
|
+
cdp: CDPSession,
|
|
106
|
+
state: MenuBindingState,
|
|
107
|
+
): Promise<BindingResult> {
|
|
108
|
+
return checkAccessibilitySpec({
|
|
109
|
+
spec: MENU_PATTERN,
|
|
110
|
+
binding: 'DropdownMenu.menu',
|
|
111
|
+
state: state.id,
|
|
112
|
+
facts: state.facts,
|
|
113
|
+
knownFailures: MENU_KNOWN_FAILURES,
|
|
114
|
+
mount: async () => {
|
|
115
|
+
const {subject, related} = await mountState(page, state);
|
|
116
|
+
return createChromiumHarness({page, cdp, subject, related});
|
|
117
|
+
},
|
|
118
|
+
activations: async () => activationsOn(page),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
test('every menu expectation is exercised by a DropdownMenu state', async ({
|
|
123
|
+
page,
|
|
124
|
+
}) => {
|
|
125
|
+
const cdp = await page.context().newCDPSession(page);
|
|
126
|
+
const results: BindingResult[] = [];
|
|
127
|
+
for (const state of MENU_BINDING_STATES) {
|
|
128
|
+
results.push(await runState(page, cdp, state));
|
|
129
|
+
}
|
|
130
|
+
expect(neverExercised(results)).toEqual([]);
|
|
131
|
+
expect(unmatchedKnownFailures(MENU_KNOWN_FAILURES, results)).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
for (const state of MENU_BINDING_STATES) {
|
|
135
|
+
test(`DropdownMenu.menu [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
136
|
+
const cdp = await page.context().newCDPSession(page);
|
|
137
|
+
const result = await runState(page, cdp, state);
|
|
138
|
+
const report = summarize(MENU_PATTERN, [result]);
|
|
139
|
+
// Required rows block (AST-020 FR9); advisory rows and exact known
|
|
140
|
+
// failures report.
|
|
141
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
142
|
+
expect(report.unrunLayers).toEqual([]);
|
|
143
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Menu.a11y.known-failures.ts
|
|
5
|
+
* @input The exact menu-contract failures the shipped DropdownMenu produces today
|
|
6
|
+
* @output MENU_KNOWN_FAILURES — records both lanes reconcile against, so a fix shows up as an unexpected pass and any other failure stays a failure
|
|
7
|
+
* @position Known-failure ledger for the DropdownMenu menu-surface binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export const MENU_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
|
|
13
|
+
{
|
|
14
|
+
expectation: 'menu.keys.enter-activates-and-closes',
|
|
15
|
+
binding: 'DropdownMenu.menu',
|
|
16
|
+
state: 'dropdown-selectable-checkbox-row',
|
|
17
|
+
evidenceLayer: 'real-browser',
|
|
18
|
+
failureEquals: 'Enter on an item left the menu displayed',
|
|
19
|
+
standardsReference:
|
|
20
|
+
'APG Menu and Menubar, Enter: "Otherwise, activates the item and closes the menu" (adopted as a required part by spec:AST-020 FR15); WCAG 2.2 2.1.1 Keyboard',
|
|
21
|
+
userImpact:
|
|
22
|
+
'A keyboard user who presses Enter on a checkbox row toggles it but stays in the open menu, where the pattern says Enter chooses and closes; Escape or Tab is needed to get back to the page.',
|
|
23
|
+
reason:
|
|
24
|
+
'DropdownMenuCheckboxItem keeps the menu open by default (hasCloseOnSelect is false), shipped as released behavior under component:DropdownMenu FR16. Whether Enter on a checkbox row closes the menu, or the component record names staying open as an exception, is a behavior decision for the component owner; this test-only binding records it and changes nothing.',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file Menu.a11y.renders.tsx
|
|
7
|
+
* @input One menu binding state from Menu.a11y.states.ts
|
|
8
|
+
* @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook), publishing how many item actions have run as `data-a11y-activations`
|
|
9
|
+
* @position Shared fixture for the DropdownMenu menu-surface binding
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {useState, type ReactElement} from 'react';
|
|
13
|
+
import {DropdownMenu} from '../DropdownMenu';
|
|
14
|
+
import {DropdownMenuItem} from '../DropdownMenuItem';
|
|
15
|
+
import {DropdownMenuCheckboxItem} from '../DropdownMenuCheckboxItem';
|
|
16
|
+
import {DropdownMenuRadioGroup} from '../DropdownMenuRadioGroup';
|
|
17
|
+
import {DropdownMenuRadioItem} from '../DropdownMenuRadioItem';
|
|
18
|
+
import {MENU_ACTION_ITEMS, type MenuBindingState} from './Menu.a11y.states';
|
|
19
|
+
|
|
20
|
+
function MenuState({
|
|
21
|
+
content,
|
|
22
|
+
label,
|
|
23
|
+
}: Pick<MenuBindingState, 'content' | 'label'>): ReactElement {
|
|
24
|
+
// An item's action leaves no trace on the menu, so every handler counts
|
|
25
|
+
// here and the count is rendered for the browser lane to read.
|
|
26
|
+
const [activations, setActivations] = useState(0);
|
|
27
|
+
const activate = () => setActivations(count => count + 1);
|
|
28
|
+
const menu =
|
|
29
|
+
content === 'actions' ? (
|
|
30
|
+
<DropdownMenu
|
|
31
|
+
button={{label}}
|
|
32
|
+
items={MENU_ACTION_ITEMS.map(item => ({...item, onClick: activate}))}
|
|
33
|
+
presentation="popover"
|
|
34
|
+
/>
|
|
35
|
+
) : (
|
|
36
|
+
<DropdownMenu button={{label}} presentation="popover">
|
|
37
|
+
<DropdownMenuItem label="Edit" onClick={activate} />
|
|
38
|
+
{/* No hasCloseOnSelect: the row keeps the menu open by default. */}
|
|
39
|
+
<DropdownMenuCheckboxItem
|
|
40
|
+
label="Show archived"
|
|
41
|
+
value={true}
|
|
42
|
+
onChange={activate}
|
|
43
|
+
/>
|
|
44
|
+
<DropdownMenuRadioGroup
|
|
45
|
+
value="newest"
|
|
46
|
+
onChange={activate}
|
|
47
|
+
label="Sort by">
|
|
48
|
+
<DropdownMenuRadioItem value="newest" label="Newest" />
|
|
49
|
+
<DropdownMenuRadioItem value="oldest" label="Oldest" />
|
|
50
|
+
</DropdownMenuRadioGroup>
|
|
51
|
+
</DropdownMenu>
|
|
52
|
+
);
|
|
53
|
+
return (
|
|
54
|
+
<div data-a11y-activations={activations}>
|
|
55
|
+
{menu}
|
|
56
|
+
<button type="button">After the menu</button>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function renderMenuState(
|
|
62
|
+
state: Pick<MenuBindingState, 'content' | 'label'>,
|
|
63
|
+
): ReactElement {
|
|
64
|
+
return <MenuState content={state.content} label={state.label} />;
|
|
65
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Menu.a11y.states.ts
|
|
5
|
+
* @input The DropdownMenu pointer menu's representative open states and the facts MENU_PATTERN reads
|
|
6
|
+
* @output MENU_BINDING_STATES and the explicit ownership exclusions for this binding
|
|
7
|
+
* @position Binding inventory shared by the jsdom test and the Chromium spec (component:DropdownMenu menu surface under spec:AST-020 FR15)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {MenuStateFacts} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export interface MenuBindingState {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly summary: string;
|
|
15
|
+
readonly storyId: string;
|
|
16
|
+
/** Which menu content the state renders. */
|
|
17
|
+
readonly content: 'actions' | 'selectable';
|
|
18
|
+
/** The trigger's accessible name; the menu is named by it. */
|
|
19
|
+
readonly label: string;
|
|
20
|
+
/** Relation name → [role, accessible name] of the item it designates. */
|
|
21
|
+
readonly items: Readonly<Record<string, readonly [string, string]>>;
|
|
22
|
+
readonly facts: MenuStateFacts;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const MENU_ACTION_ITEMS = [
|
|
26
|
+
{label: 'Rename'},
|
|
27
|
+
{label: 'Duplicate'},
|
|
28
|
+
{label: 'Archive', isDisabled: true},
|
|
29
|
+
{label: 'Delete'},
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
const story = (id: string) => `a11y-menu--${id}`;
|
|
33
|
+
|
|
34
|
+
export const MENU_BINDING_STATES = [
|
|
35
|
+
{
|
|
36
|
+
id: 'dropdown-actions',
|
|
37
|
+
summary:
|
|
38
|
+
'a DropdownMenu of four actions, one disabled, opened from its Button trigger',
|
|
39
|
+
storyId: story('dropdown-actions'),
|
|
40
|
+
content: 'actions',
|
|
41
|
+
label: 'Actions',
|
|
42
|
+
items: {
|
|
43
|
+
'first-item': ['menuitem', 'Rename'],
|
|
44
|
+
'second-item': ['menuitem', 'Duplicate'],
|
|
45
|
+
'last-item': ['menuitem', 'Delete'],
|
|
46
|
+
'disabled-item': ['menuitem', 'Archive'],
|
|
47
|
+
'typeahead-target': ['menuitem', 'Duplicate'],
|
|
48
|
+
},
|
|
49
|
+
facts: {
|
|
50
|
+
enterTarget: 'first-item',
|
|
51
|
+
wraps: true,
|
|
52
|
+
homeEnd: true,
|
|
53
|
+
disabledItem: true,
|
|
54
|
+
checked: {},
|
|
55
|
+
typeaheadCharacter: 'd',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'dropdown-selectable',
|
|
60
|
+
summary:
|
|
61
|
+
'a DropdownMenu holding an action, a checked checkbox row and a titled radio group',
|
|
62
|
+
storyId: story('dropdown-selectable'),
|
|
63
|
+
content: 'selectable',
|
|
64
|
+
label: 'View',
|
|
65
|
+
items: {
|
|
66
|
+
'first-item': ['menuitem', 'Edit'],
|
|
67
|
+
'second-item': ['menuitemcheckbox', 'Show archived'],
|
|
68
|
+
'last-item': ['menuitemradio', 'Oldest'],
|
|
69
|
+
'checkbox-item': ['menuitemcheckbox', 'Show archived'],
|
|
70
|
+
'radio-item': ['menuitemradio', 'Newest'],
|
|
71
|
+
'unchecked-radio-item': ['menuitemradio', 'Oldest'],
|
|
72
|
+
'typeahead-target': ['menuitemradio', 'Oldest'],
|
|
73
|
+
},
|
|
74
|
+
facts: {
|
|
75
|
+
enterTarget: 'first-item',
|
|
76
|
+
wraps: true,
|
|
77
|
+
homeEnd: true,
|
|
78
|
+
disabledItem: false,
|
|
79
|
+
checked: {
|
|
80
|
+
'checkbox-item': true,
|
|
81
|
+
'radio-item': true,
|
|
82
|
+
'unchecked-radio-item': false,
|
|
83
|
+
},
|
|
84
|
+
typeaheadCharacter: 'o',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'dropdown-selectable-checkbox-row',
|
|
89
|
+
summary:
|
|
90
|
+
'the same selectable menu with Enter pressed on its checkbox row, which keeps the menu open (DropdownMenuCheckboxItem hasCloseOnSelect defaults to false)',
|
|
91
|
+
storyId: story('dropdown-selectable-checkbox-row'),
|
|
92
|
+
content: 'selectable',
|
|
93
|
+
label: 'View',
|
|
94
|
+
items: {
|
|
95
|
+
'first-item': ['menuitem', 'Edit'],
|
|
96
|
+
'second-item': ['menuitemcheckbox', 'Show archived'],
|
|
97
|
+
'last-item': ['menuitemradio', 'Oldest'],
|
|
98
|
+
'checkbox-item': ['menuitemcheckbox', 'Show archived'],
|
|
99
|
+
'radio-item': ['menuitemradio', 'Newest'],
|
|
100
|
+
'unchecked-radio-item': ['menuitemradio', 'Oldest'],
|
|
101
|
+
'typeahead-target': ['menuitemradio', 'Oldest'],
|
|
102
|
+
},
|
|
103
|
+
facts: {
|
|
104
|
+
enterTarget: 'checkbox-item',
|
|
105
|
+
wraps: true,
|
|
106
|
+
homeEnd: true,
|
|
107
|
+
disabledItem: false,
|
|
108
|
+
checked: {
|
|
109
|
+
'checkbox-item': true,
|
|
110
|
+
'radio-item': true,
|
|
111
|
+
'unchecked-radio-item': false,
|
|
112
|
+
},
|
|
113
|
+
typeaheadCharacter: 'o',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
] as const satisfies ReadonlyArray<MenuBindingState>;
|
|
117
|
+
|
|
118
|
+
export const MENU_EXCLUSIONS = [
|
|
119
|
+
{
|
|
120
|
+
id: 'opening-control',
|
|
121
|
+
owner: 'the menu-button pattern (MENU_BUTTON_PATTERN, MenuButton.a11y.*)',
|
|
122
|
+
reason:
|
|
123
|
+
'How the trigger exposes itself and where focus lands when it opens the menu are the Menu Button contract. This binding opens the menu from the trigger with Enter only to reach the open state it checks.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'submenus',
|
|
127
|
+
owner:
|
|
128
|
+
'DropdownMenuSubMenu.test.tsx and DropdownMenuDrillIn.a11y.chromium.spec.ts',
|
|
129
|
+
reason:
|
|
130
|
+
'Right and Left Arrow opening and closing a flyout, a parent row’s haspopup and expanded state, and the compact-touch drill-in are not yet in a shared contract; the menu pattern exempts them to the binding, so none of these states renders a sub-menu.',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'bottom-sheet-and-header-footer',
|
|
134
|
+
owner: 'component:DropdownMenu AR4/AR5 and the modal-dialog pattern',
|
|
135
|
+
reason:
|
|
136
|
+
'On a compact touch display the menu is a bottom sheet, and with a header or footer the popover is a modal dialog in which Tab cycles instead of closing; both are dialogs, so every state here uses the plain pointer popover.',
|
|
137
|
+
},
|
|
138
|
+
] as const;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/** @vitest-environment jsdom */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file Menu.a11y.test.tsx
|
|
6
|
+
* @input MENU_PATTERN bound to the open DropdownMenu pointer menu
|
|
7
|
+
* @output jsdom-lane proof of the DOM expectations and an honest "unrun" report for the browser layers
|
|
8
|
+
* @position Binding test for the DropdownMenu menu surface (the Chromium spec covers the rest)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {cleanup, render, screen} from '@testing-library/react';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
13
|
+
import {describe, expect, it} from 'vitest';
|
|
14
|
+
import {
|
|
15
|
+
MENU_PATTERN,
|
|
16
|
+
checkAccessibilitySpec,
|
|
17
|
+
createJsdomHarness,
|
|
18
|
+
expectAccessibilitySpec,
|
|
19
|
+
summarize,
|
|
20
|
+
type BindingResult,
|
|
21
|
+
} from '@astryxdesign/a11y-spec';
|
|
22
|
+
import {MENU_KNOWN_FAILURES} from './Menu.a11y.known-failures';
|
|
23
|
+
import {renderMenuState} from './Menu.a11y.renders';
|
|
24
|
+
import {
|
|
25
|
+
MENU_BINDING_STATES,
|
|
26
|
+
MENU_EXCLUSIONS,
|
|
27
|
+
type MenuBindingState,
|
|
28
|
+
} from './Menu.a11y.states';
|
|
29
|
+
|
|
30
|
+
function trigger(state: MenuBindingState): HTMLElement {
|
|
31
|
+
return screen.getByRole('button', {name: state.label});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function menuFor(state: MenuBindingState): HTMLElement {
|
|
35
|
+
const id = trigger(state).getAttribute('aria-controls');
|
|
36
|
+
const menu = id == null ? null : document.getElementById(id);
|
|
37
|
+
if (menu == null) {
|
|
38
|
+
throw new Error(`menu state "${state.id}" has no controlled menu`);
|
|
39
|
+
}
|
|
40
|
+
return menu;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function relatedFor(
|
|
44
|
+
state: MenuBindingState,
|
|
45
|
+
): Readonly<Record<string, Element>> {
|
|
46
|
+
const related: Record<string, Element> = {opener: trigger(state)};
|
|
47
|
+
for (const [relation, [role, name]] of Object.entries(state.items)) {
|
|
48
|
+
related[relation] = screen.getByRole(role, {name, hidden: true});
|
|
49
|
+
}
|
|
50
|
+
return related;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function open(state: MenuBindingState): Promise<void> {
|
|
54
|
+
render(renderMenuState(state));
|
|
55
|
+
await userEvent.setup().click(trigger(state));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function checkState(state: MenuBindingState): Promise<BindingResult> {
|
|
59
|
+
return checkAccessibilitySpec({
|
|
60
|
+
spec: MENU_PATTERN,
|
|
61
|
+
binding: 'DropdownMenu.menu',
|
|
62
|
+
state: state.id,
|
|
63
|
+
facts: state.facts,
|
|
64
|
+
knownFailures: MENU_KNOWN_FAILURES,
|
|
65
|
+
mount: async () => {
|
|
66
|
+
await open(state);
|
|
67
|
+
return createJsdomHarness({
|
|
68
|
+
subject: menuFor(state),
|
|
69
|
+
related: relatedFor(state),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
unmount: cleanup,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('DropdownMenu pointer menu — menu pattern, jsdom lane', () => {
|
|
77
|
+
it.each(
|
|
78
|
+
MENU_BINDING_STATES.map(state => [state.id, state.summary, state] as const),
|
|
79
|
+
)('%s — %s', async (_id, _summary, state) => {
|
|
80
|
+
await expectAccessibilitySpec({
|
|
81
|
+
spec: MENU_PATTERN,
|
|
82
|
+
binding: 'DropdownMenu.menu',
|
|
83
|
+
state: state.id,
|
|
84
|
+
facts: state.facts,
|
|
85
|
+
knownFailures: MENU_KNOWN_FAILURES,
|
|
86
|
+
render: async () => {
|
|
87
|
+
await open(state);
|
|
88
|
+
},
|
|
89
|
+
subject: () => menuFor(state),
|
|
90
|
+
related: () => relatedFor(state),
|
|
91
|
+
cleanup,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('runs the DOM layer and reports the browser-owned outcomes as unrun', async () => {
|
|
96
|
+
const results: BindingResult[] = [];
|
|
97
|
+
for (const state of MENU_BINDING_STATES) {
|
|
98
|
+
results.push(await checkState(state));
|
|
99
|
+
}
|
|
100
|
+
const report = summarize(MENU_PATTERN, results);
|
|
101
|
+
expect(report.counts.pass).toBeGreaterThan(0);
|
|
102
|
+
expect(report.unrunLayers).toEqual(['accessibility-tree', 'real-browser']);
|
|
103
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('menu binding inventory', () => {
|
|
108
|
+
it('gives every representative state a distinct checked-in story', () => {
|
|
109
|
+
const stories = MENU_BINDING_STATES.map(state => state.storyId);
|
|
110
|
+
expect(new Set(stories).size).toBe(stories.length);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('keeps the opening control, submenus and dialog presentations explicit', () => {
|
|
114
|
+
expect(MENU_EXCLUSIONS.map(row => row.id)).toEqual([
|
|
115
|
+
'opening-control',
|
|
116
|
+
'submenus',
|
|
117
|
+
'bottom-sheet-and-header-footer',
|
|
118
|
+
]);
|
|
119
|
+
for (const exclusion of MENU_EXCLUSIONS) {
|
|
120
|
+
expect(exclusion.owner.length, exclusion.id).toBeGreaterThan(20);
|
|
121
|
+
expect(exclusion.reason.length, exclusion.id).toBeGreaterThan(80);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('renders the menu and every item relation it names for every state', async () => {
|
|
126
|
+
for (const state of MENU_BINDING_STATES) {
|
|
127
|
+
await open(state);
|
|
128
|
+
expect(menuFor(state).getAttribute('role'), state.id).toBe('menu');
|
|
129
|
+
expect(Object.keys(relatedFor(state)), state.id).toEqual([
|
|
130
|
+
'opener',
|
|
131
|
+
...Object.keys(state.items),
|
|
132
|
+
]);
|
|
133
|
+
cleanup();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file warmMenuBottomSheet.tsx
|
|
5
|
+
* @input Uses DropdownMenu and Testing Library
|
|
6
|
+
* @output Exports warmMenuBottomSheet, a test helper that resolves the lazily
|
|
7
|
+
* loaded touch sheet once
|
|
8
|
+
* @position Test-only helper for suites that open a DropdownMenu bottom sheet
|
|
9
|
+
*
|
|
10
|
+
* DropdownMenu loads its touch sheet with React.lazy, so the first sheet
|
|
11
|
+
* renders a tick after it opens. Opening one up front caches the resolved
|
|
12
|
+
* modules on the lazy components, and every later sheet renders
|
|
13
|
+
* synchronously. Call it from `beforeAll`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {cleanup, render, screen} from '@testing-library/react';
|
|
17
|
+
import {DropdownMenu} from '../DropdownMenu';
|
|
18
|
+
|
|
19
|
+
export async function warmMenuBottomSheet(): Promise<void> {
|
|
20
|
+
const {showModal, close} = HTMLDialogElement.prototype;
|
|
21
|
+
HTMLDialogElement.prototype.showModal = function (this: HTMLDialogElement) {
|
|
22
|
+
this.setAttribute('open', '');
|
|
23
|
+
};
|
|
24
|
+
HTMLDialogElement.prototype.close = function (this: HTMLDialogElement) {
|
|
25
|
+
this.removeAttribute('open');
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
render(
|
|
29
|
+
<DropdownMenu
|
|
30
|
+
button={{label: 'Warm'}}
|
|
31
|
+
presentation="bottom-sheet"
|
|
32
|
+
isMenuOpen
|
|
33
|
+
items={[{label: 'Warm item'}]}
|
|
34
|
+
/>,
|
|
35
|
+
);
|
|
36
|
+
// A cold import transforms the sheet's module graph; allow for it.
|
|
37
|
+
await screen.findByText('Warm item', undefined, {timeout: 15000});
|
|
38
|
+
} finally {
|
|
39
|
+
cleanup();
|
|
40
|
+
HTMLDialogElement.prototype.showModal = showModal;
|
|
41
|
+
HTMLDialogElement.prototype.close = close;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file lazyMenuBottomSheet.ts
|
|
7
|
+
* @input Uses React lazy and Suspense, useIsHydrating, MenuBottomSheet, and
|
|
8
|
+
* MenuBottomSheetActionList
|
|
9
|
+
* @output Exports MenuBottomSheet and MenuBottomSheetActionList wrappers that
|
|
10
|
+
* load the touch sheet on first render
|
|
11
|
+
* @position Internal to DropdownMenu
|
|
12
|
+
*
|
|
13
|
+
* The touch sheet (BottomSheet, its gestures, and the touch action list) only
|
|
14
|
+
* renders for the bottom-sheet presentation. Loading it on demand keeps it out
|
|
15
|
+
* of pages whose dropdowns only open as anchored popovers. Each wrapper keeps
|
|
16
|
+
* the wrapped component's props and renders nothing until its module loads.
|
|
17
|
+
*
|
|
18
|
+
* Neither wrapper renders on the server or in the render that hydrates server
|
|
19
|
+
* HTML (spec:AST-013 FR9): a lazy component that has not loaded yet would
|
|
20
|
+
* leave server markup incomplete and abort hydration. The closed sheet has no
|
|
21
|
+
* visible markup, so it first renders right after hydration.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
createElement,
|
|
26
|
+
lazy,
|
|
27
|
+
Suspense,
|
|
28
|
+
type ComponentProps,
|
|
29
|
+
type ReactElement,
|
|
30
|
+
} from 'react';
|
|
31
|
+
import {useIsHydrating} from '../hooks/useIsHydrating';
|
|
32
|
+
import type {MenuBottomSheet as EagerMenuBottomSheet} from './MenuBottomSheet';
|
|
33
|
+
import type {MenuBottomSheetActionList as EagerMenuBottomSheetActionList} from './MenuBottomSheetActionList';
|
|
34
|
+
|
|
35
|
+
const LazyMenuBottomSheet = lazy(async () =>
|
|
36
|
+
import('./MenuBottomSheet').then(module => ({
|
|
37
|
+
default: module.MenuBottomSheet,
|
|
38
|
+
})),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const LazyMenuBottomSheetActionList = lazy(async () =>
|
|
42
|
+
import('./MenuBottomSheetActionList').then(module => ({
|
|
43
|
+
default: module.MenuBottomSheetActionList,
|
|
44
|
+
})),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export function MenuBottomSheet(
|
|
48
|
+
props: ComponentProps<typeof EagerMenuBottomSheet>,
|
|
49
|
+
): ReactElement | null {
|
|
50
|
+
if (useIsHydrating()) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return createElement(
|
|
54
|
+
Suspense,
|
|
55
|
+
{fallback: null},
|
|
56
|
+
createElement(LazyMenuBottomSheet, props),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function MenuBottomSheetActionList(
|
|
61
|
+
props: ComponentProps<typeof EagerMenuBottomSheetActionList>,
|
|
62
|
+
): ReactElement | null {
|
|
63
|
+
if (useIsHydrating()) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return createElement(
|
|
67
|
+
Suspense,
|
|
68
|
+
{fallback: null},
|
|
69
|
+
createElement(LazyMenuBottomSheetActionList, props),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -12,7 +12,7 @@ export const docs = {
|
|
|
12
12
|
name: 'title',
|
|
13
13
|
type: 'string',
|
|
14
14
|
description:
|
|
15
|
-
'Primary message rendered as
|
|
15
|
+
'Primary message rendered as the configured heading level inside the empty state.',
|
|
16
16
|
required: true,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
@@ -81,6 +81,7 @@ export const docs = {
|
|
|
81
81
|
{ guidance: false, description: 'Use an EmptyState for error messages that require immediate action; use a Banner instead.' },
|
|
82
82
|
],
|
|
83
83
|
anatomy: [
|
|
84
|
+
{name: 'Container', required: true, description: 'The centered status region that owns spacing, reading order, and the compact layout variant.'},
|
|
84
85
|
{name: 'Icon', required: false, description: 'A visual cue above the title that reinforces the context, like a search icon for no results.'},
|
|
85
86
|
{name: 'Title', required: true, description: 'Primary message explaining what is empty: "No projects yet" not "No data".'},
|
|
86
87
|
{name: 'Description', required: false, description: 'Additional context explaining why it is empty or what the user can do.'},
|
|
@@ -98,7 +99,7 @@ export const docsZh = {
|
|
|
98
99
|
name: 'title',
|
|
99
100
|
type: 'string',
|
|
100
101
|
description:
|
|
101
|
-
'
|
|
102
|
+
'在空状态内部按配置的标题级别渲染主要信息。',
|
|
102
103
|
required: true,
|
|
103
104
|
},
|
|
104
105
|
{
|