@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
package/src/List/ListItem.tsx
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file ListItem.tsx
|
|
7
|
-
* @input Uses React, ReactNode, StyleXStyles, theme tokens, List edge
|
|
8
|
-
* CheckboxInput, and the marker or task checkbox a
|
|
9
|
-
* (Markdown's nested lists and the task items of mixed
|
|
7
|
+
* @input Uses React, ReactNode, StyleXStyles, theme tokens, the List row edge
|
|
8
|
+
* treatment, CheckboxInput, and the marker or task checkbox a
|
|
9
|
+
* ListMarkerScope sets (Markdown's nested lists and the task items of mixed
|
|
10
|
+
* lists)
|
|
10
11
|
* @output Exports ListItem component, ListItemProps type
|
|
11
12
|
* @position Core implementation; consumed by List, index.ts, tested by List.test.tsx
|
|
12
13
|
*
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
|
|
24
25
|
import {use, type ReactNode} from 'react';
|
|
25
26
|
import * as stylex from '@stylexjs/stylex';
|
|
27
|
+
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
26
28
|
import {
|
|
27
29
|
colorVars,
|
|
28
30
|
spacingVars,
|
|
@@ -34,7 +36,7 @@ import {ListContext, type ListMarker} from './ListContext';
|
|
|
34
36
|
import {CheckboxInput} from '../CheckboxInput';
|
|
35
37
|
import {mergeProps} from '../utils';
|
|
36
38
|
import {Item} from '../Item';
|
|
37
|
-
import type {ItemSwipeActions, ItemSwipeBehavior} from '../Item';
|
|
39
|
+
import type {ItemAlign, ItemSwipeActions, ItemSwipeBehavior} from '../Item';
|
|
38
40
|
import {themeProps} from '../utils/themeProps';
|
|
39
41
|
|
|
40
42
|
// =============================================================================
|
|
@@ -44,6 +46,13 @@ import {themeProps} from '../utils/themeProps';
|
|
|
44
46
|
export interface ListItemProps extends BaseProps<HTMLLIElement> {
|
|
45
47
|
/** Ref forwarded to the root element */
|
|
46
48
|
ref?: React.Ref<HTMLLIElement>;
|
|
49
|
+
/**
|
|
50
|
+
* Vertical alignment of the start and end content against the label,
|
|
51
|
+
* forwarded to Item. Use `first-line` to keep the start and end content level with the
|
|
52
|
+
* first line of a wrapped label.
|
|
53
|
+
* @default 'center'
|
|
54
|
+
*/
|
|
55
|
+
align?: ItemAlign;
|
|
47
56
|
/**
|
|
48
57
|
* Primary text label for the item.
|
|
49
58
|
*
|
|
@@ -142,23 +151,37 @@ const styles = stylex.create({
|
|
|
142
151
|
withCounter: {
|
|
143
152
|
counterIncrement: 'astryx-list',
|
|
144
153
|
},
|
|
145
|
-
// List's
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// and
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// the
|
|
157
|
-
|
|
154
|
+
// List's edge treatments. Each reads --_item-inset-inline, the variable
|
|
155
|
+
// Item derives its paddingInline from, on the row itself (custom properties
|
|
156
|
+
// only cascade downward, so the <ul> cannot read it); density and theme
|
|
157
|
+
// paddingInline overrides on `item` move it. Each edge resolves against ITS
|
|
158
|
+
// OWN container padding var, so asymmetric padding never over-reaches an
|
|
159
|
+
// edge, and logical properties keep RTL correct.
|
|
160
|
+
//
|
|
161
|
+
// 'flush': no inset, so content meets the row's edges.
|
|
162
|
+
edgeFlush: {
|
|
163
|
+
'--_item-inset-inline': '0px',
|
|
164
|
+
},
|
|
165
|
+
// 'outset': the list reaches the container's padding edge (List.tsx); the
|
|
166
|
+
// row steps back in by whatever padding its inset does not fill, so the row
|
|
167
|
+
// reaches min(inset, container padding) past the content line and its text
|
|
168
|
+
// lands on that line when the padding is at least the inset.
|
|
169
|
+
edgeOutset: {
|
|
158
170
|
marginInlineStart:
|
|
159
|
-
'
|
|
171
|
+
'max(var(--container-padding-inline-start, 0px) - var(--_item-inset-inline), 0px)',
|
|
160
172
|
marginInlineEnd:
|
|
161
|
-
'
|
|
173
|
+
'max(var(--container-padding-inline-end, 0px) - var(--_item-inset-inline), 0px)',
|
|
174
|
+
},
|
|
175
|
+
// 'bleed': the row fills the list, which reaches the container's padding
|
|
176
|
+
// edge (List.tsx); its text sits on the container's padding line, never
|
|
177
|
+
// closer to the row's edge than its own inset. Square corners: the row
|
|
178
|
+
// meets the container's straight sides.
|
|
179
|
+
edgeBleed: {
|
|
180
|
+
paddingInlineStart:
|
|
181
|
+
'max(var(--container-padding-inline-start, 0px), var(--_item-inset-inline))',
|
|
182
|
+
paddingInlineEnd:
|
|
183
|
+
'max(var(--container-padding-inline-end, 0px), var(--_item-inset-inline))',
|
|
184
|
+
borderRadius: 0,
|
|
162
185
|
},
|
|
163
186
|
withDivider: {
|
|
164
187
|
borderBlockEndWidth: borderVars['--border-width'],
|
|
@@ -296,6 +319,7 @@ export function ListItem({
|
|
|
296
319
|
rel,
|
|
297
320
|
isDisabled = false,
|
|
298
321
|
isSelected = false,
|
|
322
|
+
align,
|
|
299
323
|
xstyle,
|
|
300
324
|
className,
|
|
301
325
|
style,
|
|
@@ -305,8 +329,8 @@ export function ListItem({
|
|
|
305
329
|
const ctx = use(ListContext);
|
|
306
330
|
const density = ctx?.density ?? 'balanced';
|
|
307
331
|
const hasDividers = ctx?.hasDividers ?? false;
|
|
332
|
+
const edge = ctx?.edge ?? 'padded';
|
|
308
333
|
const listStyle = ctx?.listStyle ?? 'none';
|
|
309
|
-
const edgeCompensation = ctx?.edgeCompensation;
|
|
310
334
|
const hasMarkers = listStyle !== 'none';
|
|
311
335
|
// ListMarkerScope may name another marker for this item.
|
|
312
336
|
const markerKind: ListMarker | null =
|
|
@@ -360,14 +384,19 @@ export function ListItem({
|
|
|
360
384
|
rel={rel}
|
|
361
385
|
isDisabled={isDisabled}
|
|
362
386
|
isSelected={isSelected}
|
|
387
|
+
align={align}
|
|
363
388
|
density={density}
|
|
364
|
-
xstyle={
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
389
|
+
xstyle={
|
|
390
|
+
[
|
|
391
|
+
hasMarkers && styles.withCounter,
|
|
392
|
+
hasDividers && styles.withDivider,
|
|
393
|
+
hasDividers && embeddedStyles.noRadius,
|
|
394
|
+
edge === 'flush' && styles.edgeFlush,
|
|
395
|
+
edge === 'outset' && styles.edgeOutset,
|
|
396
|
+
edge === 'bleed' && styles.edgeBleed,
|
|
397
|
+
xstyle,
|
|
398
|
+
] as StyleXStyles
|
|
399
|
+
}
|
|
371
400
|
{...mergeProps(themeProps('list-item'), {className, style})}
|
|
372
401
|
{...restProps}
|
|
373
402
|
/>
|
package/src/List/index.ts
CHANGED
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export {List} from './List';
|
|
15
|
-
export type {ListProps, ListStyle, ListDensity} from './List';
|
|
15
|
+
export type {ListProps, ListStyle, ListDensity, ListEdge} from './List';
|
|
16
16
|
export {ListItem} from './ListItem';
|
|
17
17
|
export type {ListItemProps} from './ListItem';
|
|
@@ -405,6 +405,16 @@ const markdownTableCellStyles = stylex.create({
|
|
|
405
405
|
code: {
|
|
406
406
|
wordBreak: 'normal',
|
|
407
407
|
},
|
|
408
|
+
// The grid's outer edge. `dividers="grid"` draws the lines *between* cells
|
|
409
|
+
// and stops at the table's rim, so without this the top, bottom, and sides
|
|
410
|
+
// stay open and the grid reads as unfinished. On the table element, not the
|
|
411
|
+
// wrapper, so `border-collapse: collapse` merges it with the cell lines
|
|
412
|
+
// into one 1px rule rather than doubling up at the edge.
|
|
413
|
+
tableOuterEdge: {
|
|
414
|
+
borderWidth: borderVars['--border-width'],
|
|
415
|
+
borderStyle: 'solid',
|
|
416
|
+
borderColor: colorVars['--color-border'],
|
|
417
|
+
},
|
|
408
418
|
});
|
|
409
419
|
|
|
410
420
|
/**
|
|
@@ -1905,7 +1915,10 @@ function renderBlock(
|
|
|
1905
1915
|
),
|
|
1906
1916
|
)}
|
|
1907
1917
|
{...lineStamps}>
|
|
1908
|
-
<Table
|
|
1918
|
+
<Table
|
|
1919
|
+
dividers="grid"
|
|
1920
|
+
textOverflow="wrap"
|
|
1921
|
+
xstyle={markdownTableCellStyles.tableOuterEdge}>
|
|
1909
1922
|
<TableHeader>
|
|
1910
1923
|
<TableRow>
|
|
1911
1924
|
{header?.children.map((h, i) => (
|
|
@@ -117,6 +117,31 @@ describe('createMarkdownFrontmatter', () => {
|
|
|
117
117
|
).not.toThrow();
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
it('accepts the decoded fields record as metadata (identity parse)', () => {
|
|
121
|
+
const fields = createMarkdownFrontmatter({
|
|
122
|
+
name: 'fields',
|
|
123
|
+
parse: record => record,
|
|
124
|
+
});
|
|
125
|
+
const source = '---\ntitle: Release notes\n__proto__: kept\n---\n# Shipped';
|
|
126
|
+
|
|
127
|
+
const parsed = fields.parse(source);
|
|
128
|
+
expect(parsed).toEqual({
|
|
129
|
+
status: 'match',
|
|
130
|
+
contentStart: source.indexOf('# Shipped'),
|
|
131
|
+
metadata: {title: 'Release notes', ['__proto__']: 'kept'},
|
|
132
|
+
});
|
|
133
|
+
if (parsed.status !== 'match') {
|
|
134
|
+
throw new Error('expected a frontmatter match');
|
|
135
|
+
}
|
|
136
|
+
// A field named like a prototype key stays an ordinary own field.
|
|
137
|
+
expect(Object.hasOwn(parsed.metadata, '__proto__')).toBe(true);
|
|
138
|
+
expect(Object.getPrototypeOf(parsed.metadata)).toBe(Object.prototype);
|
|
139
|
+
|
|
140
|
+
const root = parseMarkdownAst(source, {plugins: [fields.plugin]});
|
|
141
|
+
expect(fields.getMetadata(root)).toEqual(parsed.metadata);
|
|
142
|
+
expect(JSON.stringify(root)).not.toContain('title: Release notes');
|
|
143
|
+
});
|
|
144
|
+
|
|
120
145
|
it('returns a transform plugin that composes in the ordered pipeline', () => {
|
|
121
146
|
expectTypeOf<MarkdownTransform>().toBeFunction();
|
|
122
147
|
const root = parseMarkdownAst(SOURCE, {plugins: [documentMetadata.plugin]});
|
|
@@ -104,18 +104,22 @@ function readFrontmatter(source: string):
|
|
|
104
104
|
if (close == null) {
|
|
105
105
|
return {status: 'defer'};
|
|
106
106
|
}
|
|
107
|
-
const
|
|
107
|
+
const entries = new Map<string, string>();
|
|
108
108
|
for (const line of body.slice(0, close.index).split(/\r?\n/)) {
|
|
109
109
|
if (line.trim() === '') {
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
const separator = line.indexOf(':');
|
|
113
113
|
const key = line.slice(0, separator).trim();
|
|
114
|
-
if (separator <= 0 || key === '' ||
|
|
114
|
+
if (separator <= 0 || key === '' || entries.has(key)) {
|
|
115
115
|
return {status: 'none'};
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
entries.set(key, line.slice(separator + 1).trim());
|
|
118
118
|
}
|
|
119
|
+
// A plain object, so a parser that returns the record unchanged passes the
|
|
120
|
+
// JSON-like metadata check. `fromEntries` defines every key as an own data
|
|
121
|
+
// property, so a field named `__proto__` stays a field.
|
|
122
|
+
const fields: Record<string, string> = Object.fromEntries(entries);
|
|
119
123
|
let end = openingLength + close.index + close[0].length;
|
|
120
124
|
if (source.startsWith('\r\n', end)) {
|
|
121
125
|
end += 2;
|
|
@@ -43,8 +43,8 @@ export const docs = {
|
|
|
43
43
|
{
|
|
44
44
|
name: 'label',
|
|
45
45
|
type: "{ position?: 'start' | 'top', width?: number | string }",
|
|
46
|
-
description: "Label display configuration. position controls label placement, width sets a custom label column width. Defaults to { position: 'top' } for multi-column layouts.",
|
|
47
|
-
default: "{ position: 'start' } (single-column) / { position: 'top' } (multi-column)",
|
|
46
|
+
description: "Label display configuration. position controls label placement, width sets a custom label column width. Defaults to { position: 'top' } for multi-column layouts. Position is ignored in horizontal mode, which always uses { position: 'top' }.",
|
|
47
|
+
default: "{ position: 'start' } (vertical single-column) / { position: 'top' } (multi-column or horizontal)",
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
name: 'maxNumOfItems',
|
|
@@ -124,6 +124,30 @@ describe('MetadataList', () => {
|
|
|
124
124
|
expect(screen.getByText('B')).toBeInTheDocument();
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it('uses top labels in horizontal orientation even when position is start', () => {
|
|
128
|
+
render(
|
|
129
|
+
<MetadataList orientation="horizontal" label={{position: 'start'}}>
|
|
130
|
+
<MetadataListItem label="A">1</MetadataListItem>
|
|
131
|
+
</MetadataList>,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const item = screen.getByRole('term').closest('.astryx-metadata-list-item');
|
|
135
|
+
expect(item).toContainElement(screen.getByRole('definition'));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('defaults to side labels and preserves width when position is omitted', () => {
|
|
139
|
+
const {container} = render(
|
|
140
|
+
<MetadataList label={{width: 120}}>
|
|
141
|
+
<MetadataListItem label="A">1</MetadataListItem>
|
|
142
|
+
</MetadataList>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const list = container.querySelector('dl');
|
|
146
|
+
expect(list?.getAttribute('style')).toContain('120px minmax(0, 1fr)');
|
|
147
|
+
expect(screen.getByRole('term').parentElement).toBe(list);
|
|
148
|
+
expect(screen.getByRole('definition').parentElement).toBe(list);
|
|
149
|
+
});
|
|
150
|
+
|
|
127
151
|
describe('numeric columns', () => {
|
|
128
152
|
// A fixed column count is a runtime value, so it arrives as a StyleX
|
|
129
153
|
// dynamic style: the template lands in the element's inline style (as the
|
|
@@ -163,6 +187,20 @@ describe('MetadataList', () => {
|
|
|
163
187
|
expect(gridTemplateOf(container)).not.toContain('repeat(');
|
|
164
188
|
});
|
|
165
189
|
|
|
190
|
+
it('defaults to top labels for columns={2} with a width-only label config', () => {
|
|
191
|
+
const {container} = render(
|
|
192
|
+
<MetadataList columns={2} label={{width: 260}}>
|
|
193
|
+
<MetadataListItem label="A">1</MetadataListItem>
|
|
194
|
+
</MetadataList>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(gridTemplateOf(container)).toContain('repeat(2, 1fr)');
|
|
198
|
+
const item = screen
|
|
199
|
+
.getByRole('term')
|
|
200
|
+
.closest('.astryx-metadata-list-item');
|
|
201
|
+
expect(item).toContainElement(screen.getByRole('definition'));
|
|
202
|
+
});
|
|
203
|
+
|
|
166
204
|
it('ignores numeric columns in horizontal orientation', () => {
|
|
167
205
|
const {container} = render(
|
|
168
206
|
<MetadataList columns={3} orientation="horizontal">
|
|
@@ -206,7 +206,14 @@ export function MetadataList({
|
|
|
206
206
|
columns === 'multi' || (typeof columns === 'number' && columns > 1);
|
|
207
207
|
// Default to 'top' for multi-column and horizontal layouts since side labels
|
|
208
208
|
// don't work well when items are in separate grid cells.
|
|
209
|
-
const
|
|
209
|
+
const defaultLabelConfig = isMultiColumn ? LABEL_TOP : LABEL_START;
|
|
210
|
+
const labelConfig = useMemo(
|
|
211
|
+
() => ({
|
|
212
|
+
...label,
|
|
213
|
+
position: label?.position ?? defaultLabelConfig.position,
|
|
214
|
+
}),
|
|
215
|
+
[label, defaultLabelConfig],
|
|
216
|
+
);
|
|
210
217
|
const [isShowAll, setIsShowAll] = useState(false);
|
|
211
218
|
const contentId = useId();
|
|
212
219
|
const t = useTranslator();
|
|
@@ -35,6 +35,7 @@ import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
|
35
35
|
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
36
36
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
37
37
|
import {InputGroup, InputGroupText} from '../InputGroup';
|
|
38
|
+
import {focusVars} from '../theme/tokens.stylex';
|
|
38
39
|
|
|
39
40
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
40
41
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -4083,6 +4084,164 @@ describe('MultiSelector option actions (grid)', () => {
|
|
|
4083
4084
|
expect(onChange).not.toHaveBeenCalled();
|
|
4084
4085
|
});
|
|
4085
4086
|
|
|
4087
|
+
it('draws the standard focus ring on the active action cell, from the focus tokens', async () => {
|
|
4088
|
+
// jsdom resolves no custom property, so the ring is read from StyleX
|
|
4089
|
+
// classes: a class name hashes one property and its value, and the same
|
|
4090
|
+
// declaration yields the same class wherever it is written.
|
|
4091
|
+
const ring = stylex.create({
|
|
4092
|
+
active: {
|
|
4093
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4094
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4095
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4096
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4097
|
+
},
|
|
4098
|
+
});
|
|
4099
|
+
const ringClasses = stylex
|
|
4100
|
+
.props(ring.active)
|
|
4101
|
+
.className!.split(' ')
|
|
4102
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4103
|
+
expect(ringClasses).toHaveLength(4);
|
|
4104
|
+
|
|
4105
|
+
const user = userEvent.setup();
|
|
4106
|
+
render(
|
|
4107
|
+
<MultiSelector
|
|
4108
|
+
label="Labels"
|
|
4109
|
+
options={OPTIONS}
|
|
4110
|
+
value={[]}
|
|
4111
|
+
onChange={() => {}}
|
|
4112
|
+
/>,
|
|
4113
|
+
);
|
|
4114
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4115
|
+
await user.click(trigger);
|
|
4116
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4117
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4118
|
+
for (const token of ringClasses) {
|
|
4119
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4120
|
+
}
|
|
4121
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4122
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4123
|
+
for (const token of ringClasses) {
|
|
4124
|
+
expect(actionCell).toHaveClass(token);
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4128
|
+
it('draws no focus ring on an action cell highlighted after pointer input, until a key moves the highlight', async () => {
|
|
4129
|
+
const ring = stylex.create({
|
|
4130
|
+
active: {
|
|
4131
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4132
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4133
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4134
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4135
|
+
},
|
|
4136
|
+
});
|
|
4137
|
+
const ringClasses = stylex
|
|
4138
|
+
.props(ring.active)
|
|
4139
|
+
.className!.split(' ')
|
|
4140
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4141
|
+
expect(ringClasses).toHaveLength(4);
|
|
4142
|
+
|
|
4143
|
+
const user = userEvent.setup();
|
|
4144
|
+
render(
|
|
4145
|
+
<MultiSelector
|
|
4146
|
+
label="Labels"
|
|
4147
|
+
options={OPTIONS}
|
|
4148
|
+
value={[]}
|
|
4149
|
+
onChange={() => {}}
|
|
4150
|
+
/>,
|
|
4151
|
+
);
|
|
4152
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4153
|
+
await user.click(trigger);
|
|
4154
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4155
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4156
|
+
fireEvent.mouseEnter(actionCell);
|
|
4157
|
+
// The hover moved the highlight onto the action cell, without the ring.
|
|
4158
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4159
|
+
for (const token of ringClasses) {
|
|
4160
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4161
|
+
}
|
|
4162
|
+
// Keyboard navigation back onto the cell draws it.
|
|
4163
|
+
fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
|
|
4164
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4165
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4166
|
+
for (const token of ringClasses) {
|
|
4167
|
+
expect(actionCell).toHaveClass(token);
|
|
4168
|
+
}
|
|
4169
|
+
});
|
|
4170
|
+
|
|
4171
|
+
describe('ring under mixed input', () => {
|
|
4172
|
+
const ringClasses = () => {
|
|
4173
|
+
const ring = stylex.create({
|
|
4174
|
+
active: {
|
|
4175
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4176
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4177
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4178
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4179
|
+
},
|
|
4180
|
+
});
|
|
4181
|
+
return stylex
|
|
4182
|
+
.props(ring.active)
|
|
4183
|
+
.className!.split(' ')
|
|
4184
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4185
|
+
};
|
|
4186
|
+
const hasRing = (cell: HTMLElement) =>
|
|
4187
|
+
ringClasses().every(token => cell.classList.contains(token));
|
|
4188
|
+
|
|
4189
|
+
async function openWithKeyboardOnBugAction() {
|
|
4190
|
+
const user = userEvent.setup();
|
|
4191
|
+
render(
|
|
4192
|
+
<MultiSelector
|
|
4193
|
+
label="Labels"
|
|
4194
|
+
options={OPTIONS}
|
|
4195
|
+
value={[]}
|
|
4196
|
+
onChange={() => {}}
|
|
4197
|
+
/>,
|
|
4198
|
+
);
|
|
4199
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4200
|
+
await user.click(trigger);
|
|
4201
|
+
const row = (name: string) => screen.getByRole('row', {name, ...h});
|
|
4202
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4203
|
+
const bugAction = row('Bug').children[1] as HTMLElement;
|
|
4204
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4205
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4206
|
+
return {trigger, row, bugAction};
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
it('keeps the ring when the pointer crosses a disabled option the highlight ignores', async () => {
|
|
4210
|
+
const {trigger, row, bugAction} = await openWithKeyboardOnBugAction();
|
|
4211
|
+
fireEvent.mouseEnter(row('Docs').children[0]);
|
|
4212
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4213
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4214
|
+
});
|
|
4215
|
+
|
|
4216
|
+
it('follows the keyboard modality when a hover moves the highlight to another action', async () => {
|
|
4217
|
+
const {trigger, row} = await openWithKeyboardOnBugAction();
|
|
4218
|
+
const docsAction = row('Docs').children[1] as HTMLElement;
|
|
4219
|
+
fireEvent.mouseEnter(docsAction);
|
|
4220
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', docsAction.id);
|
|
4221
|
+
expect(hasRing(docsAction)).toBe(true);
|
|
4222
|
+
});
|
|
4223
|
+
|
|
4224
|
+
it('draws no ring for a modifier chord after pointer input', async () => {
|
|
4225
|
+
const user = userEvent.setup();
|
|
4226
|
+
render(
|
|
4227
|
+
<MultiSelector
|
|
4228
|
+
label="Labels"
|
|
4229
|
+
options={OPTIONS}
|
|
4230
|
+
value={[]}
|
|
4231
|
+
onChange={() => {}}
|
|
4232
|
+
/>,
|
|
4233
|
+
);
|
|
4234
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4235
|
+
await user.click(trigger);
|
|
4236
|
+
const bugAction = screen.getByRole('row', {name: 'Bug', ...h})
|
|
4237
|
+
.children[1] as HTMLElement;
|
|
4238
|
+
fireEvent.mouseEnter(bugAction);
|
|
4239
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4240
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight', ctrlKey: true});
|
|
4241
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4242
|
+
});
|
|
4243
|
+
});
|
|
4244
|
+
|
|
4086
4245
|
it('the inline-end arrow reaches the action cell, Enter fires it, inline-start returns', async () => {
|
|
4087
4246
|
const user = userEvent.setup();
|
|
4088
4247
|
const onChange = vi.fn();
|
|
@@ -50,6 +50,7 @@ import type {IndicatorPosition} from '../Indicator';
|
|
|
50
50
|
import {Badge} from '../Badge';
|
|
51
51
|
import {
|
|
52
52
|
colorVars,
|
|
53
|
+
focusVars,
|
|
53
54
|
sizeVars,
|
|
54
55
|
spacingVars,
|
|
55
56
|
radiusVars,
|
|
@@ -74,7 +75,10 @@ import {
|
|
|
74
75
|
import {useMultiCombobox} from './hooks';
|
|
75
76
|
import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
|
|
76
77
|
import {devWarn, warnOnce} from '../utils/devWarning';
|
|
77
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
getInteractionModality,
|
|
80
|
+
useInteractionModalityTracking,
|
|
81
|
+
} from '../utils/interactionModality';
|
|
78
82
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
79
83
|
import {FOCUSABLE_SELECTOR} from '../hooks/focusableSelector';
|
|
80
84
|
import {useAnnounceRenderedText} from '../hooks/useAnnounceRenderedText';
|
|
@@ -436,11 +440,15 @@ const styles = stylex.create({
|
|
|
436
440
|
flexShrink: 0,
|
|
437
441
|
borderRadius: radiusVars['--radius-element'],
|
|
438
442
|
},
|
|
443
|
+
// The standard ring, from the focus tokens themselves: inside
|
|
444
|
+
// `stylex.create` StyleX resolves an imported `defineVars` group, and a
|
|
445
|
+
// plain object such as `FOCUS_OUTLINE_PARTS` compiles to variables nothing
|
|
446
|
+
// defines.
|
|
439
447
|
actionCellActive: {
|
|
440
|
-
outlineWidth:
|
|
441
|
-
outlineStyle:
|
|
442
|
-
outlineColor:
|
|
443
|
-
outlineOffset:
|
|
448
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
449
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
450
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
451
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
444
452
|
},
|
|
445
453
|
|
|
446
454
|
// Empty state
|
|
@@ -1752,8 +1760,8 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1752
1760
|
getActionCellId,
|
|
1753
1761
|
activeDescendantId,
|
|
1754
1762
|
onTriggerClick,
|
|
1755
|
-
onKeyDown,
|
|
1756
|
-
onItemMouseEnter,
|
|
1763
|
+
onKeyDown: onComboboxKeyDown,
|
|
1764
|
+
onItemMouseEnter: onComboboxItemMouseEnter,
|
|
1757
1765
|
} = useMultiCombobox({
|
|
1758
1766
|
selectableItems: sortedItems,
|
|
1759
1767
|
isDisabled: isDisabled || isEffectivelyReadOnly,
|
|
@@ -1784,6 +1792,29 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1784
1792
|
),
|
|
1785
1793
|
});
|
|
1786
1794
|
|
|
1795
|
+
// A hover moves the highlight too, but the focus ring follows the shared
|
|
1796
|
+
// interaction modality (architecture:interaction-modality INV1): it is read
|
|
1797
|
+
// whenever a key or the pointer moves the highlight, so an action cell
|
|
1798
|
+
// highlighted after pointer input keeps the hover treatment and no ring.
|
|
1799
|
+
useInteractionModalityTracking();
|
|
1800
|
+
const [isKeyboardHighlight, setIsKeyboardHighlight] = useState(
|
|
1801
|
+
() => getInteractionModality() === 'keyboard',
|
|
1802
|
+
);
|
|
1803
|
+
const onKeyDown = useCallback(
|
|
1804
|
+
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
1805
|
+
onComboboxKeyDown(event);
|
|
1806
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1807
|
+
},
|
|
1808
|
+
[onComboboxKeyDown],
|
|
1809
|
+
);
|
|
1810
|
+
const onItemMouseEnter = useCallback(
|
|
1811
|
+
(...args: Parameters<typeof onComboboxItemMouseEnter>) => {
|
|
1812
|
+
onComboboxItemMouseEnter(...args);
|
|
1813
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1814
|
+
},
|
|
1815
|
+
[onComboboxItemMouseEnter],
|
|
1816
|
+
);
|
|
1817
|
+
|
|
1787
1818
|
// The trigger button owns its click so a consumer `onClick`, which lands on
|
|
1788
1819
|
// the button, composes after the built-in toggle. A press elsewhere in the
|
|
1789
1820
|
// control box (padding, start icon, chevron) still opens the panel.
|
|
@@ -2117,6 +2148,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2117
2148
|
styles.actionCell,
|
|
2118
2149
|
isHighlighted &&
|
|
2119
2150
|
highlightedCell === 'action' &&
|
|
2151
|
+
isKeyboardHighlight &&
|
|
2120
2152
|
styles.actionCellActive,
|
|
2121
2153
|
)}>
|
|
2122
2154
|
{isSelectAll || isCreate ? null : item.action}
|
|
@@ -2131,6 +2163,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2131
2163
|
[
|
|
2132
2164
|
isGrid,
|
|
2133
2165
|
highlightedCell,
|
|
2166
|
+
isKeyboardHighlight,
|
|
2134
2167
|
getActionCellId,
|
|
2135
2168
|
renderOption,
|
|
2136
2169
|
indicatorPosition,
|