@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
|
@@ -828,6 +828,169 @@ describe('SideNavItem', () => {
|
|
|
828
828
|
expect(screen.getByText('Security')).toBeInTheDocument();
|
|
829
829
|
});
|
|
830
830
|
|
|
831
|
+
it('keeps nested rows full width and indents their content inside the row', () => {
|
|
832
|
+
render(
|
|
833
|
+
<SideNavItem label="Settings">
|
|
834
|
+
<SideNavItem label="General" href="/settings/general">
|
|
835
|
+
<SideNavItem label="Advanced" href="/settings/general/advanced" />
|
|
836
|
+
</SideNavItem>
|
|
837
|
+
</SideNavItem>,
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// The children group adds no inset of its own, so a nested row's hover
|
|
841
|
+
// and selected fill spans the nav list like a top-level row's.
|
|
842
|
+
const groups = screen.getAllByRole('group', {hidden: true});
|
|
843
|
+
for (const group of groups) {
|
|
844
|
+
expect(declaredOn(group, 'padding-inline-start')).toEqual([]);
|
|
845
|
+
expect(
|
|
846
|
+
declaredOn(group.firstElementChild!, 'padding-inline-start'),
|
|
847
|
+
).toEqual([]);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// The indent lives inside each row instead, one step per nesting level.
|
|
851
|
+
// An ordinary row is the link itself: its own padding plus the steps.
|
|
852
|
+
const top = screen.getByRole('button', {name: 'Settings'});
|
|
853
|
+
const general = screen.getByRole('link', {name: 'General'});
|
|
854
|
+
const advanced = screen.getByRole('link', {name: 'Advanced'});
|
|
855
|
+
expect(indentOf(top)).toBeNull();
|
|
856
|
+
expect(indentOf(advanced)).toBe(
|
|
857
|
+
'calc(var(--spacing-2) + 2 * var(--spacing-6))',
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
// A split row (own href plus children) keeps the wrapper's base padding
|
|
861
|
+
// and puts the steps on the link, so the whole indent is clickable
|
|
862
|
+
// instead of dead space that still shows hover and press feedback.
|
|
863
|
+
expect(indentOf(general.parentElement!)).toBeNull();
|
|
864
|
+
expect(indentOf(general)).toBe('calc(1 * var(--spacing-6))');
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('clips the children group only while it is collapsed', async () => {
|
|
868
|
+
// Nested rows sit flush with the group, so a permanent clip would cut off
|
|
869
|
+
// their focus ring. The group clips for the collapse animation only.
|
|
870
|
+
const user = userEvent.setup();
|
|
871
|
+
render(
|
|
872
|
+
<SideNavItem label="Settings" collapsible>
|
|
873
|
+
<SideNavItem label="General" href="/settings/general" />
|
|
874
|
+
</SideNavItem>,
|
|
875
|
+
);
|
|
876
|
+
const group = () => screen.getByRole('group', {hidden: true});
|
|
877
|
+
expect(declaredOn(group(), 'overflow')).toEqual(['visible']);
|
|
878
|
+
expect(declaredOn(group().firstElementChild!, 'overflow')).toEqual([]);
|
|
879
|
+
|
|
880
|
+
await user.click(screen.getByRole('button', {name: 'Settings'}));
|
|
881
|
+
expect(declaredOn(group(), 'overflow')).toEqual(['hidden']);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('puts the indent on the primary link of a nested actions row', () => {
|
|
885
|
+
render(
|
|
886
|
+
<SideNavItem label="Projects">
|
|
887
|
+
<SideNavItem
|
|
888
|
+
label="Alpha"
|
|
889
|
+
href="/projects/alpha"
|
|
890
|
+
actions={
|
|
891
|
+
<button type="button" aria-label="Alpha actions">
|
|
892
|
+
More
|
|
893
|
+
</button>
|
|
894
|
+
}
|
|
895
|
+
/>
|
|
896
|
+
</SideNavItem>,
|
|
897
|
+
);
|
|
898
|
+
const alpha = screen.getByRole('link', {name: 'Alpha'});
|
|
899
|
+
expect(indentOf(alpha.parentElement!)).toBeNull();
|
|
900
|
+
expect(indentOf(alpha)).toBe('calc(1 * var(--spacing-6))');
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it("composes a caller's style with the indent on a nested row", () => {
|
|
904
|
+
render(
|
|
905
|
+
<SideNavItem label="Settings">
|
|
906
|
+
<SideNavItem
|
|
907
|
+
label="General"
|
|
908
|
+
href="/settings/general"
|
|
909
|
+
style={{color: 'rebeccapurple'}}
|
|
910
|
+
/>
|
|
911
|
+
<SideNavItem
|
|
912
|
+
label="Security"
|
|
913
|
+
href="/settings/security"
|
|
914
|
+
style={{color: 'rebeccapurple'}}>
|
|
915
|
+
<SideNavItem label="Keys" href="/settings/security/keys" />
|
|
916
|
+
</SideNavItem>
|
|
917
|
+
</SideNavItem>,
|
|
918
|
+
);
|
|
919
|
+
// Ordinary row: the link is the row.
|
|
920
|
+
const general = screen.getByRole('link', {name: 'General'});
|
|
921
|
+
expect(general.style.color).toBe('rebeccapurple');
|
|
922
|
+
expect(indentOf(general)).toBe(
|
|
923
|
+
'calc(var(--spacing-2) + 1 * var(--spacing-6))',
|
|
924
|
+
);
|
|
925
|
+
// Split row: the caller's style still lands on the primary link.
|
|
926
|
+
const security = screen.getByRole('link', {name: 'Security'});
|
|
927
|
+
expect(security.style.color).toBe('rebeccapurple');
|
|
928
|
+
expect(indentOf(security)).toBe('calc(1 * var(--spacing-6))');
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it("lets a caller's inline-start padding replace the indent", () => {
|
|
932
|
+
// The caller's `style` composes after the indent: a caller-set
|
|
933
|
+
// inline-start padding is an inline declaration, so it wins over the
|
|
934
|
+
// indent's class rule on the element that carries the indent, on both
|
|
935
|
+
// the ordinary and the split row path.
|
|
936
|
+
render(
|
|
937
|
+
<SideNavItem label="Settings">
|
|
938
|
+
<SideNavItem
|
|
939
|
+
label="General"
|
|
940
|
+
href="/settings/general"
|
|
941
|
+
style={{paddingInlineStart: '20px'}}
|
|
942
|
+
/>
|
|
943
|
+
<SideNavItem
|
|
944
|
+
label="Security"
|
|
945
|
+
href="/settings/security"
|
|
946
|
+
style={{paddingInlineStart: '20px'}}>
|
|
947
|
+
<SideNavItem label="Keys" href="/settings/security/keys" />
|
|
948
|
+
</SideNavItem>
|
|
949
|
+
</SideNavItem>,
|
|
950
|
+
);
|
|
951
|
+
const general = screen.getByRole('link', {name: 'General'});
|
|
952
|
+
expect(general.style.paddingInlineStart).toBe('20px');
|
|
953
|
+
const security = screen.getByRole('link', {name: 'Security'});
|
|
954
|
+
expect(security.style.paddingInlineStart).toBe('20px');
|
|
955
|
+
// A row whose caller sets no padding keeps its indent.
|
|
956
|
+
expect(indentOf(screen.getByRole('link', {name: 'Keys'}))).toBe(
|
|
957
|
+
'calc(var(--spacing-2) + 2 * var(--spacing-6))',
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('indents a small nested row from the small row padding', () => {
|
|
962
|
+
render(
|
|
963
|
+
<SideNavItem label="Settings">
|
|
964
|
+
<SideNavItem label="General" href="/settings/general" size="sm" />
|
|
965
|
+
</SideNavItem>,
|
|
966
|
+
);
|
|
967
|
+
expect(indentOf(screen.getByRole('link', {name: 'General'}))).toBe(
|
|
968
|
+
'calc(var(--spacing-1) + 1 * var(--spacing-6))',
|
|
969
|
+
);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
it('does not indent the first level of a collapsed rail flyout', async () => {
|
|
973
|
+
const user = userEvent.setup();
|
|
974
|
+
renderCollapsed(
|
|
975
|
+
<SideNavItem label="Settings" icon={StubIcon}>
|
|
976
|
+
<SideNavItem label="General" href="/settings/general">
|
|
977
|
+
<SideNavItem label="Advanced" href="/settings/general/advanced" />
|
|
978
|
+
</SideNavItem>
|
|
979
|
+
</SideNavItem>,
|
|
980
|
+
);
|
|
981
|
+
await user.click(screen.getByRole('button', {name: 'Settings'}));
|
|
982
|
+
|
|
983
|
+
// jsdom leaves the native popover `display: none`, hence `hidden: true`.
|
|
984
|
+
// The flyout lists the parent's children as its own top level.
|
|
985
|
+
const general = screen.getByRole('link', {name: 'General', hidden: true});
|
|
986
|
+
const advanced = screen.getByRole('link', {name: 'Advanced', hidden: true});
|
|
987
|
+
expect(indentOf(general)).toBeNull();
|
|
988
|
+
expect(indentOf(general.parentElement!)).toBeNull();
|
|
989
|
+
expect(indentOf(advanced)).toBe(
|
|
990
|
+
'calc(var(--spacing-2) + 1 * var(--spacing-6))',
|
|
991
|
+
);
|
|
992
|
+
});
|
|
993
|
+
|
|
831
994
|
it('passes data-testid', () => {
|
|
832
995
|
render(<SideNavItem label="Dashboard" data-testid="nav-item" />);
|
|
833
996
|
expect(screen.getByTestId('nav-item')).toBeInTheDocument();
|
|
@@ -2457,6 +2620,22 @@ function declaredOn(el: Element, prop: string): string[] {
|
|
|
2457
2620
|
.filter(value => value != null);
|
|
2458
2621
|
}
|
|
2459
2622
|
|
|
2623
|
+
/**
|
|
2624
|
+
* The nesting indent a row carries, or null for none. The indent is a
|
|
2625
|
+
* runtime StyleX value, so it rides the row's inline style as a custom
|
|
2626
|
+
* property; read the value rather than the generated property name.
|
|
2627
|
+
*/
|
|
2628
|
+
function indentOf(el: HTMLElement): string | null {
|
|
2629
|
+
const {style} = el;
|
|
2630
|
+
for (let i = 0; i < style.length; i++) {
|
|
2631
|
+
const value = style.getPropertyValue(style[i]).trim();
|
|
2632
|
+
if (value.includes('var(--spacing-6)')) {
|
|
2633
|
+
return value;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2460
2639
|
/**
|
|
2461
2640
|
* The selectors that actually paint the shared ring on `el`, read out of the
|
|
2462
2641
|
* live stylesheet with StyleX's `:not(#\#)` specificity padding stripped so
|
|
@@ -90,7 +90,7 @@ export const docs = {
|
|
|
90
90
|
{
|
|
91
91
|
name: 'children',
|
|
92
92
|
type: 'ReactNode',
|
|
93
|
-
description: 'Sub-items for nesting.',
|
|
93
|
+
description: 'Sub-items for nesting. Nested items span the full width of the nav list, like top-level items; their content is indented one step per nesting level.',
|
|
94
94
|
slotElements: [
|
|
95
95
|
{
|
|
96
96
|
__element: 'SideNavItem',
|
|
@@ -213,7 +213,7 @@ export const docsDense = {
|
|
|
213
213
|
'Passive right-side content only (badges, counts). Interactive controls go in actions.',
|
|
214
214
|
actions:
|
|
215
215
|
'Row-level secondary controls (icon buttons, menus) rendered as siblings of the primary element, after the expand/collapse toggle and before nested children in DOM/focus order. Inherit the row control size via SizeContext (explicit size wins). Hidden when rail collapsed. Passive content goes in endContent; interactive content goes here.',
|
|
216
|
-
children: 'Sub-items for nesting.',
|
|
216
|
+
children: 'Sub-items for nesting. Nested rows stay full width; content indents one step per level.',
|
|
217
217
|
collapsible: 'Enables collapse for items w/ children. true=uncontrolled, object=controlled mode.',
|
|
218
218
|
size: "Row size variant: 'sm' | 'md' | 'lg'.",
|
|
219
219
|
},
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* `TopNavMenu` use). Hover is a progressive enhancement over the popover's
|
|
18
18
|
* click behavior and is inert on coarse pointers.
|
|
19
19
|
*
|
|
20
|
+
* Nested rows span the full width of the nav list, like top-level rows, so
|
|
21
|
+
* their hover and selected fill lines up with the rows above them. The
|
|
22
|
+
* nesting indent sits inside the row as extra inline-start padding, one step
|
|
23
|
+
* per level, tracked through a private depth context.
|
|
24
|
+
*
|
|
20
25
|
* SYNC: When modified, update these files to stay in sync:
|
|
21
26
|
* - /packages/core/src/SideNav/SideNav.doc.mjs
|
|
22
27
|
* - /packages/core/src/SideNav/SideNav.test.tsx
|
|
@@ -26,6 +31,8 @@
|
|
|
26
31
|
*/
|
|
27
32
|
|
|
28
33
|
import {
|
|
34
|
+
createContext,
|
|
35
|
+
use,
|
|
29
36
|
useCallback,
|
|
30
37
|
useId,
|
|
31
38
|
useMemo,
|
|
@@ -108,26 +115,45 @@ const styles = stylex.create({
|
|
|
108
115
|
gap: spacingVars['--spacing-1'],
|
|
109
116
|
pointerEvents: 'auto',
|
|
110
117
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
// The group clips its rows only for the 0fr collapse animation. Nested rows
|
|
119
|
+
// sit flush with the group, so a permanent clip would cut off their focus
|
|
120
|
+
// ring, which paints outside the row. The expanded group therefore stops
|
|
121
|
+
// clipping once it has finished opening: `overflow` is discrete, so it rides
|
|
122
|
+
// the same transition with `allow-discrete` and a delay equal to the open
|
|
123
|
+
// duration, holding `hidden` until the rows' height has settled. Collapsing
|
|
124
|
+
// flips back to `hidden` at once (no delay on the collapsed state), so
|
|
125
|
+
// nothing spills while the group closes.
|
|
126
|
+
//
|
|
127
|
+
// Reduced fallback: a browser without `transition-behavior: allow-discrete`
|
|
128
|
+
// (the tier below current Baseline) cannot delay the switch, so the group
|
|
129
|
+
// stops clipping as soon as it starts opening and the opening rows briefly
|
|
130
|
+
// overlap the row below. The group still opens and the ring still shows.
|
|
114
131
|
childrenCollapsible: {
|
|
115
132
|
display: 'grid',
|
|
116
133
|
gridTemplateRows: '1fr',
|
|
117
|
-
|
|
134
|
+
overflow: 'visible',
|
|
135
|
+
transitionProperty: 'grid-template-rows, overflow',
|
|
136
|
+
transitionBehavior: 'allow-discrete',
|
|
118
137
|
transitionDuration: {
|
|
119
|
-
default: durationVars['--duration-medium'],
|
|
120
|
-
'@media (prefers-reduced-motion: reduce)': '0s',
|
|
138
|
+
default: `${durationVars['--duration-medium']}, 0s`,
|
|
139
|
+
'@media (prefers-reduced-motion: reduce)': '0s, 0s',
|
|
140
|
+
},
|
|
141
|
+
transitionDelay: {
|
|
142
|
+
default: `0s, ${durationVars['--duration-medium']}`,
|
|
143
|
+
'@media (prefers-reduced-motion: reduce)': '0s, 0s',
|
|
121
144
|
},
|
|
122
145
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
123
146
|
},
|
|
124
147
|
childrenCollapsed: {
|
|
125
148
|
gridTemplateRows: '0fr',
|
|
149
|
+
overflow: 'hidden',
|
|
150
|
+
transitionDelay: '0s, 0s',
|
|
126
151
|
},
|
|
152
|
+
// No inline padding: nested rows fill the full width of the nav list, and
|
|
153
|
+
// each row carries its own indent (`indentStyles` below). `minHeight: 0`
|
|
154
|
+
// lets the 0fr track shrink this item to nothing; the group clips it.
|
|
127
155
|
childrenInner: {
|
|
128
|
-
overflow: 'hidden',
|
|
129
156
|
minHeight: 0,
|
|
130
|
-
paddingInlineStart: spacingVars['--spacing-6'],
|
|
131
157
|
},
|
|
132
158
|
expandChevron: {
|
|
133
159
|
display: 'inline-flex',
|
|
@@ -232,6 +258,35 @@ const styles = stylex.create({
|
|
|
232
258
|
},
|
|
233
259
|
});
|
|
234
260
|
|
|
261
|
+
// Nesting indent, drawn inside the row: one `--spacing-6` step per nesting
|
|
262
|
+
// level. The row itself stays full width, so its hover and selected fill
|
|
263
|
+
// matches a top-level row's.
|
|
264
|
+
// - `sm` / `md` — the ordinary row, where the row element is the link or
|
|
265
|
+
// button: its own inline-start padding plus the steps.
|
|
266
|
+
// SYNC: the base padding matches navItemStyles.sm / .md / .lg.
|
|
267
|
+
// - `primary` — the row-wrapper path: the wrapper keeps its base padding and
|
|
268
|
+
// the steps go on the primary link or button, so the whole indent is part
|
|
269
|
+
// of the click target rather than dead space that still shows feedback.
|
|
270
|
+
const indentStyles = stylex.create({
|
|
271
|
+
sm: (depth: number) => ({
|
|
272
|
+
paddingInlineStart: `calc(${spacingVars['--spacing-1']} + ${depth} * ${spacingVars['--spacing-6']})`,
|
|
273
|
+
}),
|
|
274
|
+
md: (depth: number) => ({
|
|
275
|
+
paddingInlineStart: `calc(${spacingVars['--spacing-2']} + ${depth} * ${spacingVars['--spacing-6']})`,
|
|
276
|
+
}),
|
|
277
|
+
primary: (depth: number) => ({
|
|
278
|
+
paddingInlineStart: `calc(${depth} * ${spacingVars['--spacing-6']})`,
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* How many SideNavItems deep the current item sits: 0 at the top level,
|
|
284
|
+
* 1 inside one parent, and so on. A collapsed rail's flyout resets it to 0,
|
|
285
|
+
* since the flyout lists a parent's children as its own top level.
|
|
286
|
+
*/
|
|
287
|
+
const SideNavItemDepthContext = createContext(0);
|
|
288
|
+
SideNavItemDepthContext.displayName = 'SideNavItemDepthContext';
|
|
289
|
+
|
|
235
290
|
/**
|
|
236
291
|
* Cascaded to the `actions` slot through `SizeContext` so a consumer's row
|
|
237
292
|
* controls come out the same height as the built-in expand/collapse toggle,
|
|
@@ -363,7 +418,9 @@ export interface SideNavItemProps extends BaseProps<HTMLElement> {
|
|
|
363
418
|
*/
|
|
364
419
|
actions?: ReactNode;
|
|
365
420
|
/**
|
|
366
|
-
* Sub-items for nesting.
|
|
421
|
+
* Sub-items for nesting. Nested items span the full width of the nav
|
|
422
|
+
* list, like top-level items; their content is indented one step per
|
|
423
|
+
* nesting level.
|
|
367
424
|
*/
|
|
368
425
|
children?: ReactNode;
|
|
369
426
|
/**
|
|
@@ -455,6 +512,7 @@ export function SideNavItem({
|
|
|
455
512
|
const id = useId();
|
|
456
513
|
const hasChildren = !!children;
|
|
457
514
|
const itemRef = useRef<HTMLDivElement>(null);
|
|
515
|
+
const depth = use(SideNavItemDepthContext);
|
|
458
516
|
|
|
459
517
|
// Popover for collapsed items with children
|
|
460
518
|
const popover = usePopover({
|
|
@@ -608,7 +666,9 @@ export function SideNavItem({
|
|
|
608
666
|
onClick={() => popover.hide()}>
|
|
609
667
|
<div {...stylex.props(styles.popoverHeader)}>{label}</div>
|
|
610
668
|
<SideNavCollapseContext value={EXPANDED_COLLAPSE_STATE}>
|
|
611
|
-
{
|
|
669
|
+
<SideNavItemDepthContext value={0}>
|
|
670
|
+
{children}
|
|
671
|
+
</SideNavItemDepthContext>
|
|
612
672
|
</SideNavCollapseContext>
|
|
613
673
|
</div>,
|
|
614
674
|
{placement: 'end', alignment: 'start', xstyle: styles.popoverGap},
|
|
@@ -686,6 +746,14 @@ export function SideNavItem({
|
|
|
686
746
|
isSelected && navItemStyles.selected,
|
|
687
747
|
isDisabled && navItemStyles.disabled,
|
|
688
748
|
] as const;
|
|
749
|
+
const rowIndent =
|
|
750
|
+
depth > 0 && indentStyles[size === 'sm' ? 'sm' : 'md'](depth);
|
|
751
|
+
const primaryIndent = depth > 0 && indentStyles.primary(depth);
|
|
752
|
+
|
|
753
|
+
// The indent is a runtime value, so it reaches the element as an inline
|
|
754
|
+
// style. These props are spread after `rest`, so fold the caller's `style`
|
|
755
|
+
// back in (caller wins) rather than letting the indent replace it.
|
|
756
|
+
const callerStyle = {style: rest.style};
|
|
689
757
|
|
|
690
758
|
// Three shapes of the same row appearance:
|
|
691
759
|
// - `rowProps` — split-action path (toggle, no actions): presentational
|
|
@@ -702,7 +770,10 @@ export function SideNavItem({
|
|
|
702
770
|
const focusableRowProps = {
|
|
703
771
|
...mergeProps(
|
|
704
772
|
itemThemeProps,
|
|
705
|
-
|
|
773
|
+
mergeProps(
|
|
774
|
+
focusOutlineProps.focusVisible(...itemStyleArgs, rowIndent),
|
|
775
|
+
callerStyle,
|
|
776
|
+
),
|
|
706
777
|
),
|
|
707
778
|
...pressable,
|
|
708
779
|
};
|
|
@@ -748,11 +819,17 @@ export function SideNavItem({
|
|
|
748
819
|
onClick={handleClick}
|
|
749
820
|
{...rest}
|
|
750
821
|
{...rowPrimaryAriaProps}
|
|
751
|
-
{...(
|
|
752
|
-
|
|
753
|
-
//
|
|
754
|
-
|
|
755
|
-
|
|
822
|
+
{...mergeProps(
|
|
823
|
+
hasActions
|
|
824
|
+
? // The wrapper rings for this element; suppressing here is what
|
|
825
|
+
// keeps the UA's own ring from painting inside that one.
|
|
826
|
+
focusOutlineProps.suppressed(styles.splitAction, primaryIndent)
|
|
827
|
+
: focusOutlineProps.focusVisible(
|
|
828
|
+
styles.splitAction,
|
|
829
|
+
primaryIndent,
|
|
830
|
+
),
|
|
831
|
+
callerStyle,
|
|
832
|
+
)}>
|
|
756
833
|
{itemContent}
|
|
757
834
|
</NavItemElement>
|
|
758
835
|
{hasIndependentToggle && (
|
|
@@ -831,7 +908,9 @@ export function SideNavItem({
|
|
|
831
908
|
<span id={`${id}-label`} hidden>
|
|
832
909
|
{label}
|
|
833
910
|
</span>
|
|
834
|
-
{
|
|
911
|
+
<SideNavItemDepthContext value={depth + 1}>
|
|
912
|
+
{children}
|
|
913
|
+
</SideNavItemDepthContext>
|
|
835
914
|
</div>
|
|
836
915
|
</div>
|
|
837
916
|
)}
|
|
@@ -34,13 +34,20 @@ an additive target for the interactive control surface.
|
|
|
34
34
|
|
|
35
35
|
## Compatibility and migration
|
|
36
36
|
|
|
37
|
-
- Released default preserved: `
|
|
37
|
+
- Released default preserved: `no` — FR5 changes the released `onChangeEnd`
|
|
38
|
+
payload; every other released default is unchanged
|
|
38
39
|
- Compatibility class: additive target and state reflection; default styling,
|
|
39
40
|
DOM semantics, and public props remain unchanged, and the one keyboard change
|
|
40
41
|
AR1 names — the horizontal arrows follow the track's direction under RTL —
|
|
41
42
|
ships as a behavior fix
|
|
43
|
+
- `onChangeEnd` payload (FR5): a new contract decision (`spec:AST-017/FR33`),
|
|
44
|
+
not a correction. Released victim: an owner that moves `value` only from
|
|
45
|
+
`onChangeEnd`, with no `onChange`, moves by keyboard today and does not under
|
|
46
|
+
FR5, so the change is breaking (`spec:AST-017/FR1`) and ships as
|
|
47
|
+
`[breaking]` in a declared minor (`spec:AST-017/FR31`).
|
|
42
48
|
- Controlled/uncontrolled behavior: unchanged; Slider remains controlled
|
|
43
|
-
- Migration decision:
|
|
49
|
+
- Migration decision: an owner that moves `value` only from `onChangeEnd`
|
|
50
|
+
moves it from `onChange` instead (FR5)
|
|
44
51
|
|
|
45
52
|
Consumer migration instructions belong in consumer docs and release notes.
|
|
46
53
|
|
|
@@ -64,18 +71,20 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
64
71
|
|
|
65
72
|
## Public concepts
|
|
66
73
|
|
|
67
|
-
The record adds one public theming target
|
|
68
|
-
|
|
74
|
+
The record adds one public theming target, names the keyboard model (AR1), and
|
|
75
|
+
states which value `onChangeEnd` carries (FR5) without adding or changing a
|
|
76
|
+
component prop or value domain. Consumer props,
|
|
69
77
|
modes, states, and usage remain documented in `Slider.doc.mjs`.
|
|
70
78
|
|
|
71
79
|
## Behavioral and layout contract
|
|
72
80
|
|
|
73
|
-
| ID | Candidate invariant
|
|
74
|
-
| --- |
|
|
75
|
-
| FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track.
|
|
76
|
-
| FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively.
|
|
77
|
-
| FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target.
|
|
78
|
-
| FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip.
|
|
81
|
+
| ID | Candidate invariant | Basis | Review state |
|
|
82
|
+
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
|
|
83
|
+
| FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
|
|
84
|
+
| FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively. | Current source, public docs, and DEC-1 | Approved additive target contract |
|
|
85
|
+
| FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
|
|
86
|
+
| FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
|
|
87
|
+
| FR5 | `onChangeEnd` fires once when an interaction ends — a pointer drag or track press is released or cancelled, or a keyboard step (arrow, Page, Home, End) completes — and carries the value the owner renders after its synchronous response to that interaction's `onChange` calls, including any clamp, snap, or rejection; it never carries a value Slider emitted that the owner did not render. A range reports both values the same way. | Maintainer decision (DEC-3) | Approved |
|
|
79
88
|
|
|
80
89
|
### Observed current target asymmetry
|
|
81
90
|
|
|
@@ -106,6 +115,8 @@ either component's remaining target shape.
|
|
|
106
115
|
|
|
107
116
|
- ORD1: an arrow or Page key result snaps to the nearest step, then clamps to
|
|
108
117
|
the bounds; Home and End go to `min` and `max` (AR1).
|
|
118
|
+
- ORD2: within one interaction, Slider calls `onChange`, the owner responds and
|
|
119
|
+
renders, then `onChangeEnd` reports the rendered value (FR5).
|
|
109
120
|
- The anatomy decision (DEC-1) introduces no value, snapping, geometry,
|
|
110
121
|
interaction, or styling precedence rule of its own.
|
|
111
122
|
|
|
@@ -214,14 +225,15 @@ separately listed value tooltip retains Tooltip's target.
|
|
|
214
225
|
|
|
215
226
|
## Verification map
|
|
216
227
|
|
|
217
|
-
| Contract | Verification | Representative states
|
|
218
|
-
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
219
|
-
| FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks
|
|
220
|
-
| AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value
|
|
221
|
-
| FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb
|
|
222
|
-
| FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value
|
|
223
|
-
| FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason
|
|
224
|
-
|
|
|
228
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
229
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
|
230
|
+
| FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
|
|
231
|
+
| AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
|
|
232
|
+
| FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
|
|
233
|
+
| FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
|
|
234
|
+
| FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
|
|
235
|
+
| FR5 | Pending: `Slider.test.tsx` change-end cases driven by an owner that renders, clamps, or rejects the emitted value (not yet in the suite) | Single and range; pointer release before and after the owner re-renders; each keyboard step | `onChangeEnd` reports a value the owner did not render, fires more than once per interaction, or skips a completed keyboard step | `audit:Slider/accessibility` |
|
|
236
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
|
|
225
237
|
|
|
226
238
|
The focused Slider suite asserts the `slider-control` target's orientation and
|
|
227
239
|
disabled state. Source/metadata guards cover all four target declarations. The
|
|
@@ -250,6 +262,15 @@ A thumb already plays the slider role as one composite control, so it carries th
|
|
|
250
262
|
|
|
251
263
|
Rejected: keeping ArrowRight as "increase" under RTL; a Tab stop or arrow model per range thumb that differs from the single thumb.
|
|
252
264
|
|
|
265
|
+
### DEC-3 — `onChangeEnd` reports the value the owner renders
|
|
266
|
+
|
|
267
|
+
**Reference:** `component:Slider/DEC-3`
|
|
268
|
+
**Decider:** `cixzhang`, `2026-10-16`
|
|
269
|
+
|
|
270
|
+
`onChangeEnd` marks the end of an interaction, so it reports what the slider shows when the interaction ends: the controlled value after the owner has answered `onChange`. An owner that clamps, snaps, or rejects a value therefore receives the value it kept, and the commit agrees with the thumb (FR5).
|
|
271
|
+
|
|
272
|
+
Rejected: reporting the last value Slider emitted through `onChange`.
|
|
273
|
+
|
|
253
274
|
## Open questions
|
|
254
275
|
|
|
255
276
|
- **OQ1 — After a joint ProgressBar and Slider family audit, should any shared
|
|
@@ -6,7 +6,7 @@ export const docs = {
|
|
|
6
6
|
name: 'Spinner',
|
|
7
7
|
displayName: 'Spinner',
|
|
8
8
|
category: 'Feedback & Status',
|
|
9
|
-
keywords: ["spinner","loader","loading","circular","progress","spin","activity","busy","indeterminate"],
|
|
9
|
+
keywords: ["spinner","loader","loading","circular","progress","spin","activity","busy","indeterminate","circularprogress"],
|
|
10
10
|
props: [
|
|
11
11
|
{
|
|
12
12
|
name: 'size',
|
|
@@ -834,6 +834,55 @@ describe('Switch', () => {
|
|
|
834
834
|
expect(root).toHaveAttribute('id', 'switch-1');
|
|
835
835
|
expect(root).toHaveAttribute('aria-label', 'Toggle notifications');
|
|
836
836
|
});
|
|
837
|
+
|
|
838
|
+
it('composes a caller aria-describedby onto the switch control', () => {
|
|
839
|
+
const {container} = render(
|
|
840
|
+
<>
|
|
841
|
+
<p id="external-hint">Applies to every repository</p>
|
|
842
|
+
<Switch
|
|
843
|
+
label="Notifications"
|
|
844
|
+
description="Sends a daily digest"
|
|
845
|
+
value={false}
|
|
846
|
+
onChange={() => {}}
|
|
847
|
+
data-testid="my-switch"
|
|
848
|
+
aria-describedby="external-hint"
|
|
849
|
+
/>
|
|
850
|
+
</>,
|
|
851
|
+
);
|
|
852
|
+
const control = screen.getByRole('switch');
|
|
853
|
+
const description = screen.getByText('Sends a daily digest');
|
|
854
|
+
expect(control.getAttribute('aria-describedby')?.split(' ')).toEqual([
|
|
855
|
+
'external-hint',
|
|
856
|
+
description.id,
|
|
857
|
+
]);
|
|
858
|
+
expect(control).toHaveAccessibleDescription(
|
|
859
|
+
'Applies to every repository Sends a daily digest',
|
|
860
|
+
);
|
|
861
|
+
// The id reference lives on the control only, not on the field wrapper.
|
|
862
|
+
const root = container.querySelector('[data-testid="my-switch"]');
|
|
863
|
+
expect(root).not.toHaveAttribute('aria-describedby');
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('names the switch control from a caller aria-labelledby', () => {
|
|
867
|
+
const {container} = render(
|
|
868
|
+
<>
|
|
869
|
+
<span id="row-title">Release notes</span>
|
|
870
|
+
<Switch
|
|
871
|
+
label="Pin Release notes"
|
|
872
|
+
isLabelHidden
|
|
873
|
+
value={false}
|
|
874
|
+
onChange={() => {}}
|
|
875
|
+
data-testid="my-switch"
|
|
876
|
+
aria-labelledby="row-title"
|
|
877
|
+
/>
|
|
878
|
+
</>,
|
|
879
|
+
);
|
|
880
|
+
const control = screen.getByRole('switch', {name: 'Release notes'});
|
|
881
|
+
expect(control).toHaveAttribute('aria-labelledby', 'row-title');
|
|
882
|
+
// The id reference lives on the control only, not on the field wrapper.
|
|
883
|
+
const root = container.querySelector('[data-testid="my-switch"]');
|
|
884
|
+
expect(root).not.toHaveAttribute('aria-labelledby');
|
|
885
|
+
});
|
|
837
886
|
});
|
|
838
887
|
|
|
839
888
|
describe('pressed state', () => {
|
package/src/Switch/Switch.tsx
CHANGED
|
@@ -566,6 +566,8 @@ export function Switch({
|
|
|
566
566
|
className,
|
|
567
567
|
style,
|
|
568
568
|
ref,
|
|
569
|
+
'aria-describedby': ariaDescribedByProp,
|
|
570
|
+
'aria-labelledby': ariaLabelledByProp,
|
|
569
571
|
...rest
|
|
570
572
|
}: SwitchProps) {
|
|
571
573
|
const t = useTranslator();
|
|
@@ -610,7 +612,12 @@ export function Switch({
|
|
|
610
612
|
// Only include descriptionID when the element actually renders.
|
|
611
613
|
// FieldLabel renders the description (with descriptionID) even when the
|
|
612
614
|
// label is visually hidden — it's sr-only, so keep it linked.
|
|
615
|
+
// A consumer's own `aria-describedby` describes the control, so it comes
|
|
616
|
+
// first here rather than landing on the field wrapper via `...rest`.
|
|
613
617
|
const describedByParts: string[] = [];
|
|
618
|
+
if (ariaDescribedByProp) {
|
|
619
|
+
describedByParts.push(ariaDescribedByProp);
|
|
620
|
+
}
|
|
614
621
|
if (description) {
|
|
615
622
|
describedByParts.push(descriptionID);
|
|
616
623
|
}
|
|
@@ -658,6 +665,10 @@ export function Switch({
|
|
|
658
665
|
}}
|
|
659
666
|
onFocus={onFocus}
|
|
660
667
|
onBlur={onBlur}
|
|
668
|
+
// A caller's `aria-labelledby` names the control (a visible title
|
|
669
|
+
// elsewhere in the row, for example), so it reaches the input rather
|
|
670
|
+
// than the field wrapper via `...rest`.
|
|
671
|
+
aria-labelledby={ariaLabelledByProp}
|
|
661
672
|
aria-describedby={ariaDescribedBy}
|
|
662
673
|
aria-invalid={status?.type === 'error' ? true : undefined}
|
|
663
674
|
aria-busy={isBusy || undefined}
|