@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,438 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ContextMenuAudit.a11y.chromium.spec.ts
|
|
5
|
+
* @input Exact-head ContextMenu stories across pointer/touch presentation, state, theme, direction, and viewport
|
|
6
|
+
* @output Screenshots and fail-closed sensor receipts for the ContextMenu whole-component audit
|
|
7
|
+
* @position Component-owned Night Watch browser evidence; CI runs it only for a ContextMenu audit pull request
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {createHash} from 'node:crypto';
|
|
11
|
+
import {execFileSync} from 'node:child_process';
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import {expect, test, type Locator, type Page} from '@playwright/test';
|
|
15
|
+
import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_STORYBOOK_DIR,
|
|
18
|
+
serveStorybook,
|
|
19
|
+
type StaticServer,
|
|
20
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
21
|
+
|
|
22
|
+
const OUTPUT = path.resolve('context-menu-audit-evidence');
|
|
23
|
+
const WIDE = {width: 1024, height: 768};
|
|
24
|
+
const NARROW = {width: 320, height: 720};
|
|
25
|
+
|
|
26
|
+
type Mode = 'light' | 'dark';
|
|
27
|
+
type Direction = 'ltr' | 'rtl';
|
|
28
|
+
type Theme = 'neutral' | 'butter';
|
|
29
|
+
type Surface = 'menu' | 'dialog';
|
|
30
|
+
type AuditCase = {
|
|
31
|
+
key: string;
|
|
32
|
+
storyId: string;
|
|
33
|
+
mode: Mode;
|
|
34
|
+
direction: Direction;
|
|
35
|
+
theme: Theme;
|
|
36
|
+
viewport: {width: number; height: number};
|
|
37
|
+
surface: Surface;
|
|
38
|
+
triggerText?: string;
|
|
39
|
+
menuName?: string;
|
|
40
|
+
expectedState?: 'default' | 'disabled' | 'sections' | 'selectable';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const cases: AuditCase[] = [
|
|
44
|
+
{
|
|
45
|
+
key: 'pointer-neutral-light',
|
|
46
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
47
|
+
mode: 'light',
|
|
48
|
+
direction: 'ltr',
|
|
49
|
+
theme: 'neutral',
|
|
50
|
+
viewport: WIDE,
|
|
51
|
+
surface: 'menu',
|
|
52
|
+
menuName: 'Document actions',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'pointer-neutral-dark',
|
|
56
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
57
|
+
mode: 'dark',
|
|
58
|
+
direction: 'ltr',
|
|
59
|
+
theme: 'neutral',
|
|
60
|
+
viewport: WIDE,
|
|
61
|
+
surface: 'menu',
|
|
62
|
+
menuName: 'Document actions',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'pointer-butter-light',
|
|
66
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
67
|
+
mode: 'light',
|
|
68
|
+
direction: 'ltr',
|
|
69
|
+
theme: 'butter',
|
|
70
|
+
viewport: WIDE,
|
|
71
|
+
surface: 'menu',
|
|
72
|
+
menuName: 'Document actions',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: 'pointer-butter-dark',
|
|
76
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
77
|
+
mode: 'dark',
|
|
78
|
+
direction: 'ltr',
|
|
79
|
+
theme: 'butter',
|
|
80
|
+
viewport: WIDE,
|
|
81
|
+
surface: 'menu',
|
|
82
|
+
menuName: 'Document actions',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'pointer-rtl-light',
|
|
86
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
87
|
+
mode: 'light',
|
|
88
|
+
direction: 'rtl',
|
|
89
|
+
theme: 'neutral',
|
|
90
|
+
viewport: WIDE,
|
|
91
|
+
surface: 'menu',
|
|
92
|
+
menuName: 'Document actions',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: 'pointer-rtl-dark',
|
|
96
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
97
|
+
mode: 'dark',
|
|
98
|
+
direction: 'rtl',
|
|
99
|
+
theme: 'neutral',
|
|
100
|
+
viewport: WIDE,
|
|
101
|
+
surface: 'menu',
|
|
102
|
+
menuName: 'Document actions',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
key: 'pointer-narrow-light',
|
|
106
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
107
|
+
mode: 'light',
|
|
108
|
+
direction: 'ltr',
|
|
109
|
+
theme: 'neutral',
|
|
110
|
+
viewport: NARROW,
|
|
111
|
+
surface: 'menu',
|
|
112
|
+
menuName: 'Document actions',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: 'pointer-narrow-dark',
|
|
116
|
+
storyId: 'core-contextmenu--pointer-presentation',
|
|
117
|
+
mode: 'dark',
|
|
118
|
+
direction: 'ltr',
|
|
119
|
+
theme: 'neutral',
|
|
120
|
+
viewport: NARROW,
|
|
121
|
+
surface: 'menu',
|
|
122
|
+
menuName: 'Document actions',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: 'disabled-item-light',
|
|
126
|
+
storyId: 'core-contextmenu--with-disabled-items',
|
|
127
|
+
mode: 'light',
|
|
128
|
+
direction: 'ltr',
|
|
129
|
+
theme: 'neutral',
|
|
130
|
+
viewport: WIDE,
|
|
131
|
+
surface: 'menu',
|
|
132
|
+
triggerText: 'Right-click this area',
|
|
133
|
+
expectedState: 'disabled',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'sections-dark',
|
|
137
|
+
storyId: 'core-contextmenu--with-sections',
|
|
138
|
+
mode: 'dark',
|
|
139
|
+
direction: 'ltr',
|
|
140
|
+
theme: 'neutral',
|
|
141
|
+
viewport: WIDE,
|
|
142
|
+
surface: 'menu',
|
|
143
|
+
triggerText: 'Right-click for grouped actions',
|
|
144
|
+
expectedState: 'sections',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: 'selectable-light',
|
|
148
|
+
storyId: 'core-contextmenu--with-selectable-items',
|
|
149
|
+
mode: 'light',
|
|
150
|
+
direction: 'ltr',
|
|
151
|
+
theme: 'neutral',
|
|
152
|
+
viewport: WIDE,
|
|
153
|
+
surface: 'menu',
|
|
154
|
+
triggerText: 'Right-click for selectable items',
|
|
155
|
+
expectedState: 'selectable',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
key: 'selectable-dark',
|
|
159
|
+
storyId: 'core-contextmenu--with-selectable-items',
|
|
160
|
+
mode: 'dark',
|
|
161
|
+
direction: 'ltr',
|
|
162
|
+
theme: 'neutral',
|
|
163
|
+
viewport: WIDE,
|
|
164
|
+
surface: 'menu',
|
|
165
|
+
triggerText: 'Right-click for selectable items',
|
|
166
|
+
expectedState: 'selectable',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'sheet-light',
|
|
170
|
+
storyId: 'core-contextmenu--bottom-sheet-presentation',
|
|
171
|
+
mode: 'light',
|
|
172
|
+
direction: 'ltr',
|
|
173
|
+
theme: 'neutral',
|
|
174
|
+
viewport: NARROW,
|
|
175
|
+
surface: 'dialog',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
key: 'sheet-dark',
|
|
179
|
+
storyId: 'core-contextmenu--bottom-sheet-presentation',
|
|
180
|
+
mode: 'dark',
|
|
181
|
+
direction: 'ltr',
|
|
182
|
+
theme: 'neutral',
|
|
183
|
+
viewport: NARROW,
|
|
184
|
+
surface: 'dialog',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: 'sheet-rtl',
|
|
188
|
+
storyId: 'core-contextmenu--bottom-sheet-presentation',
|
|
189
|
+
mode: 'light',
|
|
190
|
+
direction: 'rtl',
|
|
191
|
+
theme: 'neutral',
|
|
192
|
+
viewport: NARROW,
|
|
193
|
+
surface: 'dialog',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
key: 'inline-light',
|
|
197
|
+
storyId: 'core-contextmenu--inline-trigger',
|
|
198
|
+
mode: 'light',
|
|
199
|
+
direction: 'ltr',
|
|
200
|
+
theme: 'neutral',
|
|
201
|
+
viewport: WIDE,
|
|
202
|
+
surface: 'menu',
|
|
203
|
+
triggerText: 'T123',
|
|
204
|
+
menuName: 'Task actions',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: 'inline-dark',
|
|
208
|
+
storyId: 'core-contextmenu--inline-trigger',
|
|
209
|
+
mode: 'dark',
|
|
210
|
+
direction: 'ltr',
|
|
211
|
+
theme: 'neutral',
|
|
212
|
+
viewport: WIDE,
|
|
213
|
+
surface: 'menu',
|
|
214
|
+
triggerText: 'T123',
|
|
215
|
+
menuName: 'Task actions',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: 'scroll-container-light',
|
|
219
|
+
storyId: 'core-contextmenu--in-scroll-container',
|
|
220
|
+
mode: 'light',
|
|
221
|
+
direction: 'ltr',
|
|
222
|
+
theme: 'neutral',
|
|
223
|
+
viewport: WIDE,
|
|
224
|
+
surface: 'menu',
|
|
225
|
+
triggerText: 'Right-click this area',
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
test.describe.configure({mode: 'serial', retries: 0});
|
|
230
|
+
let server: StaticServer;
|
|
231
|
+
let checkoutSha: string;
|
|
232
|
+
let storybookSha: string;
|
|
233
|
+
|
|
234
|
+
function storyUrl(item: AuditCase) {
|
|
235
|
+
return `${server.origin}/iframe.html?id=${item.storyId}&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function settle(page: Page) {
|
|
239
|
+
await holdMotionStill(page);
|
|
240
|
+
await page.evaluate(async () => document.fonts.ready);
|
|
241
|
+
await page.waitForTimeout(40);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function openSurface(
|
|
245
|
+
page: Page,
|
|
246
|
+
item: AuditCase,
|
|
247
|
+
): Promise<{surface: Locator; pointer: {x: number; y: number}}> {
|
|
248
|
+
const trigger =
|
|
249
|
+
item.storyId === 'core-contextmenu--pointer-presentation'
|
|
250
|
+
? page.getByTestId('pointer-context-menu')
|
|
251
|
+
: item.storyId === 'core-contextmenu--bottom-sheet-presentation'
|
|
252
|
+
? page.getByTestId('bottom-sheet-context-menu')
|
|
253
|
+
: item.triggerText
|
|
254
|
+
? page.getByText(item.triggerText, {exact: true}).first()
|
|
255
|
+
: null;
|
|
256
|
+
if (trigger === null) {
|
|
257
|
+
throw new Error(`No trigger configured for ${item.key}`);
|
|
258
|
+
}
|
|
259
|
+
await trigger.scrollIntoViewIfNeeded();
|
|
260
|
+
const triggerBox = await trigger.boundingBox();
|
|
261
|
+
if (triggerBox === null) {
|
|
262
|
+
throw new Error(`Trigger has no rendered box for ${item.key}`);
|
|
263
|
+
}
|
|
264
|
+
const pointer = {
|
|
265
|
+
x: triggerBox.x + Math.min(40, triggerBox.width / 2),
|
|
266
|
+
y: triggerBox.y + Math.min(40, triggerBox.height / 2),
|
|
267
|
+
};
|
|
268
|
+
await page.mouse.click(pointer.x, pointer.y, {button: 'right'});
|
|
269
|
+
const surface =
|
|
270
|
+
item.surface === 'dialog'
|
|
271
|
+
? page.getByRole('dialog').last()
|
|
272
|
+
: item.menuName
|
|
273
|
+
? page.getByRole('menu', {name: item.menuName}).last()
|
|
274
|
+
: page.getByRole('menu').last();
|
|
275
|
+
await expect(surface).toBeVisible();
|
|
276
|
+
return {surface, pointer};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
test.beforeAll(async () => {
|
|
280
|
+
fs.rmSync(OUTPUT, {recursive: true, force: true});
|
|
281
|
+
fs.mkdirSync(OUTPUT, {recursive: true});
|
|
282
|
+
checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
283
|
+
encoding: 'utf8',
|
|
284
|
+
}).trim();
|
|
285
|
+
const expected = process.env.ASTRYX_HEAD_SHA;
|
|
286
|
+
if (expected && expected !== checkoutSha) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`checkout ${checkoutSha} does not match expected head ${expected}`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
server = await serveStorybook(
|
|
292
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
293
|
+
);
|
|
294
|
+
const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
throw new Error(`Storybook build has no SHA stamp (${response.status})`);
|
|
297
|
+
}
|
|
298
|
+
storybookSha = (await response.text()).trim();
|
|
299
|
+
if (storybookSha !== checkoutSha) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test.afterAll(async () => {
|
|
307
|
+
await server?.close();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
for (const item of cases) {
|
|
311
|
+
test(item.key, async ({browser}) => {
|
|
312
|
+
const context = await browser.newContext({
|
|
313
|
+
viewport: item.viewport,
|
|
314
|
+
colorScheme: item.mode,
|
|
315
|
+
reducedMotion: 'reduce',
|
|
316
|
+
});
|
|
317
|
+
const page = await context.newPage();
|
|
318
|
+
const pageErrors: string[] = [];
|
|
319
|
+
page.on('pageerror', error => pageErrors.push(String(error)));
|
|
320
|
+
await page.goto(storyUrl(item), {waitUntil: 'load'});
|
|
321
|
+
await page.locator('#storybook-root').waitFor();
|
|
322
|
+
const {surface, pointer} = await openSurface(page, item);
|
|
323
|
+
await settle(page);
|
|
324
|
+
|
|
325
|
+
const menuItems = page.locator(
|
|
326
|
+
'[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]',
|
|
327
|
+
);
|
|
328
|
+
if (item.surface === 'menu') {
|
|
329
|
+
await expect(menuItems.first()).toBeVisible();
|
|
330
|
+
} else {
|
|
331
|
+
await expect(surface.getByRole('button').first()).toBeVisible();
|
|
332
|
+
}
|
|
333
|
+
if (item.expectedState === 'disabled') {
|
|
334
|
+
await expect(
|
|
335
|
+
page.locator('[role="menuitem"][aria-disabled="true"]'),
|
|
336
|
+
).toHaveCount(1);
|
|
337
|
+
}
|
|
338
|
+
if (item.expectedState === 'sections') {
|
|
339
|
+
await expect(page.getByRole('group')).toHaveCount(2);
|
|
340
|
+
}
|
|
341
|
+
if (item.expectedState === 'selectable') {
|
|
342
|
+
await expect(page.getByRole('menuitemcheckbox')).toHaveCount(2);
|
|
343
|
+
await expect(page.getByRole('menuitemradio')).toHaveCount(3);
|
|
344
|
+
await expect(
|
|
345
|
+
page.locator('[role="menuitemradio"][aria-checked="true"]'),
|
|
346
|
+
).toHaveCount(1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const observed = await surface.evaluate(element => {
|
|
350
|
+
const box = element.getBoundingClientRect();
|
|
351
|
+
return {
|
|
352
|
+
theme: document
|
|
353
|
+
.querySelector('[data-astryx-theme]')
|
|
354
|
+
?.getAttribute('data-astryx-theme'),
|
|
355
|
+
mode: document.documentElement.getAttribute('data-theme'),
|
|
356
|
+
direction: getComputedStyle(element).direction,
|
|
357
|
+
viewport: {width: innerWidth, height: innerHeight},
|
|
358
|
+
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
359
|
+
horizontalOverflow:
|
|
360
|
+
document.documentElement.scrollWidth > innerWidth + 1,
|
|
361
|
+
fonts: document.fonts.status,
|
|
362
|
+
surfaceRole:
|
|
363
|
+
element.getAttribute('role') ??
|
|
364
|
+
(element instanceof HTMLDialogElement ? 'dialog' : null),
|
|
365
|
+
surfaceName:
|
|
366
|
+
element.getAttribute('aria-label') ??
|
|
367
|
+
element.getAttribute('aria-labelledby'),
|
|
368
|
+
geometry: {x: box.x, y: box.y, width: box.width, height: box.height},
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
const geometryPass =
|
|
372
|
+
observed.geometry !== null &&
|
|
373
|
+
observed.geometry.width > 0 &&
|
|
374
|
+
observed.geometry.height > 0;
|
|
375
|
+
let pointerPlacementPass = true;
|
|
376
|
+
if (item.storyId === 'core-contextmenu--pointer-presentation') {
|
|
377
|
+
const surfaceBox = await surface.boundingBox();
|
|
378
|
+
pointerPlacementPass =
|
|
379
|
+
surfaceBox !== null &&
|
|
380
|
+
Math.abs(
|
|
381
|
+
(item.direction === 'rtl'
|
|
382
|
+
? surfaceBox.x + surfaceBox.width
|
|
383
|
+
: surfaceBox.x) - pointer.x,
|
|
384
|
+
) <= 24 &&
|
|
385
|
+
Math.abs(surfaceBox.y - pointer.y) <= 24;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const png = await page.screenshot({animations: 'disabled', fullPage: true});
|
|
389
|
+
const image = `${item.key}.png`;
|
|
390
|
+
const receipt = {
|
|
391
|
+
schemaVersion: 1,
|
|
392
|
+
expected: {
|
|
393
|
+
build: checkoutSha,
|
|
394
|
+
storyId: item.storyId,
|
|
395
|
+
mode: item.mode,
|
|
396
|
+
direction: item.direction,
|
|
397
|
+
theme: item.theme,
|
|
398
|
+
viewport: item.viewport,
|
|
399
|
+
surface: item.surface,
|
|
400
|
+
state: item.expectedState ?? 'default',
|
|
401
|
+
},
|
|
402
|
+
observed: {
|
|
403
|
+
build: storybookSha,
|
|
404
|
+
...observed,
|
|
405
|
+
menuItemCount: await menuItems.count(),
|
|
406
|
+
pageErrors,
|
|
407
|
+
},
|
|
408
|
+
image: {
|
|
409
|
+
file: image,
|
|
410
|
+
sha256: createHash('sha256').update(png).digest('hex'),
|
|
411
|
+
width: png.readUInt32BE(16),
|
|
412
|
+
height: png.readUInt32BE(20),
|
|
413
|
+
},
|
|
414
|
+
checks: {geometryPass, pointerPlacementPass},
|
|
415
|
+
passed:
|
|
416
|
+
storybookSha === checkoutSha &&
|
|
417
|
+
pageErrors.length === 0 &&
|
|
418
|
+
observed.theme === item.theme &&
|
|
419
|
+
observed.mode === item.mode &&
|
|
420
|
+
observed.direction === item.direction &&
|
|
421
|
+
observed.viewport.width === item.viewport.width &&
|
|
422
|
+
observed.viewport.height === item.viewport.height &&
|
|
423
|
+
observed.reducedMotion &&
|
|
424
|
+
!observed.horizontalOverflow &&
|
|
425
|
+
observed.fonts === 'loaded' &&
|
|
426
|
+
observed.surfaceRole === item.surface &&
|
|
427
|
+
geometryPass &&
|
|
428
|
+
pointerPlacementPass,
|
|
429
|
+
};
|
|
430
|
+
fs.writeFileSync(path.join(OUTPUT, image), png);
|
|
431
|
+
fs.writeFileSync(
|
|
432
|
+
path.join(OUTPUT, `${item.key}.sensors.json`),
|
|
433
|
+
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
434
|
+
);
|
|
435
|
+
expect(receipt.passed).toBe(true);
|
|
436
|
+
await context.close();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
@@ -110,6 +110,52 @@ beforeEach(() => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
describe('Dialog', () => {
|
|
113
|
+
it('restores focus to the opener when content mounts in the open commit', () => {
|
|
114
|
+
function Harness({
|
|
115
|
+
open,
|
|
116
|
+
onOpenChange,
|
|
117
|
+
}: {
|
|
118
|
+
open: boolean;
|
|
119
|
+
onOpenChange: (v: boolean) => void;
|
|
120
|
+
}) {
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<button type="button" onClick={() => onOpenChange(true)}>
|
|
124
|
+
Open
|
|
125
|
+
</button>
|
|
126
|
+
<Dialog isOpen={open} onOpenChange={onOpenChange}>
|
|
127
|
+
{open ? (
|
|
128
|
+
<div>
|
|
129
|
+
<DialogHeader title="Review" />
|
|
130
|
+
<p>Body</p>
|
|
131
|
+
</div>
|
|
132
|
+
) : null}
|
|
133
|
+
</Dialog>
|
|
134
|
+
</>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let open = false;
|
|
139
|
+
const setOpen = (v: boolean) => {
|
|
140
|
+
open = v;
|
|
141
|
+
};
|
|
142
|
+
const harness = render(<Harness open={open} onOpenChange={setOpen} />);
|
|
143
|
+
const opener = screen.getByRole('button', {name: 'Open'});
|
|
144
|
+
opener.focus();
|
|
145
|
+
|
|
146
|
+
fireEvent.click(opener);
|
|
147
|
+
harness.rerender(<Harness open={open} onOpenChange={setOpen} />);
|
|
148
|
+
|
|
149
|
+
// The freshly mounted header title is the focused screen reader anchor
|
|
150
|
+
// while the dialog is open...
|
|
151
|
+
expect(document.activeElement).toBe(screen.getByText('Review'));
|
|
152
|
+
|
|
153
|
+
// ...but closing restores the element that opened the dialog, not the
|
|
154
|
+
// (now unmounted) title.
|
|
155
|
+
harness.rerender(<Harness open={false} onOpenChange={setOpen} />);
|
|
156
|
+
expect(document.activeElement).toBe(opener);
|
|
157
|
+
});
|
|
158
|
+
|
|
113
159
|
it('renders when isOpen is true', () => {
|
|
114
160
|
render(
|
|
115
161
|
<Dialog isOpen={true} onOpenChange={() => {}}>
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useCallback,
|
|
33
33
|
useEffect,
|
|
34
34
|
useId,
|
|
35
|
+
useLayoutEffect,
|
|
35
36
|
useMemo,
|
|
36
37
|
useRef,
|
|
37
38
|
type ReactNode,
|
|
@@ -505,12 +506,26 @@ export function Dialog({
|
|
|
505
506
|
|
|
506
507
|
const mergedDialogRef = useMergedRefs(ref, attachDialog);
|
|
507
508
|
|
|
508
|
-
// Capture the element that was focused when the dialog opened,
|
|
509
|
-
//
|
|
509
|
+
// Capture the element that was focused when the dialog opened, for
|
|
510
|
+
// directional animation origin and focus restoration on close.
|
|
510
511
|
const triggerElementRef = useRef<HTMLElement | null>(null);
|
|
511
512
|
|
|
512
513
|
useInteractionModalityTracking();
|
|
513
514
|
|
|
515
|
+
// This runs in a layout effect so the capture lands before any descendant's
|
|
516
|
+
// passive mount effect: all layout effects run before any passive effect,
|
|
517
|
+
// while a child's passive effect runs before this parent's passive effect.
|
|
518
|
+
// Content that mounts in the open commit — e.g. a DialogHeader focusing its
|
|
519
|
+
// own title on mount — would otherwise move document.activeElement inside
|
|
520
|
+
// the dialog before the capture ran, and closing would then restore focus
|
|
521
|
+
// to a detached node, stranding the user's focus on <body>.
|
|
522
|
+
useLayoutEffect(() => {
|
|
523
|
+
if (isInline || !isOpen) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
triggerElementRef.current = document.activeElement as HTMLElement | null;
|
|
527
|
+
}, [isOpen, isInline]);
|
|
528
|
+
|
|
514
529
|
// Derive dismissal behavior from purpose
|
|
515
530
|
const allowEscape = purpose !== 'required';
|
|
516
531
|
const allowBackdropClick = purpose === 'info';
|
|
@@ -526,11 +541,9 @@ export function Dialog({
|
|
|
526
541
|
}
|
|
527
542
|
|
|
528
543
|
if (isOpen) {
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
// Set directional CSS custom properties before opening
|
|
544
|
+
// Set directional CSS custom properties before opening. The trigger was
|
|
545
|
+
// captured by the layout effect above, before any descendant effect
|
|
546
|
+
// could move focus.
|
|
534
547
|
const trigger = triggerElementRef.current;
|
|
535
548
|
if (trigger && trigger !== document.body) {
|
|
536
549
|
const dir = getDialogDirection(trigger);
|
|
@@ -25,18 +25,4 @@ export const DIALOG_MODAL_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
|
|
|
25
25
|
reason:
|
|
26
26
|
'This migration records the existing focus-order violation without changing the overlapping Dialog implementation path.',
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
expectation: 'modal-dialog.focus.restored',
|
|
30
|
-
binding: 'Dialog',
|
|
31
|
-
state: 'conditional-content-focus-restoration',
|
|
32
|
-
evidenceLayer: 'real-browser',
|
|
33
|
-
failureEquals:
|
|
34
|
-
'closing the modal dialog did not return focus to its still-available invoker',
|
|
35
|
-
standardsReference:
|
|
36
|
-
'Astryx component:Dialog FR4 — closing a native Dialog must return focus to its still-connected invoker when it can receive focus.',
|
|
37
|
-
userImpact:
|
|
38
|
-
'When focus-moving content mounts only while the dialog is open, closing can leave the user at the document body instead of the control that opened the task.',
|
|
39
|
-
reason:
|
|
40
|
-
'This migration records the existing restoration violation without changing the overlapping Dialog implementation path.',
|
|
41
|
-
},
|
|
42
28
|
];
|