@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
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
|
+
import type {ReactNode} from 'react';
|
|
14
15
|
import userEvent from '@testing-library/user-event';
|
|
15
16
|
import * as stylex from '@stylexjs/stylex';
|
|
16
17
|
import {TreeList} from './TreeList';
|
|
@@ -609,12 +610,12 @@ describe('TreeList', () => {
|
|
|
609
610
|
});
|
|
610
611
|
|
|
611
612
|
it("variant='noGuides' preserves per-level indentation on the rows", () => {
|
|
612
|
-
// Indentation lives
|
|
613
|
-
// element), so it must survive when the connectors are
|
|
614
|
-
// per-row distance is published as the `--_tree-indent`
|
|
615
|
-
// (not an inline longhand — see #4308), so the theme
|
|
616
|
-
// the `
|
|
617
|
-
// than a shallower one.
|
|
613
|
+
// Indentation lives inside the row's padding-inline-start (not the
|
|
614
|
+
// guide element), so it must survive when the connectors are
|
|
615
|
+
// suppressed. The per-row distance is published as the `--_tree-indent`
|
|
616
|
+
// custom property (not an inline longhand — see #4308), so the theme
|
|
617
|
+
// layer can override the `padding-inline-start` declaration. A deeper
|
|
618
|
+
// row is indented more than a shallower one.
|
|
618
619
|
const {container} = render(
|
|
619
620
|
<TreeList items={deepItems} variant="noGuides" />,
|
|
620
621
|
);
|
|
@@ -720,6 +721,326 @@ describe('TreeList', () => {
|
|
|
720
721
|
expect(indentOf('Mid')).toContain('var(--tree-list-indent)');
|
|
721
722
|
expect(indentOf('Leaf')).toContain('var(--tree-list-indent)');
|
|
722
723
|
});
|
|
724
|
+
|
|
725
|
+
it('indents inside the row so nested rows keep the full list width', () => {
|
|
726
|
+
// The row box keeps its own inline padding and stays full width; the
|
|
727
|
+
// indent is padding on a child inside it. A nested row's hover and
|
|
728
|
+
// selected fill therefore spans the list like a root row's, while its
|
|
729
|
+
// content lands where a margin indent would have put it.
|
|
730
|
+
render(<TreeList items={deepItems} />);
|
|
731
|
+
const rules = collectCssText();
|
|
732
|
+
expect(rules).toMatch(
|
|
733
|
+
/padding-inline-start:\s*var\(--_tree-indent,\s*0px\)/,
|
|
734
|
+
);
|
|
735
|
+
expect(rules).not.toMatch(/margin-inline-start:\s*var\(--_tree-indent/);
|
|
736
|
+
|
|
737
|
+
const row = screen
|
|
738
|
+
.getByText('Leaf')
|
|
739
|
+
.closest<HTMLElement>('.astryx-tree-list-item')!;
|
|
740
|
+
const indented = row.firstElementChild as HTMLElement;
|
|
741
|
+
expect(indented.className).toContain('indentedContent');
|
|
742
|
+
expect(row.className).not.toContain('indentedContent');
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it("keeps the indent when a caller sets the row's padding", () => {
|
|
746
|
+
// Caller padding lands on the row box; the indent is on a separate
|
|
747
|
+
// element inside it, so the two compose instead of one replacing the
|
|
748
|
+
// other.
|
|
749
|
+
const items = [
|
|
750
|
+
{
|
|
751
|
+
id: 'root',
|
|
752
|
+
label: 'Root',
|
|
753
|
+
isExpanded: true,
|
|
754
|
+
children: [
|
|
755
|
+
{id: 'leaf', label: 'Leaf', style: {paddingInlineStart: '20px'}},
|
|
756
|
+
],
|
|
757
|
+
},
|
|
758
|
+
];
|
|
759
|
+
render(<TreeList items={items} />);
|
|
760
|
+
const row = screen
|
|
761
|
+
.getByText('Leaf')
|
|
762
|
+
.closest<HTMLElement>('.astryx-tree-list-item')!;
|
|
763
|
+
expect(row.style.paddingInlineStart).toBe('20px');
|
|
764
|
+
expect(row.getAttribute('style')).toContain('--_tree-indent');
|
|
765
|
+
expect((row.firstElementChild as HTMLElement).className).toContain(
|
|
766
|
+
'indentedContent',
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
describe('link rows', () => {
|
|
771
|
+
const linkItems = (endContent?: ReactNode): TreeListItemData[] => [
|
|
772
|
+
{
|
|
773
|
+
id: 'docs',
|
|
774
|
+
label: 'Docs',
|
|
775
|
+
isExpanded: true,
|
|
776
|
+
children: [
|
|
777
|
+
{id: 'report', label: 'Report', href: '/report', endContent},
|
|
778
|
+
],
|
|
779
|
+
},
|
|
780
|
+
];
|
|
781
|
+
const rowSurfaceOf = (link: HTMLElement) =>
|
|
782
|
+
link.closest<HTMLElement>('.astryx-tree-list-item')!
|
|
783
|
+
.firstElementChild as HTMLElement;
|
|
784
|
+
|
|
785
|
+
it('follows the link when the row is clicked outside its label', () => {
|
|
786
|
+
// The indent is part of the row now, so a click there must do what
|
|
787
|
+
// the row does. The click is delegated to the anchor through the
|
|
788
|
+
// shared enlarged-surface hook, modifier keys included.
|
|
789
|
+
render(<TreeList items={linkItems()} />);
|
|
790
|
+
const link = screen.getByRole('link', {name: 'Report'});
|
|
791
|
+
const clicks: MouseEvent[] = [];
|
|
792
|
+
link.addEventListener('click', e => {
|
|
793
|
+
clicks.push(e);
|
|
794
|
+
e.preventDefault();
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
fireEvent.click(rowSurfaceOf(link), {metaKey: true});
|
|
798
|
+
expect(clicks).toHaveLength(1);
|
|
799
|
+
expect(clicks[0].metaKey).toBe(true);
|
|
800
|
+
|
|
801
|
+
// The row box's own padding follows the link too.
|
|
802
|
+
fireEvent.click(rowSurfaceOf(link).parentElement!);
|
|
803
|
+
expect(clicks).toHaveLength(2);
|
|
804
|
+
|
|
805
|
+
// Clicking the label itself reaches the anchor once, not twice.
|
|
806
|
+
fireEvent.click(link);
|
|
807
|
+
expect(clicks).toHaveLength(3);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('reports one click to ancestors per press', () => {
|
|
811
|
+
// The delegated click bubbles from the anchor; the original stops,
|
|
812
|
+
// so a listener above the tree sees exactly one click.
|
|
813
|
+
const onTreeClick = vi.fn();
|
|
814
|
+
render(<TreeList items={linkItems()} onClick={onTreeClick} />);
|
|
815
|
+
const link = screen.getByRole('link', {name: 'Report'});
|
|
816
|
+
link.addEventListener('click', e => e.preventDefault());
|
|
817
|
+
|
|
818
|
+
fireEvent.click(rowSurfaceOf(link));
|
|
819
|
+
expect(onTreeClick).toHaveBeenCalledTimes(1);
|
|
820
|
+
expect(onTreeClick.mock.calls[0][0].target).toBe(link);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('leaves a role-based control in the row to itself', () => {
|
|
824
|
+
const onSwitch = vi.fn();
|
|
825
|
+
render(
|
|
826
|
+
<TreeList
|
|
827
|
+
items={linkItems(
|
|
828
|
+
<span
|
|
829
|
+
role="switch"
|
|
830
|
+
aria-checked="false"
|
|
831
|
+
aria-label="Pin"
|
|
832
|
+
tabIndex={0}
|
|
833
|
+
onClick={onSwitch}>
|
|
834
|
+
Pin
|
|
835
|
+
</span>,
|
|
836
|
+
)}
|
|
837
|
+
/>,
|
|
838
|
+
);
|
|
839
|
+
const link = screen.getByRole('link', {name: 'Report'});
|
|
840
|
+
const onLinkClick = vi.fn((e: Event) => e.preventDefault());
|
|
841
|
+
link.addEventListener('click', onLinkClick);
|
|
842
|
+
|
|
843
|
+
fireEvent.click(screen.getByRole('switch', {name: 'Pin'}));
|
|
844
|
+
expect(onSwitch).toHaveBeenCalledTimes(1);
|
|
845
|
+
expect(onLinkClick).not.toHaveBeenCalled();
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('opens the link in a new tab on a middle-click in the row', () => {
|
|
849
|
+
const open = vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
850
|
+
render(<TreeList items={linkItems()} />);
|
|
851
|
+
const link = screen.getByRole('link', {name: 'Report'});
|
|
852
|
+
|
|
853
|
+
fireEvent.mouseUp(rowSurfaceOf(link), {button: 1});
|
|
854
|
+
expect(open).toHaveBeenCalledWith('/report', '_blank', 'noopener');
|
|
855
|
+
|
|
856
|
+
// A middle-click on the label is the browser's own.
|
|
857
|
+
open.mockClear();
|
|
858
|
+
fireEvent.mouseUp(link, {button: 1});
|
|
859
|
+
expect(open).not.toHaveBeenCalled();
|
|
860
|
+
open.mockRestore();
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('does not follow a disabled link row', () => {
|
|
865
|
+
const items = [
|
|
866
|
+
{
|
|
867
|
+
id: 'docs',
|
|
868
|
+
label: 'Docs',
|
|
869
|
+
isExpanded: true,
|
|
870
|
+
children: [
|
|
871
|
+
{id: 'report', label: 'Report', href: '/report', isDisabled: true},
|
|
872
|
+
],
|
|
873
|
+
},
|
|
874
|
+
];
|
|
875
|
+
render(<TreeList items={items} />);
|
|
876
|
+
const link = screen.getByRole('link', {name: 'Report', hidden: true});
|
|
877
|
+
const onLinkClick = vi.fn((e: Event) => e.preventDefault());
|
|
878
|
+
link.addEventListener('click', onLinkClick);
|
|
879
|
+
const row = link.closest<HTMLElement>('.astryx-tree-list-item')!;
|
|
880
|
+
fireEvent.click(row.firstElementChild!);
|
|
881
|
+
expect(onLinkClick).not.toHaveBeenCalled();
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
describe('row clicks outside the label (FR7)', () => {
|
|
885
|
+
// Each row sits under an expanded root, so it has an indent; the click
|
|
886
|
+
// lands on the row surface (indent, reserved chevron column, padding),
|
|
887
|
+
// not on the label.
|
|
888
|
+
const underRoot = (children: TreeListItemData[]): TreeListItemData[] => [
|
|
889
|
+
{id: 'root', label: 'Root', isExpanded: true, children},
|
|
890
|
+
];
|
|
891
|
+
// The row box itself (its own padding) and the indented content inside
|
|
892
|
+
// it: FR7 covers clicks on both.
|
|
893
|
+
const rowOf = (label: string) =>
|
|
894
|
+
screen.getByText(label).closest<HTMLElement>('.astryx-tree-list-item')!;
|
|
895
|
+
const rowSurfaceOf = (label: string) =>
|
|
896
|
+
rowOf(label).firstElementChild as HTMLElement;
|
|
897
|
+
const folder: TreeListItemData = {
|
|
898
|
+
id: 'folder',
|
|
899
|
+
label: 'Folder',
|
|
900
|
+
children: [{id: 'file', label: 'File'}],
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
it("runs an action row's onClick once", () => {
|
|
904
|
+
const onClick = vi.fn();
|
|
905
|
+
render(
|
|
906
|
+
<TreeList items={underRoot([{id: 'run', label: 'Run', onClick}])} />,
|
|
907
|
+
);
|
|
908
|
+
fireEvent.click(rowSurfaceOf('Run'));
|
|
909
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
910
|
+
fireEvent.click(rowOf('Run'));
|
|
911
|
+
expect(onClick).toHaveBeenCalledTimes(2);
|
|
912
|
+
|
|
913
|
+
// The label's own button still runs it once per press, not twice.
|
|
914
|
+
fireEvent.click(screen.getByRole('button', {name: 'Run'}));
|
|
915
|
+
expect(onClick).toHaveBeenCalledTimes(3);
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it('toggles a parent row', () => {
|
|
919
|
+
render(<TreeList items={underRoot([folder])} />);
|
|
920
|
+
expect(screen.queryByText('File')).not.toBeInTheDocument();
|
|
921
|
+
fireEvent.click(rowSurfaceOf('Folder'));
|
|
922
|
+
expect(screen.getByText('File')).toBeInTheDocument();
|
|
923
|
+
fireEvent.click(rowOf('Folder'));
|
|
924
|
+
expect(screen.queryByText('File')).not.toBeInTheDocument();
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
it('does nothing on a disabled action or parent row', () => {
|
|
928
|
+
const onClick = vi.fn();
|
|
929
|
+
render(
|
|
930
|
+
<TreeList
|
|
931
|
+
items={underRoot([
|
|
932
|
+
{id: 'run', label: 'Run', onClick, isDisabled: true},
|
|
933
|
+
{...folder, isDisabled: true},
|
|
934
|
+
])}
|
|
935
|
+
/>,
|
|
936
|
+
);
|
|
937
|
+
fireEvent.click(rowSurfaceOf('Run'));
|
|
938
|
+
fireEvent.click(rowOf('Run'));
|
|
939
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
940
|
+
fireEvent.click(rowSurfaceOf('Folder'));
|
|
941
|
+
fireEvent.click(rowOf('Folder'));
|
|
942
|
+
expect(screen.queryByText('File')).not.toBeInTheDocument();
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it('leaves a role-based control in start or end content to itself', () => {
|
|
946
|
+
// Native and role-based controls alike keep their own activation,
|
|
947
|
+
// the same rule link rows get from the shared enlarged-surface hook.
|
|
948
|
+
const onClick = vi.fn();
|
|
949
|
+
const onSwitch = vi.fn();
|
|
950
|
+
const pin = (name: string) => (
|
|
951
|
+
<span
|
|
952
|
+
role="switch"
|
|
953
|
+
aria-checked="false"
|
|
954
|
+
aria-label={name}
|
|
955
|
+
tabIndex={0}
|
|
956
|
+
onClick={onSwitch}>
|
|
957
|
+
{name}
|
|
958
|
+
</span>
|
|
959
|
+
);
|
|
960
|
+
render(
|
|
961
|
+
<TreeList
|
|
962
|
+
items={underRoot([
|
|
963
|
+
{
|
|
964
|
+
id: 'run',
|
|
965
|
+
label: 'Run',
|
|
966
|
+
onClick,
|
|
967
|
+
startContent: pin('Pin run start'),
|
|
968
|
+
endContent: pin('Pin run end'),
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
...folder,
|
|
972
|
+
startContent: pin('Pin folder start'),
|
|
973
|
+
endContent: pin('Pin folder end'),
|
|
974
|
+
},
|
|
975
|
+
])}
|
|
976
|
+
/>,
|
|
977
|
+
);
|
|
978
|
+
for (const name of [
|
|
979
|
+
'Pin run start',
|
|
980
|
+
'Pin run end',
|
|
981
|
+
'Pin folder start',
|
|
982
|
+
'Pin folder end',
|
|
983
|
+
]) {
|
|
984
|
+
fireEvent.click(screen.getByRole('switch', {name}));
|
|
985
|
+
}
|
|
986
|
+
expect(onSwitch).toHaveBeenCalledTimes(4);
|
|
987
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
988
|
+
expect(screen.queryByText('File')).not.toBeInTheDocument();
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('draws guides above full-width rows without taking pointer input', () => {
|
|
993
|
+
// Guide columns now sit inside deeper rows' boxes. They paint above the
|
|
994
|
+
// rows so a row fill never breaks the line, and let clicks fall through
|
|
995
|
+
// to the row underneath.
|
|
996
|
+
const {container} = render(<TreeList items={deepItems} />);
|
|
997
|
+
const guide = container.querySelector('.astryx-tree-list-guide');
|
|
998
|
+
expect(guide).not.toBeNull();
|
|
999
|
+
const column = guide!.parentElement!;
|
|
1000
|
+
expect(getComputedStyle(column).pointerEvents).toBe('none');
|
|
1001
|
+
expect(getComputedStyle(column).zIndex).toBe('1');
|
|
1002
|
+
// The tree contains that stacking so a guide cannot climb over
|
|
1003
|
+
// positioned content outside the list.
|
|
1004
|
+
const treeRoot = screen.getByRole('tree').parentElement!;
|
|
1005
|
+
expect(treeRoot.className).toContain('astryx-tree-list');
|
|
1006
|
+
expect(getComputedStyle(treeRoot).isolation).toBe('isolate');
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it('lifts only rows that draw a focus ring above the guides', () => {
|
|
1010
|
+
// A plain row (no onClick, href or children) draws no focus ring, so it
|
|
1011
|
+
// stays under the guides: a focused selected plain row must not hide
|
|
1012
|
+
// them with its fill (DD3). Rows that draw the ring take the lift.
|
|
1013
|
+
render(
|
|
1014
|
+
<TreeList
|
|
1015
|
+
items={[
|
|
1016
|
+
{
|
|
1017
|
+
id: 'root',
|
|
1018
|
+
label: 'Root',
|
|
1019
|
+
isExpanded: true,
|
|
1020
|
+
children: [
|
|
1021
|
+
{id: 'plain', label: 'Plain', isSelected: true},
|
|
1022
|
+
{id: 'run', label: 'Run', onClick: () => {}},
|
|
1023
|
+
],
|
|
1024
|
+
},
|
|
1025
|
+
]}
|
|
1026
|
+
/>,
|
|
1027
|
+
);
|
|
1028
|
+
const rowOf = (label: string) =>
|
|
1029
|
+
screen.getByText(label).closest<HTMLElement>('.astryx-tree-list-item')!;
|
|
1030
|
+
expect(rowOf('Plain').className).not.toContain('focusLift');
|
|
1031
|
+
expect(rowOf('Run').className).toContain('focusLift');
|
|
1032
|
+
expect(rowOf('Root').className).toContain('focusLift');
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
it('lifts the focused row above the guides so they never cut its ring', () => {
|
|
1036
|
+
// Each treeitem publishes its own focus as `--_tree-focus-z`, and the
|
|
1037
|
+
// row reads it as its z-index, so the focused row paints above the
|
|
1038
|
+
// guide columns (z-index 1). Nested treeitems redeclare the default.
|
|
1039
|
+
render(<TreeList items={deepItems} />);
|
|
1040
|
+
const rules = collectCssText();
|
|
1041
|
+
expect(rules).toMatch(/--_tree-focus-z:\s*2/);
|
|
1042
|
+
expect(rules).toMatch(/z-index:\s*var\(--_tree-focus-z,\s*auto\)/);
|
|
1043
|
+
});
|
|
723
1044
|
});
|
|
724
1045
|
|
|
725
1046
|
// ===========================================================================
|
|
@@ -83,9 +83,13 @@ export interface TreeListProps extends BaseProps<HTMLDivElement> {
|
|
|
83
83
|
const styles = stylex.create({
|
|
84
84
|
root: {
|
|
85
85
|
position: 'relative',
|
|
86
|
+
// Contain the guides' and focused rows' `z-index` (TreeListBranches,
|
|
87
|
+
// TreeListItem) inside the tree, so they never climb over a sticky header
|
|
88
|
+
// or other positioned content outside the list.
|
|
89
|
+
isolation: 'isolate',
|
|
86
90
|
// Per-level indentation step. Public, themeable lever: a theme can retune
|
|
87
91
|
// the tree's indent metric (e.g. to `var(--spacing-5)`) via `defineTheme`
|
|
88
|
-
// on the `tree-list` target, and both the row
|
|
92
|
+
// on the `tree-list` target, and both the row indents (TreeListItem) and
|
|
89
93
|
// the guide-line offsets (TreeListBranches) read it so they stay aligned.
|
|
90
94
|
'--tree-list-indent': spacingVars['--spacing-4'],
|
|
91
95
|
// Vertical gap between adjacent rows. Public, themeable lever (default
|
|
@@ -32,10 +32,16 @@ const BRANCH_MARGIN = `calc(${spacingVars['--spacing-2']} + ${spacingVars['--spa
|
|
|
32
32
|
const LEVEL_INDENT = 'var(--tree-list-indent)';
|
|
33
33
|
|
|
34
34
|
const styles = stylex.create({
|
|
35
|
+
// Rows span the full width of the list and carry their indent inside, so
|
|
36
|
+
// the guide columns sit inside the row boxes of deeper rows. Lift the guides
|
|
37
|
+
// above the rows so a hovered or selected row's fill never breaks the line,
|
|
38
|
+
// and let pointer input fall through to the row underneath.
|
|
35
39
|
container: {
|
|
36
40
|
height: '100%',
|
|
37
41
|
position: 'absolute',
|
|
38
42
|
width: spacingVars['--spacing-5'],
|
|
43
|
+
zIndex: 1,
|
|
44
|
+
pointerEvents: 'none',
|
|
39
45
|
},
|
|
40
46
|
verticalLine: {
|
|
41
47
|
borderRadius: 1,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - /packages/cli/assets/templates/blocks/components/TreeList/ (showcase blocks)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {useId, useMemo, type ReactNode} from 'react';
|
|
17
|
+
import {useId, useMemo, useRef, type ReactNode} from 'react';
|
|
18
18
|
import * as stylex from '@stylexjs/stylex';
|
|
19
19
|
import {
|
|
20
20
|
colorVars,
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
28
28
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
29
29
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
30
|
+
import {
|
|
31
|
+
hasInteractiveAncestor,
|
|
32
|
+
useClickableContainer,
|
|
33
|
+
} from '../hooks/useClickableContainer';
|
|
30
34
|
import {Icon} from '../Icon';
|
|
31
35
|
import {mergeProps} from '../utils';
|
|
32
36
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
@@ -46,6 +50,13 @@ const styles = stylex.create({
|
|
|
46
50
|
padding: 0,
|
|
47
51
|
position: 'relative',
|
|
48
52
|
width: '100%',
|
|
53
|
+
// Publish whether THIS treeitem has keyboard focus, for its row's
|
|
54
|
+
// `zIndex` (see `contentWrapper`). Every nested <li> redeclares the
|
|
55
|
+
// default, so a focused ancestor never lifts its descendants' rows.
|
|
56
|
+
'--_tree-focus-z': {
|
|
57
|
+
default: 'auto',
|
|
58
|
+
':focus-visible': '2',
|
|
59
|
+
},
|
|
49
60
|
// The treeitem row is the roving-tabindex focus owner; suppress the
|
|
50
61
|
// native focus ring in favor of the row's :focus-visible outline below.
|
|
51
62
|
outline: 'none',
|
|
@@ -82,6 +93,9 @@ const styles = stylex.create({
|
|
|
82
93
|
display: 'flex',
|
|
83
94
|
alignItems: 'center',
|
|
84
95
|
gap: spacingVars['--spacing-2'],
|
|
96
|
+
// The row's own inline padding only. The per-level indent lives on
|
|
97
|
+
// `indentedContent` inside it, so a theme or caller padding here composes
|
|
98
|
+
// with the indent instead of replacing it.
|
|
85
99
|
paddingInline: spacingVars['--spacing-2'],
|
|
86
100
|
// No `outline: 'none'` here: this element receives the shared focus ring,
|
|
87
101
|
// which already defaults to none, and the shorthand would erase it.
|
|
@@ -89,12 +103,38 @@ const styles = stylex.create({
|
|
|
89
103
|
position: 'relative',
|
|
90
104
|
boxSizing: 'border-box',
|
|
91
105
|
textAlign: 'start',
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
},
|
|
107
|
+
// Guide lines draw above the rows (TreeListBranches) so a row's fill does
|
|
108
|
+
// not break them. Lift the row whose focus ring is showing above the guides
|
|
109
|
+
// too, so no guide cuts through the ring: when focus is inside the row, or
|
|
110
|
+
// when its own treeitem publishes focus (`--_tree-focus-z` on `wrapper`).
|
|
111
|
+
// The ring is the row's outline, so it cannot be lifted on its own: a
|
|
112
|
+
// focused row that is also filled (selected or hovered) covers the guides at
|
|
113
|
+
// that row. That is the accepted trade-off (DD3). Applied only with
|
|
114
|
+
// `focusWithinOrPublished`, the rows that draw the ring: a plain row shows
|
|
115
|
+
// no ring, so its fill stays under the guides.
|
|
116
|
+
focusLift: {
|
|
117
|
+
zIndex: {
|
|
118
|
+
default: 'var(--_tree-focus-z, auto)',
|
|
119
|
+
':has(:focus-visible)': 2,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
// Per-level indent, drawn INSIDE the row box: the row (`contentWrapper`)
|
|
123
|
+
// stays full width, so a nested row's hover and selected fill spans the
|
|
124
|
+
// list like a root row's, while its content sits exactly where a margin
|
|
125
|
+
// indent would put it. A child of the row rather than the row's own padding,
|
|
126
|
+
// so a theme or caller that sets the row's padding keeps the indent. It
|
|
127
|
+
// inherits the row's flex alignment and gap, so a theme that retunes those
|
|
128
|
+
// on `tree-list-item` still lays the content out. The row publishes only the
|
|
129
|
+
// computed distance as `--_tree-indent`; the per-level step is the public,
|
|
130
|
+
// themeable `--tree-list-indent` lever (see TreeList `root`).
|
|
131
|
+
indentedContent: {
|
|
132
|
+
display: 'flex',
|
|
133
|
+
flex: 1,
|
|
134
|
+
minWidth: 0,
|
|
135
|
+
alignItems: 'inherit',
|
|
136
|
+
gap: 'inherit',
|
|
137
|
+
paddingInlineStart: 'var(--_tree-indent, 0px)',
|
|
98
138
|
},
|
|
99
139
|
interactive: {
|
|
100
140
|
cursor: {
|
|
@@ -369,6 +409,7 @@ export function TreeListItem({
|
|
|
369
409
|
const labelId = useId();
|
|
370
410
|
const descriptionId = useId();
|
|
371
411
|
const LinkComponent = useLinkComponent();
|
|
412
|
+
const linkRef = useRef<HTMLAnchorElement>(null);
|
|
372
413
|
const isInteractive = onClick != null || href != null;
|
|
373
414
|
|
|
374
415
|
const handleToggle = useMemo(
|
|
@@ -388,8 +429,10 @@ export function TreeListItem({
|
|
|
388
429
|
if (isDisabled) {
|
|
389
430
|
return;
|
|
390
431
|
}
|
|
391
|
-
|
|
392
|
-
|
|
432
|
+
// Controls inside the row, native or role-based, keep their own
|
|
433
|
+
// activation (FR7): the same rule link rows get from the shared
|
|
434
|
+
// enlarged-surface hook below.
|
|
435
|
+
if (hasInteractiveAncestor(e.target as Element, e.currentTarget)) {
|
|
393
436
|
return;
|
|
394
437
|
}
|
|
395
438
|
if (onClick != null) {
|
|
@@ -402,6 +445,34 @@ export function TreeListItem({
|
|
|
402
445
|
return undefined;
|
|
403
446
|
}, [onClick, hasChildren, onToggle, id, isDisabled]);
|
|
404
447
|
|
|
448
|
+
// A link row (an `href` and no other row action) is an enlarged surface for
|
|
449
|
+
// its link: the indent is part of the row, so a click anywhere in it must
|
|
450
|
+
// follow the link, as Enter already does. Built on the shared
|
|
451
|
+
// enlarged-surface hook (family:navigation-destinations), which skips
|
|
452
|
+
// clicks on nested controls, native or role-based, and text selections.
|
|
453
|
+
// - Clicks are delegated: the hook re-dispatches the click on the anchor
|
|
454
|
+
// with its modifier keys, so the link component and the browser handle
|
|
455
|
+
// same-tab, new-tab and new-window activation, and stops the original, so
|
|
456
|
+
// ancestors see one click per press.
|
|
457
|
+
// - Middle-click has no synthetic equivalent, so it takes the hook's href
|
|
458
|
+
// path, which opens the destination in a new tab after the shared
|
|
459
|
+
// navigation rule.
|
|
460
|
+
// The container ref is attached to link rows only, so every other row stays
|
|
461
|
+
// a plain element and both hook instances stay inert on it.
|
|
462
|
+
const forwardsToLink = href != null && handleClick == null;
|
|
463
|
+
const linkRowRef = useRef<HTMLDivElement>(null);
|
|
464
|
+
const {onClick: delegateToLink} = useClickableContainer({
|
|
465
|
+
containerRef: linkRowRef,
|
|
466
|
+
interactiveRef: linkRef,
|
|
467
|
+
disabled: isDisabled || !forwardsToLink,
|
|
468
|
+
});
|
|
469
|
+
const {onMouseUp: openLinkOnMiddleClick} = useClickableContainer({
|
|
470
|
+
containerRef: linkRowRef,
|
|
471
|
+
href,
|
|
472
|
+
target,
|
|
473
|
+
disabled: isDisabled || !forwardsToLink,
|
|
474
|
+
});
|
|
475
|
+
|
|
405
476
|
// Per-level indent distance. The per-level step is the public, themeable
|
|
406
477
|
// `--tree-list-indent` lever (default `--spacing-4`, set on the tree-list
|
|
407
478
|
// root). A leaf adds a fixed chevron-column offset (chevron width + gap) so
|
|
@@ -411,8 +482,8 @@ export function TreeListItem({
|
|
|
411
482
|
// every row sits flush, like a parent at that level. That offset is tied to
|
|
412
483
|
// the chevron's own dimensions, not the indent step, so it does not scale
|
|
413
484
|
// with the lever. Published as the private `--_tree-indent` and consumed by
|
|
414
|
-
// `
|
|
415
|
-
//
|
|
485
|
+
// `indentedContent`'s stylesheet `padding-inline-start`; the step stays
|
|
486
|
+
// themeable through `--tree-list-indent` (see #4308).
|
|
416
487
|
const reservesChevronColumn = !hasChildren && hasExpandableItems;
|
|
417
488
|
const indentDistance = reservesChevronColumn
|
|
418
489
|
? `calc(${nestedLevel} * var(--tree-list-indent) + ${spacingVars['--spacing-4']} + ${spacingVars['--spacing-2']})`
|
|
@@ -521,6 +592,7 @@ export function TreeListItem({
|
|
|
521
592
|
)}
|
|
522
593
|
{href != null ? (
|
|
523
594
|
<LinkComponent
|
|
595
|
+
ref={linkRef}
|
|
524
596
|
href={href}
|
|
525
597
|
target={target}
|
|
526
598
|
aria-disabled={isDisabled || undefined}
|
|
@@ -599,6 +671,7 @@ export function TreeListItem({
|
|
|
599
671
|
isInteractive || (hasChildren && onClick == null)
|
|
600
672
|
? focusOutlineProps.focusWithinOrPublished(
|
|
601
673
|
styles.contentWrapper,
|
|
674
|
+
styles.focusLift,
|
|
602
675
|
densityStyles[density],
|
|
603
676
|
styles.interactive,
|
|
604
677
|
interactionOverlayStyles.backgroundImage,
|
|
@@ -620,8 +693,10 @@ export function TreeListItem({
|
|
|
620
693
|
className,
|
|
621
694
|
{...indentStyle, ...style},
|
|
622
695
|
)}
|
|
623
|
-
|
|
624
|
-
{
|
|
696
|
+
ref={forwardsToLink ? linkRowRef : undefined}
|
|
697
|
+
onClick={forwardsToLink ? delegateToLink : handleClick}
|
|
698
|
+
onMouseUp={forwardsToLink ? openLinkOnMiddleClick : undefined}>
|
|
699
|
+
<div {...stylex.props(styles.indentedContent)}>{innerContent}</div>
|
|
625
700
|
</div>
|
|
626
701
|
</div>
|
|
627
702
|
{isExpanded && renderedChildren != null && (
|
|
@@ -624,6 +624,32 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
624
624
|
sourceGenRef.current = ++searchGenRef.current;
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
+
// The previous source is remembered in state, not a ref, so a render React
|
|
628
|
+
// discards cannot consume the comparison without its state updates.
|
|
629
|
+
const [sourceOfState, setSourceOfState] = useState(searchSource);
|
|
630
|
+
if (sourceOfState !== searchSource) {
|
|
631
|
+
setSourceOfState(searchSource);
|
|
632
|
+
if (isLoading) {
|
|
633
|
+
setIsLoading(false);
|
|
634
|
+
setHasSearched(false);
|
|
635
|
+
}
|
|
636
|
+
loadingRef.current = false;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// When the search source is replaced, any in-flight load from the previous
|
|
640
|
+
// source is abandoned (generation bumped above). Notify the wrapper's busy lane
|
|
641
|
+
// in an effect so external store listeners run safely after commit.
|
|
642
|
+
const isInitialMountRef = useRef(true);
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
if (isInitialMountRef.current) {
|
|
645
|
+
isInitialMountRef.current = false;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!loadingRef.current) {
|
|
649
|
+
onLoadingChangeRef.current?.(false);
|
|
650
|
+
}
|
|
651
|
+
}, [searchSource]);
|
|
652
|
+
|
|
627
653
|
// Layer for dropdown
|
|
628
654
|
const handleLayerShow = useCallback(() => {
|
|
629
655
|
onOpenChange?.(true);
|