@astryxdesign/core 0.7.2-canary.fe2c655 → 0.7.2
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/CHANGELOG.md +156 -2
- package/README.md +4 -4
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -1
- package/dist/AspectRatio/AspectRatio.d.ts +6 -8
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +44 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +8 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +1 -0
- 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/CheckboxList.d.ts +1 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +6 -2
- 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/DropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +84 -113
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +15 -4
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
- package/dist/DropdownMenu/menuRegionStops.d.ts +38 -0
- package/dist/DropdownMenu/menuRegionStops.d.ts.map +1 -0
- package/dist/DropdownMenu/menuRegionStops.js +131 -0
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +4 -10
- package/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +8 -4
- package/dist/FormLayout/FormLayout.d.ts +5 -4
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -1
- package/dist/FormLayout/FormLayoutContext.d.ts +3 -3
- package/dist/FormLayout/FormLayoutContext.js +3 -3
- package/dist/Grid/Grid.d.ts +8 -7
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +5 -4
- package/dist/Grid/GridSpan.d.ts +1 -1
- 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/Item/Item.d.ts +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +19 -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/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +8 -2
- package/dist/Markdown/plugins/headingLinks.d.ts.map +1 -1
- package/dist/Markdown/plugins/headingLinks.js +9 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +5 -1
- package/dist/MetadataList/MetadataListContext.d.ts +1 -1
- package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.d.ts +9 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +30 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +5 -0
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +45 -11
- 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/SideNav/SideNavItem.d.ts +8 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +76 -10
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- 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/Token/Token.js +1 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +5 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +5 -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/astryx.css +27 -4
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +18 -13
- package/dist/reset.a11y.chromium.spec.js +50 -1
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +15 -9
- package/docs.mjs +4 -4
- package/package.json +4 -4
- package/scripts/agent-doc-state.mjs +66 -6
- package/scripts/postinstall.mjs +44 -3
- package/src/AppShell/AppShell.tsx +45 -1
- package/src/AspectRatio/AspectRatio.test.tsx +44 -43
- package/src/AspectRatio/AspectRatio.tsx +38 -18
- package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
- package/src/Avatar/Avatar.doc.mjs +5 -3
- package/src/Avatar/Avatar.tsx +11 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
- package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +24 -1
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Chat/ChatLayoutScrollButton.test.tsx +13 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +64 -19
- 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.fr10.test.tsx +29 -2
- package/src/CheckboxList/CheckboxList.test.tsx +22 -0
- package/src/CheckboxList/CheckboxList.tsx +4 -1
- package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
- package/src/CheckboxList/CheckboxListItem.tsx +10 -0
- package/src/Code/Code.doc.mjs +1 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
- package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
- package/src/ContextMenu/ContextMenu.test.tsx +12 -2
- 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 +1 -0
- package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +79 -6
- package/src/DropdownMenu/DropdownMenu.tsx +88 -144
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
- package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
- package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
- package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +17 -3
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
- package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
- package/src/DropdownMenu/menuRegionStops.ts +167 -0
- package/src/EmptyState/EmptyState.doc.mjs +3 -2
- package/src/EmptyState/EmptyState.spec.md +189 -0
- package/src/EmptyState/EmptyState.test.tsx +74 -3
- package/src/EmptyState/EmptyState.tsx +4 -8
- package/src/Field/Field.doc.mjs +14 -8
- package/src/Field/Field.spec.md +217 -0
- package/src/Field/Field.test.tsx +60 -2
- package/src/Field/Field.tsx +17 -8
- package/src/Field/FieldLabel.doc.mjs +133 -26
- package/src/Field/FieldLabel.spec.md +172 -0
- package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
- package/src/FormLayout/FormLayout.doc.mjs +6 -3
- package/src/FormLayout/FormLayout.spec.md +192 -0
- package/src/FormLayout/FormLayout.test.tsx +15 -0
- package/src/FormLayout/FormLayout.tsx +7 -6
- package/src/FormLayout/FormLayoutContext.ts +3 -3
- package/src/Grid/Grid.doc.mjs +7 -1
- package/src/Grid/Grid.spec.md +93 -113
- package/src/Grid/Grid.test.tsx +22 -0
- package/src/Grid/Grid.tsx +10 -9
- package/src/Grid/GridSpan.doc.mjs +3 -3
- package/src/Grid/GridSpan.tsx +4 -1
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/Icon/Icon.doc.mjs +36 -0
- package/src/Indicator/Indicator.doc.mjs +3 -3
- package/src/Item/Item.doc.mjs +12 -8
- package/src/Item/Item.test.tsx +61 -0
- package/src/Item/Item.tsx +40 -4
- package/src/Layout/LayoutHeader.doc.mjs +5 -0
- 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 +16 -0
- package/src/List/ListItem.tsx +56 -27
- package/src/List/index.ts +1 -1
- package/src/Markdown/Markdown.tsx +14 -1
- package/src/Markdown/parser.ts +8 -2
- package/src/Markdown/plugins/headingLinks.ts +9 -1
- package/src/MetadataList/MetadataList.doc.mjs +3 -3
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MobileNav/MobileNav.tsx +30 -3
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
- package/src/MoreMenu/MoreMenu.test.tsx +5 -1
- package/src/PowerSearch/PowerSearch.test.tsx +62 -0
- package/src/PowerSearch/PowerSearch.tsx +9 -0
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +71 -0
- package/src/PowerSearch/PowerSearchValueEditor.tsx +45 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
- package/src/RadioList/RadioList.fr10.test.tsx +45 -2
- package/src/RadioList/RadioList.test.tsx +22 -0
- package/src/RadioList/RadioList.tsx +4 -1
- package/src/RadioList/RadioListItem.doc.mjs +16 -0
- package/src/RadioList/RadioListItem.tsx +10 -1
- package/src/Resizable/useResizable.doc.mjs +2 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
- package/src/SideNav/SideNav.spec.md +227 -0
- package/src/SideNav/SideNav.test.tsx +220 -0
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +99 -17
- package/src/Slider/Slider.spec.md +39 -18
- package/src/Spinner/Spinner.doc.mjs +1 -1
- package/src/TabList/TabList.spec.md +57 -44
- package/src/Table/Table.spec.md +6 -1
- package/src/Table/plugins/activation/useTableActivation.spec.md +254 -0
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
- package/src/Token/Token.tsx +1 -1
- package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +86 -0
- package/src/Toolbar/Toolbar.test.tsx +0 -8
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +47 -0
- package/src/TopNav/TopNavMegaMenu.tsx +9 -3
- package/src/TopNav/TopNavMenu.test.tsx +46 -1
- package/src/TopNav/TopNavMenu.tsx +13 -3
- package/src/TreeList/TreeList.doc.mjs +1 -0
- 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/docPropLiterals.test.ts +36 -10
- package/src/hooks/useMenuHover.ts +18 -13
- package/src/postinstall.test.mjs +79 -0
- package/src/reset.a11y.chromium.spec.ts +52 -1
- package/src/reset.css +9 -29
- package/src/reset.test.ts +11 -0
- package/src/theme/derivedVarRegistry.test.ts +7 -0
- package/src/unicodePropertyEscapes.source-build.test.mjs +69 -0
- package/src/utils/plainDate.test.ts +37 -1
- package/src/utils/plainDate.ts +15 -5
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) => (
|
package/src/Markdown/parser.ts
CHANGED
|
@@ -1718,8 +1718,14 @@ interface DelimiterRun {
|
|
|
1718
1718
|
readonly canClose: boolean;
|
|
1719
1719
|
}
|
|
1720
1720
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1721
|
+
// Built at runtime, not written as literals: for targets without property
|
|
1722
|
+
// escapes, or when no browserslist is set, Babel's
|
|
1723
|
+
// transform-unicode-property-regex rewrites a `\p{…}` literal, and the copy
|
|
1724
|
+
// bundled with Next.js lacks the Unicode data it needs, so a source build fails
|
|
1725
|
+
// with "Unknown property". Literals are safe once Next's bundled Babel ships
|
|
1726
|
+
// that data.
|
|
1727
|
+
const UNICODE_WHITESPACE = new RegExp('^[\\t\\n\\f\\r\\p{Zs}]$', 'u');
|
|
1728
|
+
const UNICODE_PUNCTUATION = new RegExp('^[\\p{P}\\p{S}]$', 'u');
|
|
1723
1729
|
|
|
1724
1730
|
/** The character before `index` (a whole code point), if any. */
|
|
1725
1731
|
function characterBefore(text: string, index: number): string | undefined {
|
|
@@ -59,13 +59,21 @@ function fail(message: string): never {
|
|
|
59
59
|
throw new TypeError(`Markdown heading links: ${message}`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Built at runtime, not written as a literal: for targets without property
|
|
63
|
+
// escapes, or when no browserslist is set, Babel's
|
|
64
|
+
// transform-unicode-property-regex rewrites a `\p{…}` literal, and the copy
|
|
65
|
+
// bundled with Next.js lacks the Unicode data it needs, so a source build fails
|
|
66
|
+
// with "Unknown property". A literal is safe once Next's bundled Babel ships
|
|
67
|
+
// that data.
|
|
68
|
+
const NON_SLUG_CHARACTERS = new RegExp('[^\\p{Letter}\\p{Number}]+', 'gu');
|
|
69
|
+
|
|
62
70
|
function headingSlug(value: string): string {
|
|
63
71
|
return value
|
|
64
72
|
.normalize('NFKC')
|
|
65
73
|
.trim()
|
|
66
74
|
.toLowerCase()
|
|
67
75
|
.replace(/['"]/gu, '')
|
|
68
|
-
.replace(
|
|
76
|
+
.replace(NON_SLUG_CHARACTERS, '-')
|
|
69
77
|
.replace(/^-+|-+$/gu, '');
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -7,7 +7,7 @@ export const docs = {
|
|
|
7
7
|
displayName: 'Metadata List',
|
|
8
8
|
group: 'MetadataList',
|
|
9
9
|
category: 'Table & List',
|
|
10
|
-
keywords: ["metadata","description","definition","keyvalue","properties","details","attributes","summary"],
|
|
10
|
+
keywords: ["metadata","description","definition","keyvalue","properties","details","attributes","summary","descriptionlist","descriptions"],
|
|
11
11
|
theming: {
|
|
12
12
|
targets: [
|
|
13
13
|
{
|
|
@@ -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();
|
|
@@ -260,8 +260,15 @@ const dynamicStyles = stylex.create({
|
|
|
260
260
|
// Close timing
|
|
261
261
|
// =============================================================================
|
|
262
262
|
|
|
263
|
-
/**
|
|
264
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Longest the drawer will wait before closing, however long the hold is.
|
|
265
|
+
*
|
|
266
|
+
* Exported so `AppShell` can defer hiding this drawer's `Activity` boundary
|
|
267
|
+
* by at least this long too (`ACTIVITY_HIDE_BUFFER_MS` in AppShell.tsx) —
|
|
268
|
+
* otherwise Activity's `display: none !important` lands before this delay's
|
|
269
|
+
* own scheduled `dialog.close()` has a chance to run (#5701).
|
|
270
|
+
*/
|
|
271
|
+
export const MAX_CLOSE_DELAY_MS = 250;
|
|
265
272
|
/** Fraction of the hold to close at, so the close never lands on its boundary. */
|
|
266
273
|
const CLOSE_WITHIN_HOLD = 0.6;
|
|
267
274
|
|
|
@@ -488,6 +495,21 @@ export function MobileNav({
|
|
|
488
495
|
// Element that was focused when the drawer opened; focus goes back to it
|
|
489
496
|
// once the native dialog has closed (see closeAndRestoreFocus).
|
|
490
497
|
const triggerElementRef = useRef<HTMLElement | null>(null);
|
|
498
|
+
|
|
499
|
+
// Keeps the dialog rendered from the commit that closes it until the delayed
|
|
500
|
+
// `close()` below has run. The `display` transition on the dialog is meant to
|
|
501
|
+
// do this, but a browser that does not hold a discrete `display` transition
|
|
502
|
+
// (WebKit) drops the dialog to `display: none` at once while it is still an
|
|
503
|
+
// open modal: nothing is visible, yet the whole page stays inert until the
|
|
504
|
+
// timer fires. Set during render, so it is already true in the commit that
|
|
505
|
+
// closes. The drawer's transform and the backdrop stay keyed to `isOpen`, so
|
|
506
|
+
// the slide-out is unchanged.
|
|
507
|
+
const [isCloseHeld, setIsCloseHeld] = useState(false);
|
|
508
|
+
const [wasOpen, setWasOpen] = useState(isOpen);
|
|
509
|
+
if (wasOpen !== isOpen) {
|
|
510
|
+
setWasOpen(isOpen);
|
|
511
|
+
setIsCloseHeld(!isOpen);
|
|
512
|
+
}
|
|
491
513
|
const gutterRef = useRef<ScrollbarGutterHold | null>(null);
|
|
492
514
|
|
|
493
515
|
// Gives back the gutter held open in place of the hidden scrollbar.
|
|
@@ -563,7 +585,12 @@ export function MobileNav({
|
|
|
563
585
|
|
|
564
586
|
closeTimeoutRef.current = setTimeout(() => {
|
|
565
587
|
closeAndRestoreFocus(dialog, triggerElementRef);
|
|
588
|
+
setIsCloseHeld(false);
|
|
566
589
|
}, resolveCloseDelay(dialog));
|
|
590
|
+
} else {
|
|
591
|
+
// Nothing is open, so there is no close to hold the dialog for, and no
|
|
592
|
+
// close timer is left to release the hold.
|
|
593
|
+
setIsCloseHeld(false);
|
|
567
594
|
}
|
|
568
595
|
|
|
569
596
|
return () => {
|
|
@@ -647,7 +674,7 @@ export function MobileNav({
|
|
|
647
674
|
layerStructureReset.reset,
|
|
648
675
|
styles.dialog,
|
|
649
676
|
overlayPaddingReset.reset,
|
|
650
|
-
isOpen && styles.open,
|
|
677
|
+
(isOpen || isCloseHeld) && styles.open,
|
|
651
678
|
styles.backdrop,
|
|
652
679
|
isOpen && styles.backdropOpen,
|
|
653
680
|
xstyle,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
beforeEach,
|
|
27
27
|
afterEach,
|
|
28
28
|
} from 'vitest';
|
|
29
|
-
import {StrictMode} from 'react';
|
|
29
|
+
import {StrictMode, useLayoutEffect} from 'react';
|
|
30
30
|
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
31
31
|
import {MobileNav} from './MobileNav';
|
|
32
32
|
import {AppShell} from '../AppShell/AppShell';
|
|
@@ -136,6 +136,42 @@ describe('MobileNav close path edge cases', () => {
|
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
// A browser that does not hold a discrete `display` transition (WebKit)
|
|
140
|
+
// drops the dialog to `display: none` the moment the open styling goes, while
|
|
141
|
+
// it is still an open modal: nothing is visible, yet the page stays inert
|
|
142
|
+
// until the close timer fires. So the dialog's own open styling has to be
|
|
143
|
+
// held by the component, not left to that transition. jsdom applies no
|
|
144
|
+
// StyleX styles, so this reads the readable class names StyleX emits in
|
|
145
|
+
// tests: the `open` class (display) is held through the closing commit, the
|
|
146
|
+
// `backdropOpen` class is not, and the hold ends when the dialog closes.
|
|
147
|
+
it('keeps the dialog rendered until its delayed close has run', () => {
|
|
148
|
+
vi.useFakeTimers();
|
|
149
|
+
try {
|
|
150
|
+
const {rerender} = render(<Drawer isOpen />);
|
|
151
|
+
const dialog = screen.getByTestId('mobile-nav');
|
|
152
|
+
expect(dialog.className).toMatch(/styles\.open/);
|
|
153
|
+
expect(dialog.className).toMatch(/styles\.backdropOpen/);
|
|
154
|
+
|
|
155
|
+
act(() => {
|
|
156
|
+
rerender(<Drawer isOpen={false} />);
|
|
157
|
+
});
|
|
158
|
+
// Still an open modal, so still rendered; only the backdrop's open
|
|
159
|
+
// styling (the fade-out) has gone.
|
|
160
|
+
expect(dialog).toHaveAttribute('open');
|
|
161
|
+
expect(dialog.className).toMatch(/styles\.open/);
|
|
162
|
+
expect(dialog.className).not.toMatch(/styles\.backdropOpen/);
|
|
163
|
+
|
|
164
|
+
act(() => {
|
|
165
|
+
vi.advanceTimersByTime(300);
|
|
166
|
+
});
|
|
167
|
+
// Closed: the hold is released along with the dialog.
|
|
168
|
+
expect(dialog).not.toHaveAttribute('open');
|
|
169
|
+
expect(dialog.className).not.toMatch(/styles\.open/);
|
|
170
|
+
} finally {
|
|
171
|
+
vi.useRealTimers();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
139
175
|
it('closes the dialog when unmounted while fully open', () => {
|
|
140
176
|
const {unmount} = render(<Drawer isOpen />);
|
|
141
177
|
const dialog = screen.getByTestId('mobile-nav');
|
|
@@ -401,26 +437,148 @@ describe('MobileNav close path edge cases', () => {
|
|
|
401
437
|
});
|
|
402
438
|
|
|
403
439
|
it('closes the AppShell drawer when dismissed with Escape', () => {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
440
|
+
// Fake timers: AppShell now defers hiding this drawer's Activity boundary
|
|
441
|
+
// (#5701) so MobileNav's own close effect gets a real chance to run —
|
|
442
|
+
// the dialog closes via its normal deferred setTimeout, not synchronously.
|
|
443
|
+
vi.useFakeTimers();
|
|
444
|
+
try {
|
|
445
|
+
render(<TestShell />);
|
|
446
|
+
act(() => {
|
|
447
|
+
fireEvent.click(screen.getByRole('button', {name: /open navigation/i}));
|
|
448
|
+
});
|
|
449
|
+
const dialog = screen.getAllByRole('dialog', {hidden: true})[0];
|
|
450
|
+
expect(dialog).toHaveAttribute('open');
|
|
408
451
|
|
|
409
|
-
|
|
452
|
+
act(() => {
|
|
453
|
+
fireEvent(
|
|
454
|
+
dialog,
|
|
455
|
+
new Event('cancel', {cancelable: true, bubbles: false}),
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
act(() => {
|
|
459
|
+
vi.advanceTimersByTime(300);
|
|
460
|
+
});
|
|
410
461
|
|
|
411
|
-
|
|
462
|
+
expect(dialog).not.toHaveAttribute('open');
|
|
463
|
+
} finally {
|
|
464
|
+
vi.useRealTimers();
|
|
465
|
+
}
|
|
412
466
|
});
|
|
413
467
|
|
|
414
468
|
it('closes the AppShell drawer when the backdrop is clicked', () => {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
469
|
+
vi.useFakeTimers();
|
|
470
|
+
try {
|
|
471
|
+
render(<TestShell />);
|
|
472
|
+
act(() => {
|
|
473
|
+
fireEvent.click(screen.getByRole('button', {name: /open navigation/i}));
|
|
474
|
+
});
|
|
475
|
+
const dialog = screen.getAllByRole('dialog', {hidden: true})[0];
|
|
476
|
+
expect(dialog).toHaveAttribute('open');
|
|
419
477
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
478
|
+
// A click landing on the dialog itself, not the drawer panel, is a
|
|
479
|
+
// backdrop dismiss.
|
|
480
|
+
act(() => {
|
|
481
|
+
fireEvent.click(dialog);
|
|
482
|
+
});
|
|
483
|
+
act(() => {
|
|
484
|
+
vi.advanceTimersByTime(300);
|
|
485
|
+
});
|
|
423
486
|
|
|
424
|
-
|
|
487
|
+
expect(dialog).not.toHaveAttribute('open');
|
|
488
|
+
} finally {
|
|
489
|
+
vi.useRealTimers();
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// #5701: AppShell mounts the drawer inside a React.Activity boundary that
|
|
494
|
+
// used to flip to mode="hidden" in the SAME commit as the close, which
|
|
495
|
+
// forced this file's own unmount-only safety-net effect to call
|
|
496
|
+
// dialog.close() synchronously — closing correctly, but skipping the
|
|
497
|
+
// slide-out transition entirely, because Activity's display:none landed
|
|
498
|
+
// before any transition could run. AppShell now defers that flip, so the
|
|
499
|
+
// dialog should still be open (and rendered) for a beat after closing,
|
|
500
|
+
// not synchronously cut off.
|
|
501
|
+
it('keeps the drawer open long enough for its close transition to run (#5701)', () => {
|
|
502
|
+
vi.useFakeTimers();
|
|
503
|
+
try {
|
|
504
|
+
render(<TestShell />);
|
|
505
|
+
act(() => {
|
|
506
|
+
fireEvent.click(screen.getByRole('button', {name: /open navigation/i}));
|
|
507
|
+
});
|
|
508
|
+
const dialog = screen.getAllByRole('dialog', {hidden: true})[0];
|
|
509
|
+
expect(dialog).toHaveAttribute('open');
|
|
510
|
+
|
|
511
|
+
act(() => {
|
|
512
|
+
fireEvent.click(
|
|
513
|
+
screen.getByRole('button', {name: /close navigation/i}),
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Immediately after closing, with no timers advanced: the old
|
|
518
|
+
// synchronous-hide bug closed the dialog on this exact tick. The fix
|
|
519
|
+
// means it must still be open here — Activity hasn't hidden the
|
|
520
|
+
// subtree yet, so MobileNav's own deferred close effect is still the
|
|
521
|
+
// one in control.
|
|
522
|
+
expect(dialog).toHaveAttribute('open');
|
|
523
|
+
|
|
524
|
+
// MobileNav's own close delay (capped at 250ms) runs first: about
|
|
525
|
+
// 260ms after the click the dialog has closed on its own timing while
|
|
526
|
+
// AppShell's Activity boundary (held for 250ms + 50ms) still shows the
|
|
527
|
+
// subtree. Hiding first would cut off that close (#5701).
|
|
528
|
+
act(() => {
|
|
529
|
+
vi.advanceTimersByTime(260);
|
|
530
|
+
});
|
|
531
|
+
expect(dialog).not.toHaveAttribute('open');
|
|
532
|
+
// Activity hides its subtree with an inline `display: none`; a closed
|
|
533
|
+
// dialog's own UA style does not count here.
|
|
534
|
+
expect(dialog.style.display).not.toBe('none');
|
|
535
|
+
|
|
536
|
+
// After the hold, Activity hides the subtree.
|
|
537
|
+
act(() => {
|
|
538
|
+
vi.advanceTimersByTime(60);
|
|
539
|
+
});
|
|
540
|
+
expect(dialog.style.display).toBe('none');
|
|
541
|
+
} finally {
|
|
542
|
+
vi.useRealTimers();
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
// A controlled open must be visible in the commit that carries it. The
|
|
546
|
+
// Activity boundary only defers the CLOSE; if opening waited for an effect
|
|
547
|
+
// to copy the open state, the drawer would render hidden for one commit
|
|
548
|
+
// with `isOpen` already true.
|
|
549
|
+
it('shows a controlled open in the same commit, not one effect later', () => {
|
|
550
|
+
const commits: {isOpen: boolean; isHidden: boolean}[] = [];
|
|
551
|
+
function Probe({isOpen}: {isOpen: boolean}) {
|
|
552
|
+
useLayoutEffect(() => {
|
|
553
|
+
const drawer = document.querySelector('dialog');
|
|
554
|
+
commits.push({
|
|
555
|
+
isOpen,
|
|
556
|
+
isHidden:
|
|
557
|
+
drawer != null && getComputedStyle(drawer).display === 'none',
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const shell = (isOpen: boolean) => (
|
|
563
|
+
<AppShell
|
|
564
|
+
sideNav={
|
|
565
|
+
<SideNav>
|
|
566
|
+
<SideNavSection title="Test" isHeaderHidden>
|
|
567
|
+
<SideNavItem label="Home" />
|
|
568
|
+
</SideNavSection>
|
|
569
|
+
</SideNav>
|
|
570
|
+
}
|
|
571
|
+
mobileNav={{breakpoint: 'md', isOpen, onOpenChange: () => {}}}>
|
|
572
|
+
<Probe isOpen={isOpen} />
|
|
573
|
+
<div>Content</div>
|
|
574
|
+
</AppShell>
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
const {rerender} = render(shell(false));
|
|
578
|
+
rerender(shell(true));
|
|
579
|
+
|
|
580
|
+
const openCommits = commits.filter(commit => commit.isOpen);
|
|
581
|
+
expect(openCommits.length).toBeGreaterThan(0);
|
|
582
|
+
expect(openCommits.every(commit => !commit.isHidden)).toBe(true);
|
|
425
583
|
});
|
|
426
584
|
});
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
* SYNC: When MoreMenu.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
+
import {describe, it, expect, vi, beforeAll, beforeEach} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {MoreMenu} from './MoreMenu';
|
|
16
16
|
import {DropdownMenu} from '../DropdownMenu/DropdownMenu';
|
|
17
|
+
import {warmMenuBottomSheet} from '../DropdownMenu/__tests__/warmMenuBottomSheet';
|
|
18
|
+
|
|
19
|
+
// DropdownMenu's touch sheet is loaded lazily; resolve it once up front.
|
|
20
|
+
beforeAll(warmMenuBottomSheet, 20000);
|
|
17
21
|
|
|
18
22
|
// Mock showPopover and hidePopover methods since they're not implemented in jsdom
|
|
19
23
|
beforeEach(() => {
|