@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
package/src/Button/Button.tsx
CHANGED
|
@@ -594,26 +594,37 @@ export function Button({
|
|
|
594
594
|
// the consumer is deliberately showing it.
|
|
595
595
|
const delaySpinner = isPending || isInterruptible;
|
|
596
596
|
const groupDisabled = buttonGroup?.isDisabled ?? false;
|
|
597
|
-
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
|
|
597
|
+
const isTrueDisabled = isDisabled || groupDisabled;
|
|
598
|
+
// Busy-only: the button can't fire again purely because an action is in
|
|
599
|
+
// flight, not because it's genuinely isDisabled/group-disabled. Per the API
|
|
600
|
+
// Conventions "Disabled vs Busy" rule, busy is visual-only (aria-busy +
|
|
601
|
+
// spinner) and must never take focusability away — the click-handler ref
|
|
602
|
+
// guard (actionInFlightRef) is what actually makes a fire-once action fire
|
|
603
|
+
// once, so the native disabled attribute adds nothing but a focus loss
|
|
604
|
+
// here. When interruptible, the loading state drives the spinner and
|
|
605
|
+
// aria-busy but not disabled at all, so clicks keep landing and can
|
|
606
|
+
// interrupt the in-flight action.
|
|
607
|
+
const isBusyOnlyDisabled =
|
|
608
|
+
!isTrueDisabled && isLoadingState && !isInterruptible;
|
|
609
|
+
const buttonDisabled = isTrueDisabled || isBusyOnlyDisabled;
|
|
605
610
|
// isIconOnly prop is the source of truth for icon-only rendering.
|
|
606
611
|
// When false (default), label is always rendered as visible text.
|
|
607
612
|
|
|
608
613
|
const LinkComponent = useLinkComponent(as);
|
|
609
614
|
|
|
610
|
-
// Render as link when href is provided and button is not
|
|
611
|
-
// Disabled links are an accessibility anti-pattern
|
|
612
|
-
|
|
615
|
+
// Render as link when href is provided and the button is not TRULY
|
|
616
|
+
// disabled. Disabled links are an accessibility anti-pattern, so a
|
|
617
|
+
// genuinely isDisabled/group-disabled button falls back to <button>.
|
|
618
|
+
// Busy-only does NOT fall back: swapping <a> for a disabled <button>
|
|
619
|
+
// mid-action would drop focus the same way the native attribute does
|
|
620
|
+
// (credit @AKnassa, #4879), which is exactly the bug this PR fixes.
|
|
621
|
+
const renderAsLink = href != null && !isTrueDisabled;
|
|
613
622
|
|
|
614
|
-
// Use aria-disabled
|
|
615
|
-
//
|
|
616
|
-
|
|
623
|
+
// Use aria-disabled (button stays focusable) when busy-only, or when a
|
|
624
|
+
// tooltip is present so keyboard users can still reach it. A genuinely
|
|
625
|
+
// isDisabled/group-disabled button (not busy) keeps native disabled.
|
|
626
|
+
const useAriaDisabled =
|
|
627
|
+
buttonDisabled && (isBusyOnlyDisabled || tooltip != null);
|
|
617
628
|
|
|
618
629
|
// Attach tooltip behavior via the hook rather than wrapping the button in a
|
|
619
630
|
// <Tooltip> element. The hook adds hover/focus triggers to the button itself,
|
|
@@ -630,6 +641,16 @@ export function Button({
|
|
|
630
641
|
// a re-click while pending starts a fresh action that interrupts the prior.
|
|
631
642
|
if (buttonDisabled || (actionInFlightRef.current && !isInterruptible)) {
|
|
632
643
|
e.preventDefault();
|
|
644
|
+
// A native `disabled` element never dispatches a click at all, so it
|
|
645
|
+
// never reaches a wrapping element's own handler either. An
|
|
646
|
+
// aria-disabled button (busy-only, or with a tooltip) is still a real,
|
|
647
|
+
// interactive element the DOM keeps dispatching clicks on —
|
|
648
|
+
// preventDefault alone only cancels the button's OWN default action
|
|
649
|
+
// (form submit, link navigation), not propagation. Without this, an
|
|
650
|
+
// unavailable Button inside a clickable row/card/backdrop still
|
|
651
|
+
// triggers that ancestor's action, which the previously native-disabled
|
|
652
|
+
// control never did.
|
|
653
|
+
e.stopPropagation();
|
|
633
654
|
return;
|
|
634
655
|
}
|
|
635
656
|
props.onClick?.(e);
|
|
@@ -669,7 +690,7 @@ export function Button({
|
|
|
669
690
|
isIconOnly && styles.iconOnly,
|
|
670
691
|
interactionOverlayStyles.backgroundImage,
|
|
671
692
|
buttonDisabled && styles.inactive,
|
|
672
|
-
|
|
693
|
+
isTrueDisabled && styles.disabled,
|
|
673
694
|
useAriaDisabled && styles.ariaDisabled,
|
|
674
695
|
renderAsLink && styles.link,
|
|
675
696
|
!buttonGroup && styles.pressable,
|
|
@@ -797,7 +818,9 @@ export function Button({
|
|
|
797
818
|
{...describedByProp}
|
|
798
819
|
{...edgeCompAttr}
|
|
799
820
|
aria-busy={isLoadingState || undefined}
|
|
800
|
-
|
|
821
|
+
aria-disabled={useAriaDisabled || undefined}
|
|
822
|
+
onClick={handleClick}
|
|
823
|
+
{...(handleKeyDown ? {onKeyDown: handleKeyDown} : null)}>
|
|
801
824
|
{buttonContent}
|
|
802
825
|
</LinkComponent>
|
|
803
826
|
);
|
|
@@ -22,65 +22,4 @@
|
|
|
22
22
|
|
|
23
23
|
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
24
24
|
|
|
25
|
-
export const BUTTON_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
|
|
26
|
-
// ---- Button and IconButton go natively disabled while busy --------------
|
|
27
|
-
// One defect, one record per component.
|
|
28
|
-
// They are written out rather than generated, because a record has to name
|
|
29
|
-
// exactly one thing that is broken — a loop would make it easy to widen this
|
|
30
|
-
// later without anyone noticing.
|
|
31
|
-
{
|
|
32
|
-
expectation: 'button.focus.reachable-and-escapable',
|
|
33
|
-
binding: 'Button',
|
|
34
|
-
state: 'button-loading',
|
|
35
|
-
evidenceLayer: 'real-browser',
|
|
36
|
-
failureEquals:
|
|
37
|
-
'10 presses of Tab from the start of the document never reached the button, so a keyboard user cannot get to this action',
|
|
38
|
-
standardsReference:
|
|
39
|
-
'WCAG 2.2 2.1.1 Keyboard and 2.1.2 No Keyboard Trap (Level A)',
|
|
40
|
-
userImpact:
|
|
41
|
-
'A keyboard user who activates Save and waits is dropped to the top of the document the moment the action starts, and the button leaves the tab sequence entirely — so they cannot get back to it, to see that it is busy or to interrupt it. The next Tab restarts from the beginning of the page.',
|
|
42
|
-
reason:
|
|
43
|
-
'Button sets the native disabled attribute while a clickAction is pending. A natively disabled element is neither focusable nor a tab stop. Separate implementation work will turn this record into an unexpected pass.',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
expectation: 'button.focus.reachable-and-escapable',
|
|
47
|
-
binding: 'IconButton',
|
|
48
|
-
state: 'icon-button-loading',
|
|
49
|
-
evidenceLayer: 'real-browser',
|
|
50
|
-
failureEquals:
|
|
51
|
-
'10 presses of Tab from the start of the document never reached the button, so a keyboard user cannot get to this action',
|
|
52
|
-
standardsReference:
|
|
53
|
-
'WCAG 2.2 2.1.1 Keyboard and 2.1.2 No Keyboard Trap (Level A)',
|
|
54
|
-
userImpact:
|
|
55
|
-
'The same drop from an icon button, where it is worse: an icon button is usually one of several in a row, so the user loses their place among them.',
|
|
56
|
-
reason:
|
|
57
|
-
'IconButton is a thin wrapper over Button and inherits the behaviour exactly.',
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
expectation: 'button.unavailable.inert',
|
|
62
|
-
binding: 'Button',
|
|
63
|
-
state: 'button-loading',
|
|
64
|
-
evidenceLayer: 'real-browser',
|
|
65
|
-
failureEquals:
|
|
66
|
-
'this state is declared focusable so its reason stays reachable, but it cannot take focus — so a keyboard user can neither read why it is unavailable nor reach it to confirm it refuses to act',
|
|
67
|
-
standardsReference:
|
|
68
|
-
'Current Astryx family:buttons FR3 and WAI-ARIA APG Button unavailable-state requirement; supports WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
|
|
69
|
-
userImpact:
|
|
70
|
-
'The third face of the same defect: because the busy button cannot take focus, there is no way to confirm from the keyboard that it declines a second press — the user can neither reach it nor see why it is unavailable.',
|
|
71
|
-
reason:
|
|
72
|
-
'Same native disabled attribute. Recorded separately because a record names exactly one outcome, and a fix that restored focus without keeping the button inert would satisfy one of these and not the other.',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
expectation: 'button.unavailable.inert',
|
|
76
|
-
binding: 'IconButton',
|
|
77
|
-
state: 'icon-button-loading',
|
|
78
|
-
evidenceLayer: 'real-browser',
|
|
79
|
-
failureEquals:
|
|
80
|
-
'this state is declared focusable so its reason stays reachable, but it cannot take focus — so a keyboard user can neither read why it is unavailable nor reach it to confirm it refuses to act',
|
|
81
|
-
standardsReference:
|
|
82
|
-
'Current Astryx family:buttons FR3 and WAI-ARIA APG Button unavailable-state requirement; supports WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
|
|
83
|
-
userImpact: 'The same, from an icon button.',
|
|
84
|
-
reason: 'Inherited from Button, as above.',
|
|
85
|
-
},
|
|
86
|
-
];
|
|
25
|
+
export const BUTTON_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
|
|
@@ -177,16 +177,9 @@ export const BUTTON_BINDING_STATES = [
|
|
|
177
177
|
binding: 'Button',
|
|
178
178
|
summary:
|
|
179
179
|
'a button waiting on the action it started: the action is unavailable, and it is meant to stay focusable so the user can see that and interrupt it',
|
|
180
|
-
// `focusable: true` is the declaration, and the component does not deliver
|
|
181
|
-
// it — see the known-failure records. Declaring what the component does
|
|
182
|
-
// instead would make the expectations that catch it not-applicable, which
|
|
183
|
-
// is how a contract quietly stops noticing a defect.
|
|
184
180
|
facts: facts({operable: false, unavailable: true}),
|
|
185
181
|
visibleLabel: null,
|
|
186
182
|
storyId: 'a11y-button-pattern--button-loading',
|
|
187
|
-
declaredNotDelivered: [
|
|
188
|
-
{fact: 'focusable', owned: 'button.focus.reachable-and-escapable'},
|
|
189
|
-
],
|
|
190
183
|
},
|
|
191
184
|
|
|
192
185
|
// ---- IconButton ---------------------------------------------------------
|
|
@@ -214,9 +207,6 @@ export const BUTTON_BINDING_STATES = [
|
|
|
214
207
|
facts: facts({operable: false, unavailable: true}),
|
|
215
208
|
visibleLabel: null,
|
|
216
209
|
storyId: 'a11y-button-pattern--icon-button-loading',
|
|
217
|
-
declaredNotDelivered: [
|
|
218
|
-
{fact: 'focusable', owned: 'button.focus.reachable-and-escapable'},
|
|
219
|
-
],
|
|
220
210
|
},
|
|
221
211
|
|
|
222
212
|
// ---- ClickableCard ------------------------------------------------------
|
package/src/Card/Card.doc.mjs
CHANGED
|
@@ -83,7 +83,7 @@ export const docs = {
|
|
|
83
83
|
code: `
|
|
84
84
|
import {Card} from '@astryxdesign/core/Card';
|
|
85
85
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
86
|
-
import {VStack} from '@astryxdesign/core/
|
|
86
|
+
import {VStack} from '@astryxdesign/core/VStack';
|
|
87
87
|
import {Heading} from '@astryxdesign/core/Heading';
|
|
88
88
|
import {Text} from '@astryxdesign/core/Text';
|
|
89
89
|
|
|
@@ -395,7 +395,9 @@ test('captures narrow touch reflow in both modes', async ({browser}) => {
|
|
|
395
395
|
try {
|
|
396
396
|
const page = await context.newPage();
|
|
397
397
|
const scenario = CASES.find(item => item.name === 'narrow-overflow');
|
|
398
|
-
if (!scenario) {
|
|
398
|
+
if (!scenario) {
|
|
399
|
+
throw new Error('Narrow metadata fixture is missing');
|
|
400
|
+
}
|
|
399
401
|
const failures: string[] = [];
|
|
400
402
|
for (const mode of ['light', 'dark'] as const) {
|
|
401
403
|
failures.push(
|
|
@@ -209,6 +209,40 @@ describe('CheckboxInput', () => {
|
|
|
209
209
|
);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
it('preserves a caller-provided id on the input and its label', () => {
|
|
213
|
+
render(
|
|
214
|
+
<CheckboxInput
|
|
215
|
+
id="accept-terms"
|
|
216
|
+
label="Accept terms"
|
|
217
|
+
value={false}
|
|
218
|
+
onChange={() => {}}
|
|
219
|
+
/>,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const checkbox = screen.getByRole('checkbox');
|
|
223
|
+
expect(checkbox).toHaveAttribute('id', 'accept-terms');
|
|
224
|
+
expect(screen.getByText('Accept terms').closest('label')).toHaveAttribute(
|
|
225
|
+
'for',
|
|
226
|
+
'accept-terms',
|
|
227
|
+
);
|
|
228
|
+
expect(screen.getByLabelText('Accept terms')).toBe(checkbox);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('generates an id the label resolves when the caller passes an empty id', () => {
|
|
232
|
+
render(
|
|
233
|
+
<CheckboxInput
|
|
234
|
+
id=""
|
|
235
|
+
label="Accept terms"
|
|
236
|
+
value={false}
|
|
237
|
+
onChange={() => {}}
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const checkbox = screen.getByRole('checkbox');
|
|
242
|
+
expect(checkbox.id).not.toBe('');
|
|
243
|
+
expect(screen.getByLabelText('Accept terms')).toBe(checkbox);
|
|
244
|
+
});
|
|
245
|
+
|
|
212
246
|
it('forwards arbitrary data-* attributes to the input', () => {
|
|
213
247
|
render(
|
|
214
248
|
<CheckboxInput
|
|
@@ -311,6 +311,7 @@ const dynamicWidthStyles = stylex.create({
|
|
|
311
311
|
* ```
|
|
312
312
|
*/
|
|
313
313
|
export function CheckboxInput({
|
|
314
|
+
id: idProp,
|
|
314
315
|
label,
|
|
315
316
|
isLabelHidden = false,
|
|
316
317
|
description,
|
|
@@ -340,7 +341,8 @@ export function CheckboxInput({
|
|
|
340
341
|
// The row is the pressable: the overlay's pressed arm reads the row's
|
|
341
342
|
// scope marker, the way the indicator's hover tint does.
|
|
342
343
|
const pressable = usePressFeedback();
|
|
343
|
-
const
|
|
344
|
+
const generatedId = useId();
|
|
345
|
+
const id = idProp || generatedId;
|
|
344
346
|
const descriptionID = useId();
|
|
345
347
|
const statusMessageID = useId();
|
|
346
348
|
// Announce the effective required state (form default included) while the
|
|
@@ -59,6 +59,28 @@ beforeEach(() => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
describe('CheckboxList', () => {
|
|
62
|
+
it('forwards CheckboxListItem align to its Item row unchanged', () => {
|
|
63
|
+
render(
|
|
64
|
+
<CheckboxList label="Toppings" value={[]} onChange={() => {}}>
|
|
65
|
+
<CheckboxListItem label="Cheese" value="cheese" data-testid="cheese" />
|
|
66
|
+
<CheckboxListItem
|
|
67
|
+
label="Olives"
|
|
68
|
+
value="olives"
|
|
69
|
+
align="first-line"
|
|
70
|
+
data-testid="olives"
|
|
71
|
+
/>
|
|
72
|
+
</CheckboxList>,
|
|
73
|
+
);
|
|
74
|
+
expect(screen.getByTestId('cheese')).toHaveAttribute(
|
|
75
|
+
'data-align',
|
|
76
|
+
'center',
|
|
77
|
+
);
|
|
78
|
+
expect(screen.getByTestId('olives')).toHaveAttribute(
|
|
79
|
+
'data-align',
|
|
80
|
+
'first-line',
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
62
84
|
it('renders with label', () => {
|
|
63
85
|
render(
|
|
64
86
|
<CheckboxList label="Preferences" value={[]} onChange={() => {}}>
|
|
@@ -55,6 +55,13 @@ export const docs = {
|
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
name: 'align',
|
|
60
|
+
type: "'center' | 'start' | 'first-line'",
|
|
61
|
+
description:
|
|
62
|
+
'Vertical alignment of the start and end content against the label, passed through to Item unchanged. `first-line` keeps a small mark (a checkbox) level with the first line of a wrapped label. See Item.align.',
|
|
63
|
+
default: "'center'",
|
|
64
|
+
},
|
|
58
65
|
{
|
|
59
66
|
name: 'isDisabled',
|
|
60
67
|
type: 'boolean',
|
|
@@ -130,6 +137,13 @@ export const docsZh = {
|
|
|
130
137
|
type: 'ReactNode',
|
|
131
138
|
description: '在标签区域后渲染的内容。',
|
|
132
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: 'align',
|
|
142
|
+
type: "'center' | 'start' | 'first-line'",
|
|
143
|
+
description:
|
|
144
|
+
'起始和结束内容相对于标签的垂直对齐方式,原样传递给 Item。`first-line` 让小标记(复选框)与换行标签的第一行保持水平。参见 Item.align。',
|
|
145
|
+
default: "'center'",
|
|
146
|
+
},
|
|
133
147
|
{
|
|
134
148
|
name: 'isDisabled',
|
|
135
149
|
type: 'boolean',
|
|
@@ -165,6 +179,8 @@ export const docsDense = {
|
|
|
165
179
|
description:
|
|
166
180
|
'Individual checkbox item w/ label, description, end content slot. Collection mode inside CheckboxList with value; standalone inside List or CheckboxList without value.',
|
|
167
181
|
propDescriptions: {
|
|
182
|
+
align:
|
|
183
|
+
"Start/end content alignment, passed to Item: 'center' (default), 'start', or 'first-line' (centered on the label's first line).",
|
|
168
184
|
label:
|
|
169
185
|
'Primary label. String or ReactNode; nested controls keep their behavior. A ReactNode names the checkbox from its visible text.',
|
|
170
186
|
'aria-label':
|
|
@@ -29,6 +29,7 @@ import {ListContext} from '../List/ListContext';
|
|
|
29
29
|
import {CheckboxListContext} from './CheckboxListContext';
|
|
30
30
|
import {useTranslator} from '../i18n';
|
|
31
31
|
import {ItemDescriptionContext} from '../Item/ItemDescriptionContext';
|
|
32
|
+
import type {ItemAlign} from '../Item';
|
|
32
33
|
|
|
33
34
|
// =============================================================================
|
|
34
35
|
// Styles
|
|
@@ -45,6 +46,13 @@ const styles = stylex.create({
|
|
|
45
46
|
// =============================================================================
|
|
46
47
|
|
|
47
48
|
export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
|
|
49
|
+
/**
|
|
50
|
+
* Vertical alignment of the start and end content against the label,
|
|
51
|
+
* forwarded to Item. Use `first-line` to keep the checkbox level with the
|
|
52
|
+
* first line of a wrapped label.
|
|
53
|
+
* @default 'center'
|
|
54
|
+
*/
|
|
55
|
+
align?: ItemAlign;
|
|
48
56
|
/**
|
|
49
57
|
* Primary text label for the item.
|
|
50
58
|
*
|
|
@@ -192,6 +200,7 @@ export function CheckboxListItem({
|
|
|
192
200
|
className,
|
|
193
201
|
style,
|
|
194
202
|
onClick: onClickProp,
|
|
203
|
+
align,
|
|
195
204
|
...restProps
|
|
196
205
|
}: CheckboxListItemProps) {
|
|
197
206
|
const t = useTranslator();
|
|
@@ -297,6 +306,7 @@ export function CheckboxListItem({
|
|
|
297
306
|
label={namesFromVisibleLabel ? <span id={labelID}>{label}</span> : label}
|
|
298
307
|
description={description}
|
|
299
308
|
endContent={endContent}
|
|
309
|
+
align={align}
|
|
300
310
|
isDisabled={effectiveDisabled}
|
|
301
311
|
// Delegate row clicks to the checkbox instead of wiring onClick (which
|
|
302
312
|
// would add an invisible row button = a second tab stop). The checkbox
|
package/src/Code/Code.doc.mjs
CHANGED
|
@@ -8,12 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {createRef} from 'react';
|
|
11
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
12
|
-
import {render, screen, fireEvent} from '@testing-library/react';
|
|
11
|
+
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
+
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
13
|
+
import * as stylex from '@stylexjs/stylex';
|
|
13
14
|
import {CommandPaletteInput} from './CommandPaletteInput';
|
|
14
15
|
import {CommandPaletteContext} from './CommandPaletteContext';
|
|
15
16
|
import {Dialog} from '../Dialog';
|
|
16
17
|
import type {CommandPaletteContextValue} from './CommandPaletteContext';
|
|
18
|
+
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
19
|
+
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
__resetInteractionModalityForTest();
|
|
23
|
+
});
|
|
17
24
|
|
|
18
25
|
function makeContext(
|
|
19
26
|
overrides: Partial<CommandPaletteContextValue> = {},
|
|
@@ -279,6 +286,45 @@ describe('CommandPaletteInput', () => {
|
|
|
279
286
|
},
|
|
280
287
|
);
|
|
281
288
|
|
|
289
|
+
it('draws the shared ring only when keyboard navigation acquired focus', () => {
|
|
290
|
+
render(<CommandPaletteInput hasAutoFocus={false} />);
|
|
291
|
+
const input = screen.getByRole('searchbox');
|
|
292
|
+
const ringClasses = stylex
|
|
293
|
+
.props(focusOutlineStyles.focusVisible)
|
|
294
|
+
.className!.split(' ');
|
|
295
|
+
const isRinged = () =>
|
|
296
|
+
ringClasses.every(className => input.classList.contains(className));
|
|
297
|
+
|
|
298
|
+
fireEvent.pointerDown(input);
|
|
299
|
+
fireEvent.focus(input);
|
|
300
|
+
expect(isRinged()).toBe(false);
|
|
301
|
+
|
|
302
|
+
fireEvent.blur(input);
|
|
303
|
+
fireEvent.keyDown(input, {key: 'Tab'});
|
|
304
|
+
fireEvent.focus(input);
|
|
305
|
+
expect(isRinged()).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('preserves caller focus and blur handlers while tracking modality', () => {
|
|
309
|
+
const onFocus = vi.fn();
|
|
310
|
+
const onBlur = vi.fn();
|
|
311
|
+
render(
|
|
312
|
+
<CommandPaletteInput
|
|
313
|
+
hasAutoFocus={false}
|
|
314
|
+
onFocus={onFocus}
|
|
315
|
+
onBlur={onBlur}
|
|
316
|
+
/>,
|
|
317
|
+
);
|
|
318
|
+
const input = screen.getByRole('searchbox');
|
|
319
|
+
|
|
320
|
+
fireEvent.keyDown(input, {key: 'Tab'});
|
|
321
|
+
fireEvent.focus(input);
|
|
322
|
+
fireEvent.blur(input);
|
|
323
|
+
|
|
324
|
+
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
325
|
+
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
326
|
+
});
|
|
327
|
+
|
|
282
328
|
it('does not schedule focus when hasAutoFocus is false', async () => {
|
|
283
329
|
const focusSpy = vi.spyOn(HTMLElement.prototype, 'focus');
|
|
284
330
|
render(<CommandPaletteInput hasAutoFocus={false} />);
|
|
@@ -481,8 +527,11 @@ describe('CommandPaletteInput popup context', () => {
|
|
|
481
527
|
</CommandPaletteContext>,
|
|
482
528
|
);
|
|
483
529
|
|
|
484
|
-
await
|
|
485
|
-
|
|
530
|
+
await act(async () => {
|
|
531
|
+
await new Promise<void>(resolve =>
|
|
532
|
+
requestAnimationFrame(() => resolve()),
|
|
533
|
+
);
|
|
486
534
|
});
|
|
535
|
+
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
487
536
|
});
|
|
488
537
|
});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - /packages/cli/assets/templates/blocks/components/CommandPalette/ (showcase blocks)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {useCallback, useEffect, useRef, type ReactNode} from 'react';
|
|
16
|
+
import {useCallback, useEffect, useRef, useState, type ReactNode} from 'react';
|
|
17
17
|
import * as stylex from '@stylexjs/stylex';
|
|
18
18
|
import {Icon} from '../Icon';
|
|
19
19
|
import {Spinner} from '../Spinner';
|
|
@@ -28,6 +28,11 @@ import {useCommandPaletteContext} from './CommandPaletteContext';
|
|
|
28
28
|
import {useDialogContext} from '../Dialog/DialogContext';
|
|
29
29
|
import type {BaseProps} from '../BaseProps';
|
|
30
30
|
import {themeProps} from '../utils/themeProps';
|
|
31
|
+
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
32
|
+
import {
|
|
33
|
+
getInteractionModality,
|
|
34
|
+
useInteractionModalityTracking,
|
|
35
|
+
} from '../utils/interactionModality';
|
|
31
36
|
import {useTranslator} from '../i18n';
|
|
32
37
|
|
|
33
38
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
@@ -71,7 +76,6 @@ const styles = stylex.create({
|
|
|
71
76
|
minWidth: 0,
|
|
72
77
|
borderWidth: 0,
|
|
73
78
|
borderStyle: 'none',
|
|
74
|
-
outline: 'none',
|
|
75
79
|
backgroundColor: 'transparent',
|
|
76
80
|
color: colorVars['--color-text-primary'],
|
|
77
81
|
fontFamily: typographyVars['--font-family-body'],
|
|
@@ -169,6 +173,8 @@ export function CommandPaletteInput({
|
|
|
169
173
|
endContent,
|
|
170
174
|
onChange,
|
|
171
175
|
onKeyDown,
|
|
176
|
+
onFocus,
|
|
177
|
+
onBlur,
|
|
172
178
|
ref,
|
|
173
179
|
xstyle,
|
|
174
180
|
className,
|
|
@@ -184,6 +190,26 @@ export function CommandPaletteInput({
|
|
|
184
190
|
const hasListbox = ctx ? ctx.selectableItems.length > 0 : true;
|
|
185
191
|
const dialogContext = useDialogContext();
|
|
186
192
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
193
|
+
const [isKeyboardFocus, setIsKeyboardFocus] = useState(false);
|
|
194
|
+
|
|
195
|
+
// Text-entry controls match :focus-visible after pointer focus in Chromium.
|
|
196
|
+
// Keep the shared selector, but narrow its ring to focus acquired through
|
|
197
|
+
// keyboard navigation so a click or pointer-opened palette stays unringed.
|
|
198
|
+
useInteractionModalityTracking();
|
|
199
|
+
const handleFocus = useCallback(
|
|
200
|
+
(event: React.FocusEvent<HTMLInputElement>) => {
|
|
201
|
+
setIsKeyboardFocus(getInteractionModality() === 'keyboard');
|
|
202
|
+
onFocus?.(event);
|
|
203
|
+
},
|
|
204
|
+
[onFocus],
|
|
205
|
+
);
|
|
206
|
+
const handleBlur = useCallback(
|
|
207
|
+
(event: React.FocusEvent<HTMLInputElement>) => {
|
|
208
|
+
setIsKeyboardFocus(false);
|
|
209
|
+
onBlur?.(event);
|
|
210
|
+
},
|
|
211
|
+
[onBlur],
|
|
212
|
+
);
|
|
187
213
|
|
|
188
214
|
// Use context values as fallback
|
|
189
215
|
const value = controlledValue ?? ctx?.search;
|
|
@@ -259,7 +285,9 @@ export function CommandPaletteInput({
|
|
|
259
285
|
<input
|
|
260
286
|
{...props}
|
|
261
287
|
ref={useMergedRefs(ref, inputRef)}
|
|
262
|
-
{...
|
|
288
|
+
{...(isKeyboardFocus
|
|
289
|
+
? focusOutlineProps.focusVisible(styles.input)
|
|
290
|
+
: focusOutlineProps.suppressed(styles.input))}
|
|
263
291
|
type="text"
|
|
264
292
|
role={ctx ? 'combobox' : standaloneRole}
|
|
265
293
|
aria-expanded={
|
|
@@ -300,6 +328,8 @@ export function CommandPaletteInput({
|
|
|
300
328
|
onChange?.(e);
|
|
301
329
|
}}
|
|
302
330
|
onKeyDown={handleKeyDown}
|
|
331
|
+
onFocus={handleFocus}
|
|
332
|
+
onBlur={handleBlur}
|
|
303
333
|
/>
|
|
304
334
|
{(ctx?.isBusy || hasEndContent) && (
|
|
305
335
|
<span {...stylex.props(styles.end)}>
|