@astryxdesign/core 0.7.1 → 0.7.2-canary.01eab6a
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/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.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +47 -4
- 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/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
- 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/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/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/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/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 +472 -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.test.tsx +62 -13
- package/src/DropdownMenu/DropdownMenu.tsx +47 -4
- 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/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__/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/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/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/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/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 +2 -2
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -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/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/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -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/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/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- 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/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
|
@@ -96,14 +96,15 @@ Supporting one does not justify omitting another from the durable contract.
|
|
|
96
96
|
|
|
97
97
|
## Behavioral and layout contract
|
|
98
98
|
|
|
99
|
-
| ID | Invariant
|
|
100
|
-
| --- |
|
|
101
|
-
| FR1 | The current render contains one Tree list, one Item and Item label per rendered node, optional Header, Chevron, Item description, Start content, End content, and Guide parts, and delegated Chevron glyphs.
|
|
102
|
-
| FR2 | Tree list, Item, Chevron, Item label, and Guide carry the five current `tree-list*` targets documented below.
|
|
103
|
-
| FR3 | Chevron glyph delegates to Icon's `icon` target. Header, Start content, and End content are consumer-owned and have no TreeList target.
|
|
104
|
-
| FR4 | Item description is a stable library-rendered part below Item label and currently has no public target. It does not inherit through `tree-list-item-label`; both spans are siblings inside the row content.
|
|
105
|
-
| FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two.
|
|
106
|
-
| FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision.
|
|
99
|
+
| ID | Invariant | Basis | Contract state |
|
|
100
|
+
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
101
|
+
| FR1 | The current render contains one Tree list, one Item and Item label per rendered node, optional Header, Chevron, Item description, Start content, End content, and Guide parts, and delegated Chevron glyphs. | Current source, docs, and tests | Current behavior |
|
|
102
|
+
| FR2 | Tree list, Item, Chevron, Item label, and Guide carry the five current `tree-list*` targets documented below. | Current source and target docs | Current behavior |
|
|
103
|
+
| FR3 | Chevron glyph delegates to Icon's `icon` target. Header, Start content, and End content are consumer-owned and have no TreeList target. | Current source and component docs | Current ownership |
|
|
104
|
+
| FR4 | Item description is a stable library-rendered part below Item label and currently has no public target. It does not inherit through `tree-list-item-label`; both spans are siblings inside the row content. | Current source and target docs | Current reachability gap; no new target is accepted |
|
|
105
|
+
| FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged |
|
|
106
|
+
| FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent |
|
|
107
|
+
| FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged |
|
|
107
108
|
|
|
108
109
|
### Allowed variation
|
|
109
110
|
|
|
@@ -183,6 +184,16 @@ structure. Its current lack of a target is a reachability gap, not a decision
|
|
|
183
184
|
that it must remain unthemeable. Header, Start content, and End content remain
|
|
184
185
|
caller-owned even though TreeList positions their slot wrappers.
|
|
185
186
|
|
|
187
|
+
### Design decisions
|
|
188
|
+
|
|
189
|
+
<!-- design-decisions:v1 -->
|
|
190
|
+
|
|
191
|
+
| ID | Decision | Intent or reason | Applies to | Allowed variation |
|
|
192
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
193
|
+
| DD1 | Every Item spans the full width of the tree list at any depth; its nesting indent sits inside the row, ahead of its content. | Hover, pressed, and selected fills line up across depths, so the tree reads as one list of equal rows while labels keep their per-level alignment. | Item at every depth, at rest and through hover, pressed, selected, disabled, and focus | Themes set the indent step with `--tree-list-indent` and may restyle the row's fill, radius, and padding; row padding adds to the indent instead of replacing it. |
|
|
194
|
+
| DD2 | A nested Item's indent area is part of the row's painted target: it shows the row's hover and press feedback. | The whole painted row reads as one target, matching where a click acts (FR7), so no part of a row lights up apart from the rest. | Nested Items that act on click | Themes may restyle the feedback through the `tree-list-item` target. |
|
|
195
|
+
| DD3 | Guides paint above row fills, and an Item whose focus ring is showing paints above the guides. | Hierarchy lines stay continuous through hover and selection, and the focus indicator is never cut; on a focused row that is also filled, the ring takes precedence and the fill covers the guides at that row. | Guide and Item in `lineGuides` trees | Themes may recolor or hide guides through the `tree-list-guide` target. |
|
|
196
|
+
|
|
186
197
|
### Theming anatomy
|
|
187
198
|
|
|
188
199
|
<!-- anatomy-theming:v1 -->
|
|
@@ -242,15 +253,16 @@ authorize or prohibit a future target.
|
|
|
242
253
|
|
|
243
254
|
## Verification map
|
|
244
255
|
|
|
245
|
-
| Contract | Verification | Representative states
|
|
246
|
-
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
247
|
-
| FR1 | `TreeList.test.tsx` rendering, hierarchy, slot, and accessibility suites plus source inspection | Flat, nested, described, headed, and slotted items
|
|
248
|
-
| FR2 | `TreeList.test.tsx` and `themingTargets.test.ts` | Five local targets and their documented states
|
|
249
|
-
| FR3 | Source inspection plus Icon public target metadata | Expandable rows and caller-provided slots
|
|
250
|
-
| FR4 | `TreeList.test.tsx` description rendering test and source inspection | Item with description
|
|
251
|
-
| AR1 | Tree View accessibility contract (`internal/a11y-spec`, pending) bound to TreeList, in jsdom and Chromium; `TreeList.test.tsx` row-naming cases ("names a parent row by its label alone, not the chevron toggle", "names a plain row by its label and describes it with its description", "names and describes link and action rows themselves, not only their inner control", "keeps start and end content, including controls, out of the row name", "typeahead matches the label, not the description or start content") and `TreeListRowNames.a11y.chromium.spec.ts` for the browser-computed names; `TreeList.test.tsx` structural ARIA cases ("sets aria-level, aria-posinset, and aria-setsize at the top level", "sets aria-level/posinset/setsize correctly at deeper levels", "sets aria-level across three depths"); `TreeList.test.tsx` and `useTreeFocus.test.tsx` keyboard suites | Flat and nested trees; expanded/collapsed; selected/unselected; LTR and RTL; typeahead
|
|
252
|
-
| FR5, FR6, ORD1 | Focused `TreeList.test.tsx` row-identity and styling-composition coverage, required with implementation | One input at a time; all three together; sibling rows
|
|
253
|
-
|
|
|
256
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
257
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
|
258
|
+
| FR1 | `TreeList.test.tsx` rendering, hierarchy, slot, and accessibility suites plus source inspection | Flat, nested, described, headed, and slotted items | Removing asserted Tree list, Header, Item, Chevron, Item label, Item description, Start content, End content, or Guide behavior fails existing role, content, hierarchy, or state assertions; Chevron glyph presence remains source-inspected only. | `audit:TreeList/anatomy` |
|
|
259
|
+
| FR2 | `TreeList.test.tsx` and `themingTargets.test.ts` | Five local targets and their documented states | Removing or moving any current local target fails focused class assertions or the global inventory. | `audit:TreeList/theming` |
|
|
260
|
+
| FR3 | Source inspection plus Icon public target metadata | Expandable rows and caller-provided slots | Existing focused tests do not assert the composed Icon instance; changing glyph or slot ownership requires this map and the relevant owner metadata to change. | `audit:TreeList/theming` |
|
|
261
|
+
| FR4 | `TreeList.test.tsx` description rendering test and source inspection | Item with description | Removing description fails content coverage; adding a target requires an explicit map update. | `audit:TreeList/anatomy` |
|
|
262
|
+
| AR1 | Tree View accessibility contract (`internal/a11y-spec`, pending) bound to TreeList, in jsdom and Chromium; `TreeList.test.tsx` row-naming cases ("names a parent row by its label alone, not the chevron toggle", "names a plain row by its label and describes it with its description", "names and describes link and action rows themselves, not only their inner control", "keeps start and end content, including controls, out of the row name", "typeahead matches the label, not the description or start content") and `TreeListRowNames.a11y.chromium.spec.ts` for the browser-computed names; `TreeList.test.tsx` structural ARIA cases ("sets aria-level, aria-posinset, and aria-setsize at the top level", "sets aria-level/posinset/setsize correctly at deeper levels", "sets aria-level across three depths"); `TreeList.test.tsx` and `useTreeFocus.test.tsx` keyboard suites | Flat and nested trees; expanded/collapsed; selected/unselected; LTR and RTL; typeahead | A row loses its role, label name, state or position, two rows hold a Tab stop, an arrow key stops moving or stops mirroring under RTL, Home/End or typeahead stop working, or the tree claims multi-select | `audit:TreeList/accessibility` |
|
|
263
|
+
| FR5, FR6, ORD1 | Focused `TreeList.test.tsx` row-identity and styling-composition coverage, required with implementation | One input at a time; all three together; sibling rows | Dropping any mechanism, applying mechanisms to different elements, targeting the `<li>`, changing the merge order, or styling a sibling row fails. | `audit:TreeList/api` |
|
|
264
|
+
| FR7 | Focused `TreeList.test.tsx` row-click coverage, required with implementation | Action, parent, link, and disabled rows; clicks in the indent and padding, on the Chevron, and on Start and End content controls; plain, modified, and middle clicks | A click outside the label does nothing or runs the wrong action, a link-row click reaches ancestors twice, the Chevron or a Start or End content control activates the row, or a disabled row acts. | `audit:TreeList/behavior` |
|
|
265
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and five current local targets | Missing, extra, duplicated, prefixed, or stale mappings fail repository validation. | `audit:TreeList/theming` |
|
|
254
266
|
|
|
255
267
|
Focused target-placement assertions cover all five local targets. Runtime support
|
|
256
268
|
and mutation-sensitive coverage for the item styling seam may land separately,
|
|
@@ -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,
|