@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
|
@@ -35,6 +35,7 @@ import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
|
35
35
|
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
36
36
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
37
37
|
import {InputGroup, InputGroupText} from '../InputGroup';
|
|
38
|
+
import {focusVars} from '../theme/tokens.stylex';
|
|
38
39
|
|
|
39
40
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
40
41
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -4083,6 +4084,164 @@ describe('MultiSelector option actions (grid)', () => {
|
|
|
4083
4084
|
expect(onChange).not.toHaveBeenCalled();
|
|
4084
4085
|
});
|
|
4085
4086
|
|
|
4087
|
+
it('draws the standard focus ring on the active action cell, from the focus tokens', async () => {
|
|
4088
|
+
// jsdom resolves no custom property, so the ring is read from StyleX
|
|
4089
|
+
// classes: a class name hashes one property and its value, and the same
|
|
4090
|
+
// declaration yields the same class wherever it is written.
|
|
4091
|
+
const ring = stylex.create({
|
|
4092
|
+
active: {
|
|
4093
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4094
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4095
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4096
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4097
|
+
},
|
|
4098
|
+
});
|
|
4099
|
+
const ringClasses = stylex
|
|
4100
|
+
.props(ring.active)
|
|
4101
|
+
.className!.split(' ')
|
|
4102
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4103
|
+
expect(ringClasses).toHaveLength(4);
|
|
4104
|
+
|
|
4105
|
+
const user = userEvent.setup();
|
|
4106
|
+
render(
|
|
4107
|
+
<MultiSelector
|
|
4108
|
+
label="Labels"
|
|
4109
|
+
options={OPTIONS}
|
|
4110
|
+
value={[]}
|
|
4111
|
+
onChange={() => {}}
|
|
4112
|
+
/>,
|
|
4113
|
+
);
|
|
4114
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4115
|
+
await user.click(trigger);
|
|
4116
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4117
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4118
|
+
for (const token of ringClasses) {
|
|
4119
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4120
|
+
}
|
|
4121
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4122
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4123
|
+
for (const token of ringClasses) {
|
|
4124
|
+
expect(actionCell).toHaveClass(token);
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4128
|
+
it('draws no focus ring on an action cell highlighted after pointer input, until a key moves the highlight', async () => {
|
|
4129
|
+
const ring = stylex.create({
|
|
4130
|
+
active: {
|
|
4131
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4132
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4133
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4134
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4135
|
+
},
|
|
4136
|
+
});
|
|
4137
|
+
const ringClasses = stylex
|
|
4138
|
+
.props(ring.active)
|
|
4139
|
+
.className!.split(' ')
|
|
4140
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4141
|
+
expect(ringClasses).toHaveLength(4);
|
|
4142
|
+
|
|
4143
|
+
const user = userEvent.setup();
|
|
4144
|
+
render(
|
|
4145
|
+
<MultiSelector
|
|
4146
|
+
label="Labels"
|
|
4147
|
+
options={OPTIONS}
|
|
4148
|
+
value={[]}
|
|
4149
|
+
onChange={() => {}}
|
|
4150
|
+
/>,
|
|
4151
|
+
);
|
|
4152
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4153
|
+
await user.click(trigger);
|
|
4154
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4155
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4156
|
+
fireEvent.mouseEnter(actionCell);
|
|
4157
|
+
// The hover moved the highlight onto the action cell, without the ring.
|
|
4158
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4159
|
+
for (const token of ringClasses) {
|
|
4160
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4161
|
+
}
|
|
4162
|
+
// Keyboard navigation back onto the cell draws it.
|
|
4163
|
+
fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
|
|
4164
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4165
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4166
|
+
for (const token of ringClasses) {
|
|
4167
|
+
expect(actionCell).toHaveClass(token);
|
|
4168
|
+
}
|
|
4169
|
+
});
|
|
4170
|
+
|
|
4171
|
+
describe('ring under mixed input', () => {
|
|
4172
|
+
const ringClasses = () => {
|
|
4173
|
+
const ring = stylex.create({
|
|
4174
|
+
active: {
|
|
4175
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4176
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4177
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4178
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4179
|
+
},
|
|
4180
|
+
});
|
|
4181
|
+
return stylex
|
|
4182
|
+
.props(ring.active)
|
|
4183
|
+
.className!.split(' ')
|
|
4184
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4185
|
+
};
|
|
4186
|
+
const hasRing = (cell: HTMLElement) =>
|
|
4187
|
+
ringClasses().every(token => cell.classList.contains(token));
|
|
4188
|
+
|
|
4189
|
+
async function openWithKeyboardOnBugAction() {
|
|
4190
|
+
const user = userEvent.setup();
|
|
4191
|
+
render(
|
|
4192
|
+
<MultiSelector
|
|
4193
|
+
label="Labels"
|
|
4194
|
+
options={OPTIONS}
|
|
4195
|
+
value={[]}
|
|
4196
|
+
onChange={() => {}}
|
|
4197
|
+
/>,
|
|
4198
|
+
);
|
|
4199
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4200
|
+
await user.click(trigger);
|
|
4201
|
+
const row = (name: string) => screen.getByRole('row', {name, ...h});
|
|
4202
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4203
|
+
const bugAction = row('Bug').children[1] as HTMLElement;
|
|
4204
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4205
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4206
|
+
return {trigger, row, bugAction};
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
it('keeps the ring when the pointer crosses a disabled option the highlight ignores', async () => {
|
|
4210
|
+
const {trigger, row, bugAction} = await openWithKeyboardOnBugAction();
|
|
4211
|
+
fireEvent.mouseEnter(row('Docs').children[0]);
|
|
4212
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4213
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4214
|
+
});
|
|
4215
|
+
|
|
4216
|
+
it('follows the keyboard modality when a hover moves the highlight to another action', async () => {
|
|
4217
|
+
const {trigger, row} = await openWithKeyboardOnBugAction();
|
|
4218
|
+
const docsAction = row('Docs').children[1] as HTMLElement;
|
|
4219
|
+
fireEvent.mouseEnter(docsAction);
|
|
4220
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', docsAction.id);
|
|
4221
|
+
expect(hasRing(docsAction)).toBe(true);
|
|
4222
|
+
});
|
|
4223
|
+
|
|
4224
|
+
it('draws no ring for a modifier chord after pointer input', async () => {
|
|
4225
|
+
const user = userEvent.setup();
|
|
4226
|
+
render(
|
|
4227
|
+
<MultiSelector
|
|
4228
|
+
label="Labels"
|
|
4229
|
+
options={OPTIONS}
|
|
4230
|
+
value={[]}
|
|
4231
|
+
onChange={() => {}}
|
|
4232
|
+
/>,
|
|
4233
|
+
);
|
|
4234
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4235
|
+
await user.click(trigger);
|
|
4236
|
+
const bugAction = screen.getByRole('row', {name: 'Bug', ...h})
|
|
4237
|
+
.children[1] as HTMLElement;
|
|
4238
|
+
fireEvent.mouseEnter(bugAction);
|
|
4239
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4240
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight', ctrlKey: true});
|
|
4241
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4242
|
+
});
|
|
4243
|
+
});
|
|
4244
|
+
|
|
4086
4245
|
it('the inline-end arrow reaches the action cell, Enter fires it, inline-start returns', async () => {
|
|
4087
4246
|
const user = userEvent.setup();
|
|
4088
4247
|
const onChange = vi.fn();
|
|
@@ -50,6 +50,7 @@ import type {IndicatorPosition} from '../Indicator';
|
|
|
50
50
|
import {Badge} from '../Badge';
|
|
51
51
|
import {
|
|
52
52
|
colorVars,
|
|
53
|
+
focusVars,
|
|
53
54
|
sizeVars,
|
|
54
55
|
spacingVars,
|
|
55
56
|
radiusVars,
|
|
@@ -74,7 +75,10 @@ import {
|
|
|
74
75
|
import {useMultiCombobox} from './hooks';
|
|
75
76
|
import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
|
|
76
77
|
import {devWarn, warnOnce} from '../utils/devWarning';
|
|
77
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
getInteractionModality,
|
|
80
|
+
useInteractionModalityTracking,
|
|
81
|
+
} from '../utils/interactionModality';
|
|
78
82
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
79
83
|
import {FOCUSABLE_SELECTOR} from '../hooks/focusableSelector';
|
|
80
84
|
import {useAnnounceRenderedText} from '../hooks/useAnnounceRenderedText';
|
|
@@ -436,11 +440,15 @@ const styles = stylex.create({
|
|
|
436
440
|
flexShrink: 0,
|
|
437
441
|
borderRadius: radiusVars['--radius-element'],
|
|
438
442
|
},
|
|
443
|
+
// The standard ring, from the focus tokens themselves: inside
|
|
444
|
+
// `stylex.create` StyleX resolves an imported `defineVars` group, and a
|
|
445
|
+
// plain object such as `FOCUS_OUTLINE_PARTS` compiles to variables nothing
|
|
446
|
+
// defines.
|
|
439
447
|
actionCellActive: {
|
|
440
|
-
outlineWidth:
|
|
441
|
-
outlineStyle:
|
|
442
|
-
outlineColor:
|
|
443
|
-
outlineOffset:
|
|
448
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
449
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
450
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
451
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
444
452
|
},
|
|
445
453
|
|
|
446
454
|
// Empty state
|
|
@@ -1752,8 +1760,8 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1752
1760
|
getActionCellId,
|
|
1753
1761
|
activeDescendantId,
|
|
1754
1762
|
onTriggerClick,
|
|
1755
|
-
onKeyDown,
|
|
1756
|
-
onItemMouseEnter,
|
|
1763
|
+
onKeyDown: onComboboxKeyDown,
|
|
1764
|
+
onItemMouseEnter: onComboboxItemMouseEnter,
|
|
1757
1765
|
} = useMultiCombobox({
|
|
1758
1766
|
selectableItems: sortedItems,
|
|
1759
1767
|
isDisabled: isDisabled || isEffectivelyReadOnly,
|
|
@@ -1784,6 +1792,29 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1784
1792
|
),
|
|
1785
1793
|
});
|
|
1786
1794
|
|
|
1795
|
+
// A hover moves the highlight too, but the focus ring follows the shared
|
|
1796
|
+
// interaction modality (architecture:interaction-modality INV1): it is read
|
|
1797
|
+
// whenever a key or the pointer moves the highlight, so an action cell
|
|
1798
|
+
// highlighted after pointer input keeps the hover treatment and no ring.
|
|
1799
|
+
useInteractionModalityTracking();
|
|
1800
|
+
const [isKeyboardHighlight, setIsKeyboardHighlight] = useState(
|
|
1801
|
+
() => getInteractionModality() === 'keyboard',
|
|
1802
|
+
);
|
|
1803
|
+
const onKeyDown = useCallback(
|
|
1804
|
+
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
1805
|
+
onComboboxKeyDown(event);
|
|
1806
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1807
|
+
},
|
|
1808
|
+
[onComboboxKeyDown],
|
|
1809
|
+
);
|
|
1810
|
+
const onItemMouseEnter = useCallback(
|
|
1811
|
+
(...args: Parameters<typeof onComboboxItemMouseEnter>) => {
|
|
1812
|
+
onComboboxItemMouseEnter(...args);
|
|
1813
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1814
|
+
},
|
|
1815
|
+
[onComboboxItemMouseEnter],
|
|
1816
|
+
);
|
|
1817
|
+
|
|
1787
1818
|
// The trigger button owns its click so a consumer `onClick`, which lands on
|
|
1788
1819
|
// the button, composes after the built-in toggle. A press elsewhere in the
|
|
1789
1820
|
// control box (padding, start icon, chevron) still opens the panel.
|
|
@@ -2117,6 +2148,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2117
2148
|
styles.actionCell,
|
|
2118
2149
|
isHighlighted &&
|
|
2119
2150
|
highlightedCell === 'action' &&
|
|
2151
|
+
isKeyboardHighlight &&
|
|
2120
2152
|
styles.actionCellActive,
|
|
2121
2153
|
)}>
|
|
2122
2154
|
{isSelectAll || isCreate ? null : item.action}
|
|
@@ -2131,6 +2163,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2131
2163
|
[
|
|
2132
2164
|
isGrid,
|
|
2133
2165
|
highlightedCell,
|
|
2166
|
+
isKeyboardHighlight,
|
|
2134
2167
|
getActionCellId,
|
|
2135
2168
|
renderOption,
|
|
2136
2169
|
indicatorPosition,
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* built Storybook.
|
|
7
7
|
* @output Real-engine proof for option actions: the action is visible at rest
|
|
8
8
|
* beside its row (`spec:AST-058` FR8), the keyboard reaches it through the
|
|
9
|
-
* inline-end arrow in both directions and fires it with Enter,
|
|
10
|
-
*
|
|
9
|
+
* inline-end arrow in both directions and fires it with Enter, the active
|
|
10
|
+
* action cell draws the standard focus ring when the shared input modality
|
|
11
|
+
* is keyboard and not after pointer input, and the highlight follows a real
|
|
12
|
+
* mouse across rows and actions.
|
|
11
13
|
* @position Run with `pnpm test:a11y-contract`; paint, pointer paths and the
|
|
12
14
|
* active descendant as an engine exposes them are not jsdom claims.
|
|
13
15
|
*/
|
|
@@ -143,6 +145,114 @@ test('the keyboard reaches an action through the inline-end arrow and fires it w
|
|
|
143
145
|
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
144
146
|
});
|
|
145
147
|
|
|
148
|
+
test('the active action cell draws the standard focus ring', async ({page}) => {
|
|
149
|
+
await page.goto(
|
|
150
|
+
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
|
|
151
|
+
);
|
|
152
|
+
const search = page.getByRole('combobox');
|
|
153
|
+
await expect(search).toBeFocused();
|
|
154
|
+
await search.press('ArrowDown'); // select-all
|
|
155
|
+
await search.press('ArrowDown'); // Feature (no action)
|
|
156
|
+
await search.press('ArrowDown'); // Bug
|
|
157
|
+
await search.press('ArrowRight');
|
|
158
|
+
await expect
|
|
159
|
+
.poll(async () => activeDescendant(page))
|
|
160
|
+
.toBe('gridcell:Edit Bug');
|
|
161
|
+
const ring = await page.evaluate(() => {
|
|
162
|
+
const id = document.activeElement?.getAttribute('aria-activedescendant');
|
|
163
|
+
const cell = id ? document.getElementById(id) : null;
|
|
164
|
+
if (!cell) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const cs = getComputedStyle(cell);
|
|
168
|
+
// The ring the focus tokens describe, resolved by the browser on a probe.
|
|
169
|
+
const probe = document.createElement('div');
|
|
170
|
+
probe.style.cssText =
|
|
171
|
+
'outline-width:var(--focus-outline-width);outline-style:var(--focus-outline-style);outline-color:var(--focus-outline-color);outline-offset:var(--focus-outline-offset)';
|
|
172
|
+
document.body.appendChild(probe);
|
|
173
|
+
const want = getComputedStyle(probe);
|
|
174
|
+
const result = {
|
|
175
|
+
style: cs.outlineStyle,
|
|
176
|
+
width: cs.outlineWidth,
|
|
177
|
+
color: cs.outlineColor,
|
|
178
|
+
offset: cs.outlineOffset,
|
|
179
|
+
wantStyle: want.outlineStyle,
|
|
180
|
+
wantWidth: want.outlineWidth,
|
|
181
|
+
wantColor: want.outlineColor,
|
|
182
|
+
wantOffset: want.outlineOffset,
|
|
183
|
+
};
|
|
184
|
+
probe.remove();
|
|
185
|
+
return result;
|
|
186
|
+
});
|
|
187
|
+
expect(ring).not.toBeNull();
|
|
188
|
+
expect(ring!.style).not.toBe('none');
|
|
189
|
+
expect(ring!.width).not.toBe('0px');
|
|
190
|
+
expect(ring!.style).toBe(ring!.wantStyle);
|
|
191
|
+
expect(ring!.width).toBe(ring!.wantWidth);
|
|
192
|
+
expect(ring!.color).toBe(ring!.wantColor);
|
|
193
|
+
expect(ring!.offset).toBe(ring!.wantOffset);
|
|
194
|
+
// Back on the option cell, the action cell drops the ring.
|
|
195
|
+
await search.press('ArrowLeft');
|
|
196
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
197
|
+
const after = await page
|
|
198
|
+
.getByRole('row', {name: 'Bug'})
|
|
199
|
+
.locator('[data-multi-selector-action-cell]')
|
|
200
|
+
.evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
201
|
+
expect(after).toBe('none');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('the action-cell ring follows the shared input modality', async ({
|
|
205
|
+
page,
|
|
206
|
+
}) => {
|
|
207
|
+
await page.goto(
|
|
208
|
+
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
|
|
209
|
+
);
|
|
210
|
+
const search = page.getByRole('combobox');
|
|
211
|
+
await expect(search).toBeFocused();
|
|
212
|
+
const actionCell = (name: string) =>
|
|
213
|
+
page.getByRole('row', {name}).locator('[data-multi-selector-action-cell]');
|
|
214
|
+
const outlineStyle = async (name: string) =>
|
|
215
|
+
actionCell(name).evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
216
|
+
const hover = async (name: string) => {
|
|
217
|
+
const cell = await box(actionCell(name));
|
|
218
|
+
await page.mouse.move(cell.x + cell.width / 2, cell.y + cell.height / 2, {
|
|
219
|
+
steps: 6,
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Pointer input: a click into the search field, then a hover onto an
|
|
224
|
+
// action, moves the highlight without the ring, and leaves none behind.
|
|
225
|
+
await search.click();
|
|
226
|
+
await hover('Bug');
|
|
227
|
+
await expect
|
|
228
|
+
.poll(async () => activeDescendant(page))
|
|
229
|
+
.toBe('gridcell:Edit Bug');
|
|
230
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
231
|
+
// A modifier chord is not keyboard navigation for the shared tracker.
|
|
232
|
+
await search.press('Control+ArrowRight');
|
|
233
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
234
|
+
await page.mouse.move(1, 1, {steps: 6});
|
|
235
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
236
|
+
|
|
237
|
+
// Keyboard input: back onto the same cell, the ring is drawn.
|
|
238
|
+
await search.press('ArrowLeft');
|
|
239
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
240
|
+
await search.press('ArrowRight');
|
|
241
|
+
await expect
|
|
242
|
+
.poll(async () => activeDescendant(page))
|
|
243
|
+
.toBe('gridcell:Edit Bug');
|
|
244
|
+
expect(await outlineStyle('Bug')).not.toBe('none');
|
|
245
|
+
|
|
246
|
+
// Still keyboard-driven, a hover that moves the highlight to another
|
|
247
|
+
// row's action carries the ring with it.
|
|
248
|
+
await hover('P0');
|
|
249
|
+
await expect
|
|
250
|
+
.poll(async () => activeDescendant(page))
|
|
251
|
+
.toBe('gridcell:Edit P0');
|
|
252
|
+
expect(await outlineStyle('P0')).not.toBe('none');
|
|
253
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
254
|
+
});
|
|
255
|
+
|
|
146
256
|
test('under RTL the inline-end arrow is ArrowLeft', async ({page}) => {
|
|
147
257
|
await page.goto(
|
|
148
258
|
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions-rtl&viewMode=story`,
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* @file hooks.ts
|
|
7
7
|
* @input Uses React hooks, MultiSelectorOptionData type
|
|
8
8
|
* @output Exports useMultiCombobox hook
|
|
9
|
-
* @position
|
|
9
|
+
* @position Public hook, exported from the MultiSelector entry point; used by
|
|
10
|
+
* MultiSelector.tsx
|
|
10
11
|
*
|
|
11
12
|
* SYNC: When modified, update:
|
|
12
13
|
* - /packages/core/src/MultiSelector/index.ts
|
|
14
|
+
* - /packages/core/src/MultiSelector/useMultiCombobox.doc.mjs
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
import {useCallback, useRef, useState} from 'react';
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useMultiCombobox',
|
|
6
|
+
displayName: 'useMultiCombobox',
|
|
7
|
+
group: 'MultiSelector',
|
|
8
|
+
keywords: [
|
|
9
|
+
'combobox',
|
|
10
|
+
'multi-select',
|
|
11
|
+
'multiselect',
|
|
12
|
+
'listbox',
|
|
13
|
+
'grid',
|
|
14
|
+
'toggle',
|
|
15
|
+
'highlight',
|
|
16
|
+
'active option',
|
|
17
|
+
'aria-activedescendant',
|
|
18
|
+
'keyboard',
|
|
19
|
+
'headless',
|
|
20
|
+
'apg',
|
|
21
|
+
'accessibility',
|
|
22
|
+
'a11y',
|
|
23
|
+
],
|
|
24
|
+
params: [
|
|
25
|
+
{
|
|
26
|
+
name: 'options',
|
|
27
|
+
type: 'UseMultiComboboxOptions',
|
|
28
|
+
description: 'Configuration object.',
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'options.selectableItems',
|
|
33
|
+
type: 'MultiSelectorOptionData[]',
|
|
34
|
+
description:
|
|
35
|
+
'The options in the same order as they render. Options with `disabled` cannot be toggled and are skipped while moving through the open list; ArrowDown or ArrowUp that opens the list still lands on the first or last option.',
|
|
36
|
+
required: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'options.isOpen',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
description: 'Whether the list is shown. The caller owns this state.',
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'options.onOpen',
|
|
46
|
+
type: '() => unknown',
|
|
47
|
+
description:
|
|
48
|
+
'Called to show the list. Return `false` to refuse; the hook then leaves the highlight alone.',
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'options.onClose',
|
|
53
|
+
type: '() => void',
|
|
54
|
+
description: 'Called to hide the list. The highlight resets first.',
|
|
55
|
+
required: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'options.onToggle',
|
|
59
|
+
type: '(itemValue: string) => void',
|
|
60
|
+
description:
|
|
61
|
+
'Called with the value of the option to add or remove. Toggling keeps the list open.',
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'options.listboxId',
|
|
66
|
+
type: 'string',
|
|
67
|
+
description:
|
|
68
|
+
"The listbox (or grid) element's id. Option and action-cell ids are derived from it.",
|
|
69
|
+
required: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'options.hasSearch',
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
description:
|
|
75
|
+
'Whether the list has its own search field. Space then types instead of toggling, opening by a press or Enter does not pre-highlight (ArrowDown and ArrowUp still open on the first and last option), Delete and Backspace edit the query, and type-to-select is off. Home and End still jump to the first and last option, so a search input that keeps them for the caret should stop them before this handler and rely on PageUp and PageDown.',
|
|
76
|
+
default: 'false',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'options.onClear',
|
|
80
|
+
type: '() => void',
|
|
81
|
+
description:
|
|
82
|
+
'Clears every selected value. When provided, Delete or Backspace on the closed control clears them, so clearing is not pointer-only. Ignored while the list is open or with `hasSearch`.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'options.hasValue',
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
description:
|
|
88
|
+
'Whether anything is selected. The Delete and Backspace clear is skipped when false.',
|
|
89
|
+
default: 'false',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'options.isGrid',
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
description:
|
|
95
|
+
"Whether each row pairs an option cell with an action cell. The inline-end arrow then moves the highlight to the row's action and the inline-start arrow back, following visual direction in right-to-left layouts, and Enter on the action runs `onActivateAction` instead of toggling.",
|
|
96
|
+
default: 'false',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'options.rowHasAction',
|
|
100
|
+
type: '(index: number) => boolean',
|
|
101
|
+
description:
|
|
102
|
+
'In a grid, whether the row at `index` has an action cell to move to.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'options.onActivateAction',
|
|
106
|
+
type: '(index: number) => void',
|
|
107
|
+
description:
|
|
108
|
+
"In a grid, runs the control in the highlighted row's action cell. The selection does not change.",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'options.isDisabled',
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
description: 'Whether the control ignores clicks and keys.',
|
|
114
|
+
default: 'false',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
returns: [
|
|
118
|
+
{
|
|
119
|
+
name: 'highlightedIndex',
|
|
120
|
+
type: 'number',
|
|
121
|
+
description: 'Index of the active row, or -1 when none is active.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'setHighlightedIndex',
|
|
125
|
+
type: '(index: number) => void',
|
|
126
|
+
description:
|
|
127
|
+
"Sets the active row, for example after filtering. The highlight lands on the row's option cell.",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'highlightedCell',
|
|
131
|
+
type: "'option' | 'action'",
|
|
132
|
+
description:
|
|
133
|
+
"Which cell of the active row is highlighted; `'action'` only in a grid.",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'activeDescendantId',
|
|
137
|
+
type: 'string | undefined',
|
|
138
|
+
description:
|
|
139
|
+
"The id to pass as the control's `aria-activedescendant`: the active option, or its action cell in a grid. Undefined while the list is closed or nothing is active.",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'getItemId',
|
|
143
|
+
type: '(index: number) => string',
|
|
144
|
+
description: 'The id to render on the option at `index`.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'getActionCellId',
|
|
148
|
+
type: '(index: number) => string',
|
|
149
|
+
description:
|
|
150
|
+
'In a grid, the id to render on the action cell of row `index`.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'onTriggerClick',
|
|
154
|
+
type: '() => void',
|
|
155
|
+
description: 'Toggles the list from a press on the control.',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'onKeyDown',
|
|
159
|
+
type: '(e: React.KeyboardEvent) => void',
|
|
160
|
+
description:
|
|
161
|
+
'Key handler for the control that keeps focus. ArrowDown and ArrowUp open the list or move the active row; Enter (and Space without search) toggles the active option and keeps the list open; Escape and Tab close it; Home, End, PageUp and PageDown jump to the first or last option; without search, typed characters move to the first option whose label starts with them.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'onItemMouseEnter',
|
|
165
|
+
type: "(item: MultiSelectorOptionData, index: number, cell?: 'option' | 'action') => void",
|
|
166
|
+
description:
|
|
167
|
+
'Makes the hovered option, or its action cell, active without scrolling the list or moving focus.',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
usage: {
|
|
171
|
+
description:
|
|
172
|
+
'Tracks the active option of a multi-select combobox and handles its keys, while DOM focus stays on the control. It powers MultiSelector; unlike useCombobox, toggling an option keeps the list open, and rows can carry an action cell. It renders nothing and sets no attributes: the caller renders the control and the list, and wires the ARIA attributes from what it returns.',
|
|
173
|
+
bestPractices: [
|
|
174
|
+
{
|
|
175
|
+
guidance: true,
|
|
176
|
+
description:
|
|
177
|
+
"Render each option with `id={getItemId(index)}` and pass `activeDescendantId` as the control's `aria-activedescendant`.",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
guidance: true,
|
|
181
|
+
description:
|
|
182
|
+
'Use `isGrid` only when rows carry a control next to the option; give each action cell `id={getActionCellId(index)}`.',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
guidance: true,
|
|
186
|
+
description:
|
|
187
|
+
'Announce the number of results through useAnnounce when the options change for a new query.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
guidance: false,
|
|
191
|
+
description:
|
|
192
|
+
'Close the list after each toggle; a multi-select stays open so several options can be picked in a row.',
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
relatedComponents: ['MultiSelector'],
|
|
197
|
+
relatedHooks: ['useCombobox', 'useAnnounce'],
|
|
198
|
+
importPath: '@astryxdesign/core/MultiSelector',
|
|
199
|
+
category: 'focus',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
203
|
+
export const docsDense = {
|
|
204
|
+
description:
|
|
205
|
+
'Multi-select combobox state + keys; focus stays on control. Powers MultiSelector. Toggle keeps list open; rows may carry an action cell (grid). Renders nothing, sets no attrs: caller renders + wires ARIA.',
|
|
206
|
+
paramDescriptions: {
|
|
207
|
+
options: 'config.',
|
|
208
|
+
'options.selectableItems':
|
|
209
|
+
'options in render order; disabled ones not toggleable, skipped while moving in open list (ArrowDown/Up opening still lands first / last).',
|
|
210
|
+
'options.isOpen': 'list shown; caller owns state.',
|
|
211
|
+
'options.onOpen':
|
|
212
|
+
'show list; return false to refuse (highlight untouched).',
|
|
213
|
+
'options.onClose': 'hide list; highlight resets first.',
|
|
214
|
+
'options.onToggle': 'add / remove value; list stays open.',
|
|
215
|
+
'options.listboxId':
|
|
216
|
+
'listbox / grid id; option + action-cell ids derive from it.',
|
|
217
|
+
'options.hasSearch':
|
|
218
|
+
'own search field: Space types, press / Enter opening does not pre-highlight (ArrowDown/Up still first / last), Delete / Backspace edit query, no type-to-select. Home / End still jump; stop them first to keep them for the caret, use PageUp / PageDown.',
|
|
219
|
+
'options.onClear':
|
|
220
|
+
'clear all; Delete / Backspace on closed control. ignored when open or hasSearch.',
|
|
221
|
+
'options.hasValue': 'anything selected; clear skipped when false.',
|
|
222
|
+
'options.isGrid':
|
|
223
|
+
'rows = option + action cell; inline-end / inline-start arrows move between them (RTL-aware); Enter on action -> onActivateAction.',
|
|
224
|
+
'options.rowHasAction': 'grid: row at index has an action cell.',
|
|
225
|
+
'options.onActivateAction':
|
|
226
|
+
"grid: run highlighted row's action; selection unchanged.",
|
|
227
|
+
'options.isDisabled': 'ignore clicks + keys.',
|
|
228
|
+
},
|
|
229
|
+
returnDescriptions: {
|
|
230
|
+
highlightedIndex: 'active row index, -1 = none.',
|
|
231
|
+
setHighlightedIndex: 'set active row (lands on option cell).',
|
|
232
|
+
highlightedCell: "'option' | 'action' (action only in grid).",
|
|
233
|
+
activeDescendantId:
|
|
234
|
+
'control aria-activedescendant: active option or its action cell; undefined when closed / none.',
|
|
235
|
+
getItemId: 'id for option at index.',
|
|
236
|
+
getActionCellId: 'grid: id for action cell of row index.',
|
|
237
|
+
onTriggerClick: 'toggle list from a press on control.',
|
|
238
|
+
onKeyDown:
|
|
239
|
+
'control keydown: ArrowDown/Up open or move; Enter (Space w/o search) toggles, stays open; Escape / Tab close; Home / End / PageUp / PageDown jump; typed chars jump by label prefix (w/o search).',
|
|
240
|
+
onItemMouseEnter:
|
|
241
|
+
'hover makes option / action cell active; no scroll, no focus move.',
|
|
242
|
+
},
|
|
243
|
+
usage: {
|
|
244
|
+
description:
|
|
245
|
+
'Headless multi-select combobox: active option + keys, focus stays on control; toggle keeps list open. Caller renders + wires ARIA.',
|
|
246
|
+
bestPractices: [
|
|
247
|
+
{
|
|
248
|
+
guidance: true,
|
|
249
|
+
description:
|
|
250
|
+
'Option id = getItemId(index); control aria-activedescendant = activeDescendantId.',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
guidance: true,
|
|
254
|
+
description:
|
|
255
|
+
'isGrid only when rows carry a control; action cell id = getActionCellId(index).',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
guidance: true,
|
|
259
|
+
description: 'Announce result count via useAnnounce on new query.',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
guidance: false,
|
|
263
|
+
description: 'Close after each toggle; multi-select stays open.',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
};
|