@astryxdesign/core 0.7.1 → 0.7.2-canary.151434a

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.
Files changed (251) hide show
  1. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  2. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  3. package/dist/AspectRatio/AspectRatio.js +44 -3
  4. package/dist/Button/Button.d.ts.map +1 -1
  5. package/dist/Button/Button.js +38 -14
  6. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  7. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  8. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  9. package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
  10. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  11. package/dist/CheckboxList/CheckboxListItem.js +4 -2
  12. package/dist/Dialog/Dialog.d.ts.map +1 -1
  13. package/dist/Dialog/Dialog.js +20 -8
  14. package/dist/Drawer/Drawer.d.ts +191 -0
  15. package/dist/Drawer/Drawer.d.ts.map +1 -0
  16. package/dist/Drawer/Drawer.js +382 -0
  17. package/dist/Drawer/DrawerHeader.d.ts +85 -0
  18. package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
  19. package/dist/Drawer/DrawerHeader.js +134 -0
  20. package/dist/Drawer/index.d.ts +11 -0
  21. package/dist/Drawer/index.d.ts.map +1 -0
  22. package/dist/Drawer/index.js +11 -0
  23. package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
  24. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
  25. package/dist/Drawer/useDrawerDialogPresence.js +236 -0
  26. package/dist/DropdownMenu/DropdownMenu.js +1 -1
  27. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  28. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  29. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +4 -4
  31. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  32. package/dist/EmptyState/EmptyState.js +4 -10
  33. package/dist/Field/Field.d.ts +1 -1
  34. package/dist/Field/Field.d.ts.map +1 -1
  35. package/dist/Field/Field.js +8 -4
  36. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  37. package/dist/HoverCard/useHoverCard.js +10 -2
  38. package/dist/Item/Item.d.ts +6 -2
  39. package/dist/Item/Item.d.ts.map +1 -1
  40. package/dist/Item/Item.js +19 -4
  41. package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
  42. package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
  43. package/dist/Layer/layerViewportInset.stylex.js +6 -2
  44. package/dist/Layer/useLayer.d.ts.map +1 -1
  45. package/dist/Layer/useLayer.js +13 -7
  46. package/dist/Layer/useLayerDismissal.d.ts +1 -1
  47. package/dist/Layer/useLayerDismissal.js +1 -1
  48. package/dist/Layout/container.stylex.d.ts +5 -5
  49. package/dist/Layout/container.stylex.js +4 -4
  50. package/dist/List/List.d.ts +46 -19
  51. package/dist/List/List.d.ts.map +1 -1
  52. package/dist/List/List.js +28 -10
  53. package/dist/List/ListContext.d.ts +8 -3
  54. package/dist/List/ListContext.d.ts.map +1 -1
  55. package/dist/List/ListContext.js +7 -2
  56. package/dist/List/ListItem.d.ts +13 -5
  57. package/dist/List/ListItem.d.ts.map +1 -1
  58. package/dist/List/ListItem.js +21 -8
  59. package/dist/List/index.d.ts +1 -1
  60. package/dist/List/index.d.ts.map +1 -1
  61. package/dist/Markdown/Markdown.d.ts.map +1 -1
  62. package/dist/Markdown/Markdown.js +8 -1
  63. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
  64. package/dist/Markdown/plugins/frontmatter.js +7 -3
  65. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  66. package/dist/MetadataList/MetadataList.js +5 -1
  67. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  68. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  69. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  70. package/dist/MultiSelector/MultiSelector.js +22 -8
  71. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
  72. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  73. package/dist/MultiSelector/hooks.js +3 -1
  74. package/dist/Pagination/Pagination.d.ts +8 -5
  75. package/dist/Pagination/Pagination.d.ts.map +1 -1
  76. package/dist/Pagination/Pagination.js +21 -8
  77. package/dist/Popover/Popover.js +2 -2
  78. package/dist/PowerSearch/PowerSearch.js +1 -1
  79. package/dist/RadioList/RadioListItem.d.ts +9 -1
  80. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  81. package/dist/RadioList/RadioListItem.js +2 -0
  82. package/dist/Selector/hooks.d.ts.map +1 -1
  83. package/dist/Selector/hooks.js +6 -2
  84. package/dist/Switch/Switch.d.ts +1 -1
  85. package/dist/Switch/Switch.d.ts.map +1 -1
  86. package/dist/Switch/Switch.js +13 -1
  87. package/dist/TabList/TabMenu.d.ts +2 -2
  88. package/dist/TabList/TabMenu.d.ts.map +1 -1
  89. package/dist/TabList/TabMenu.js +6 -3
  90. package/dist/Text/useTruncation.d.ts +7 -5
  91. package/dist/Text/useTruncation.d.ts.map +1 -1
  92. package/dist/Text/useTruncation.js +16 -5
  93. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  94. package/dist/Tooltip/useTooltip.js +11 -0
  95. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  96. package/dist/Typeahead/BaseTypeahead.js +26 -0
  97. package/dist/astryx.css +35 -12
  98. package/dist/hooks/useEntryAnimation.d.ts +2 -1
  99. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  100. package/dist/hooks/useEntryAnimation.js +14 -12
  101. package/dist/hooks/useIsHydrating.d.ts +6 -0
  102. package/dist/hooks/useIsHydrating.d.ts.map +1 -0
  103. package/dist/hooks/useIsHydrating.js +30 -0
  104. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  105. package/dist/i18n/generated-locales/en.generated.js +112 -1
  106. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  107. package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +1 -0
  111. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  112. package/dist/theme/derivedVarRegistry.js +0 -3
  113. package/dist/utils/focusOutline.stylex.d.ts +4 -0
  114. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  115. package/dist/utils/focusOutline.stylex.js +4 -0
  116. package/dist/utils/plainDate.d.ts.map +1 -1
  117. package/dist/utils/plainDate.js +15 -9
  118. package/dist/utils/sharedMutationObserver.d.ts +21 -0
  119. package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
  120. package/dist/utils/sharedMutationObserver.js +69 -0
  121. package/locales/en.json +444 -0
  122. package/locales/pseudo.json +333 -0
  123. package/package.json +9 -4
  124. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  125. package/src/AspectRatio/AspectRatio.tsx +38 -18
  126. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  127. package/src/Button/Button.doc.mjs +2 -2
  128. package/src/Button/Button.test.tsx +155 -9
  129. package/src/Button/Button.tsx +39 -16
  130. package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
  131. package/src/Button/__tests__/Button.a11y.states.ts +0 -10
  132. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  133. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  134. package/src/CheckboxList/CheckboxList.test.tsx +22 -0
  135. package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
  136. package/src/CheckboxList/CheckboxListItem.tsx +10 -0
  137. package/src/Dialog/Dialog.test.tsx +46 -0
  138. package/src/Dialog/Dialog.tsx +20 -7
  139. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  140. package/src/Drawer/Drawer.doc.mjs +472 -0
  141. package/src/Drawer/Drawer.spec.md +357 -0
  142. package/src/Drawer/Drawer.test.tsx +1300 -0
  143. package/src/Drawer/Drawer.tsx +613 -0
  144. package/src/Drawer/DrawerDirection.test.tsx +127 -0
  145. package/src/Drawer/DrawerHeader.doc.mjs +293 -0
  146. package/src/Drawer/DrawerHeader.test.tsx +102 -0
  147. package/src/Drawer/DrawerHeader.tsx +235 -0
  148. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
  149. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
  150. package/src/Drawer/index.ts +13 -0
  151. package/src/Drawer/useDrawerDialogPresence.ts +284 -0
  152. package/src/DropdownMenu/DropdownMenu.test.tsx +21 -12
  153. package/src/DropdownMenu/DropdownMenu.tsx +2 -2
  154. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  155. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  156. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +2 -2
  157. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
  158. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
  159. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
  160. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
  161. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
  162. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  163. package/src/EmptyState/EmptyState.spec.md +189 -0
  164. package/src/EmptyState/EmptyState.test.tsx +74 -3
  165. package/src/EmptyState/EmptyState.tsx +4 -8
  166. package/src/Field/Field.doc.mjs +14 -8
  167. package/src/Field/Field.spec.md +217 -0
  168. package/src/Field/Field.test.tsx +60 -2
  169. package/src/Field/Field.tsx +17 -8
  170. package/src/HoverCard/HoverCard.test.tsx +58 -0
  171. package/src/HoverCard/useHoverCard.tsx +10 -2
  172. package/src/IconButton/IconButton.doc.mjs +2 -2
  173. package/src/IconButton/IconButton.test.tsx +3 -1
  174. package/src/Item/Item.doc.mjs +12 -8
  175. package/src/Item/Item.spec.md +226 -0
  176. package/src/Item/Item.test.tsx +61 -0
  177. package/src/Item/Item.tsx +40 -4
  178. package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
  179. package/src/Layer/layerViewportInset.stylex.ts +6 -2
  180. package/src/Layer/layerViewportInset.test.ts +1 -0
  181. package/src/Layer/useLayer.tsx +24 -13
  182. package/src/Layer/useLayerDismissal.ts +1 -1
  183. package/src/Layout/container.stylex.ts +6 -6
  184. package/src/List/List.doc.mjs +14 -1
  185. package/src/List/List.spec.md +129 -77
  186. package/src/List/List.test.tsx +163 -94
  187. package/src/List/List.tsx +74 -18
  188. package/src/List/ListContext.tsx +9 -3
  189. package/src/List/ListItem.doc.mjs +24 -5
  190. package/src/List/ListItem.tsx +56 -27
  191. package/src/List/index.ts +1 -1
  192. package/src/Markdown/Markdown.tsx +14 -1
  193. package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
  194. package/src/Markdown/plugins/frontmatter.ts +7 -3
  195. package/src/MetadataList/MetadataList.doc.mjs +2 -2
  196. package/src/MetadataList/MetadataList.test.tsx +38 -0
  197. package/src/MetadataList/MetadataList.tsx +8 -1
  198. package/src/MetadataList/MetadataListContext.tsx +1 -1
  199. package/src/MultiSelector/MultiSelector.test.tsx +159 -0
  200. package/src/MultiSelector/MultiSelector.tsx +40 -7
  201. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
  202. package/src/MultiSelector/hooks.ts +3 -1
  203. package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
  204. package/src/Pagination/Pagination.doc.mjs +7 -7
  205. package/src/Pagination/Pagination.spec.md +9 -9
  206. package/src/Pagination/Pagination.test.tsx +242 -0
  207. package/src/Pagination/Pagination.tsx +27 -11
  208. package/src/Popover/Popover.test.tsx +2 -2
  209. package/src/Popover/Popover.tsx +4 -4
  210. package/src/PowerSearch/PowerSearch.test.tsx +85 -0
  211. package/src/PowerSearch/PowerSearch.tsx +2 -2
  212. package/src/RadioList/RadioList.test.tsx +22 -0
  213. package/src/RadioList/RadioListItem.doc.mjs +16 -0
  214. package/src/RadioList/RadioListItem.tsx +10 -1
  215. package/src/Selector/hooks.ts +6 -2
  216. package/src/Selector/useCombobox.doc.mjs +237 -0
  217. package/src/Switch/Switch.test.tsx +49 -0
  218. package/src/Switch/Switch.tsx +11 -0
  219. package/src/TabList/TabList.spec.md +57 -44
  220. package/src/TabList/TabList.test.tsx +67 -0
  221. package/src/TabList/TabMenu.doc.mjs +4 -3
  222. package/src/TabList/TabMenu.tsx +7 -4
  223. package/src/Text/useTruncation.test.tsx +69 -0
  224. package/src/Text/useTruncation.ts +16 -5
  225. package/src/Tokenizer/Tokenizer.test.tsx +54 -1
  226. package/src/Tooltip/Tooltip.test.tsx +31 -0
  227. package/src/Tooltip/useTooltip.test.tsx +13 -0
  228. package/src/Tooltip/useTooltip.tsx +11 -0
  229. package/src/TreeList/TreeList.spec.md +29 -17
  230. package/src/Typeahead/BaseTypeahead.tsx +26 -0
  231. package/src/Typeahead/Typeahead.test.tsx +260 -2
  232. package/src/hooks/useEntryAnimation.doc.mjs +4 -4
  233. package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
  234. package/src/hooks/useEntryAnimation.ts +15 -16
  235. package/src/hooks/useIsHydrating.test.tsx +70 -0
  236. package/src/hooks/useIsHydrating.ts +31 -0
  237. package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
  238. package/src/i18n/InternationalizationProvider.spec.md +252 -0
  239. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
  240. package/src/i18n/generated-locales/en.generated.ts +121 -0
  241. package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
  242. package/src/i18n/useTranslator.test.tsx +37 -0
  243. package/src/index.ts +1 -0
  244. package/src/reset.css +0 -29
  245. package/src/theme/derivedVarRegistry.test.ts +3 -3
  246. package/src/theme/derivedVarRegistry.ts +0 -3
  247. package/src/utils/focusOutline.stylex.ts +4 -0
  248. package/src/utils/plainDate.test.ts +37 -1
  249. package/src/utils/plainDate.ts +15 -5
  250. package/src/utils/sharedMutationObserver.test.ts +53 -0
  251. package/src/utils/sharedMutationObserver.ts +78 -0
@@ -59,6 +59,28 @@ beforeEach(() => {
59
59
  });
60
60
 
61
61
  describe('CheckboxList', () => {
62
+ it('forwards CheckboxListItem align to its Item row unchanged', () => {
63
+ render(
64
+ <CheckboxList label="Toppings" value={[]} onChange={() => {}}>
65
+ <CheckboxListItem label="Cheese" value="cheese" data-testid="cheese" />
66
+ <CheckboxListItem
67
+ label="Olives"
68
+ value="olives"
69
+ align="first-line"
70
+ data-testid="olives"
71
+ />
72
+ </CheckboxList>,
73
+ );
74
+ expect(screen.getByTestId('cheese')).toHaveAttribute(
75
+ 'data-align',
76
+ 'center',
77
+ );
78
+ expect(screen.getByTestId('olives')).toHaveAttribute(
79
+ 'data-align',
80
+ 'first-line',
81
+ );
82
+ });
83
+
62
84
  it('renders with label', () => {
63
85
  render(
64
86
  <CheckboxList label="Preferences" value={[]} onChange={() => {}}>
@@ -55,6 +55,13 @@ export const docs = {
55
55
  },
56
56
  ],
57
57
  },
58
+ {
59
+ name: 'align',
60
+ type: "'center' | 'start' | 'first-line'",
61
+ description:
62
+ 'Vertical alignment of the start and end content against the label, passed through to Item unchanged. `first-line` keeps a small mark (a checkbox) level with the first line of a wrapped label. See Item.align.',
63
+ default: "'center'",
64
+ },
58
65
  {
59
66
  name: 'isDisabled',
60
67
  type: 'boolean',
@@ -130,6 +137,13 @@ export const docsZh = {
130
137
  type: 'ReactNode',
131
138
  description: '在标签区域后渲染的内容。',
132
139
  },
140
+ {
141
+ name: 'align',
142
+ type: "'center' | 'start' | 'first-line'",
143
+ description:
144
+ '起始和结束内容相对于标签的垂直对齐方式,原样传递给 Item。`first-line` 让小标记(复选框)与换行标签的第一行保持水平。参见 Item.align。',
145
+ default: "'center'",
146
+ },
133
147
  {
134
148
  name: 'isDisabled',
135
149
  type: 'boolean',
@@ -165,6 +179,8 @@ export const docsDense = {
165
179
  description:
166
180
  'Individual checkbox item w/ label, description, end content slot. Collection mode inside CheckboxList with value; standalone inside List or CheckboxList without value.',
167
181
  propDescriptions: {
182
+ align:
183
+ "Start/end content alignment, passed to Item: 'center' (default), 'start', or 'first-line' (centered on the label's first line).",
168
184
  label:
169
185
  'Primary label. String or ReactNode; nested controls keep their behavior. A ReactNode names the checkbox from its visible text.',
170
186
  'aria-label':
@@ -29,6 +29,7 @@ import {ListContext} from '../List/ListContext';
29
29
  import {CheckboxListContext} from './CheckboxListContext';
30
30
  import {useTranslator} from '../i18n';
31
31
  import {ItemDescriptionContext} from '../Item/ItemDescriptionContext';
32
+ import type {ItemAlign} from '../Item';
32
33
 
33
34
  // =============================================================================
34
35
  // Styles
@@ -45,6 +46,13 @@ const styles = stylex.create({
45
46
  // =============================================================================
46
47
 
47
48
  export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
49
+ /**
50
+ * Vertical alignment of the start and end content against the label,
51
+ * forwarded to Item. Use `first-line` to keep the checkbox level with the
52
+ * first line of a wrapped label.
53
+ * @default 'center'
54
+ */
55
+ align?: ItemAlign;
48
56
  /**
49
57
  * Primary text label for the item.
50
58
  *
@@ -192,6 +200,7 @@ export function CheckboxListItem({
192
200
  className,
193
201
  style,
194
202
  onClick: onClickProp,
203
+ align,
195
204
  ...restProps
196
205
  }: CheckboxListItemProps) {
197
206
  const t = useTranslator();
@@ -297,6 +306,7 @@ export function CheckboxListItem({
297
306
  label={namesFromVisibleLabel ? <span id={labelID}>{label}</span> : label}
298
307
  description={description}
299
308
  endContent={endContent}
309
+ align={align}
300
310
  isDisabled={effectiveDisabled}
301
311
  // Delegate row clicks to the checkbox instead of wiring onClick (which
302
312
  // would add an invisible row button = a second tab stop). The checkbox
@@ -110,6 +110,52 @@ beforeEach(() => {
110
110
  });
111
111
 
112
112
  describe('Dialog', () => {
113
+ it('restores focus to the opener when content mounts in the open commit', () => {
114
+ function Harness({
115
+ open,
116
+ onOpenChange,
117
+ }: {
118
+ open: boolean;
119
+ onOpenChange: (v: boolean) => void;
120
+ }) {
121
+ return (
122
+ <>
123
+ <button type="button" onClick={() => onOpenChange(true)}>
124
+ Open
125
+ </button>
126
+ <Dialog isOpen={open} onOpenChange={onOpenChange}>
127
+ {open ? (
128
+ <div>
129
+ <DialogHeader title="Review" />
130
+ <p>Body</p>
131
+ </div>
132
+ ) : null}
133
+ </Dialog>
134
+ </>
135
+ );
136
+ }
137
+
138
+ let open = false;
139
+ const setOpen = (v: boolean) => {
140
+ open = v;
141
+ };
142
+ const harness = render(<Harness open={open} onOpenChange={setOpen} />);
143
+ const opener = screen.getByRole('button', {name: 'Open'});
144
+ opener.focus();
145
+
146
+ fireEvent.click(opener);
147
+ harness.rerender(<Harness open={open} onOpenChange={setOpen} />);
148
+
149
+ // The freshly mounted header title is the focused screen reader anchor
150
+ // while the dialog is open...
151
+ expect(document.activeElement).toBe(screen.getByText('Review'));
152
+
153
+ // ...but closing restores the element that opened the dialog, not the
154
+ // (now unmounted) title.
155
+ harness.rerender(<Harness open={false} onOpenChange={setOpen} />);
156
+ expect(document.activeElement).toBe(opener);
157
+ });
158
+
113
159
  it('renders when isOpen is true', () => {
114
160
  render(
115
161
  <Dialog isOpen={true} onOpenChange={() => {}}>
@@ -32,6 +32,7 @@ import {
32
32
  useCallback,
33
33
  useEffect,
34
34
  useId,
35
+ useLayoutEffect,
35
36
  useMemo,
36
37
  useRef,
37
38
  type ReactNode,
@@ -505,12 +506,26 @@ export function Dialog({
505
506
 
506
507
  const mergedDialogRef = useMergedRefs(ref, attachDialog);
507
508
 
508
- // Capture the element that was focused when the dialog opened,
509
- // for directional animation origin and focus restoration on close.
509
+ // Capture the element that was focused when the dialog opened, for
510
+ // directional animation origin and focus restoration on close.
510
511
  const triggerElementRef = useRef<HTMLElement | null>(null);
511
512
 
512
513
  useInteractionModalityTracking();
513
514
 
515
+ // This runs in a layout effect so the capture lands before any descendant's
516
+ // passive mount effect: all layout effects run before any passive effect,
517
+ // while a child's passive effect runs before this parent's passive effect.
518
+ // Content that mounts in the open commit — e.g. a DialogHeader focusing its
519
+ // own title on mount — would otherwise move document.activeElement inside
520
+ // the dialog before the capture ran, and closing would then restore focus
521
+ // to a detached node, stranding the user's focus on <body>.
522
+ useLayoutEffect(() => {
523
+ if (isInline || !isOpen) {
524
+ return;
525
+ }
526
+ triggerElementRef.current = document.activeElement as HTMLElement | null;
527
+ }, [isOpen, isInline]);
528
+
514
529
  // Derive dismissal behavior from purpose
515
530
  const allowEscape = purpose !== 'required';
516
531
  const allowBackdropClick = purpose === 'info';
@@ -526,11 +541,9 @@ export function Dialog({
526
541
  }
527
542
 
528
543
  if (isOpen) {
529
- // Capture the currently focused element as the trigger — used for
530
- // directional animation origin and focus restoration on close.
531
- triggerElementRef.current = document.activeElement as HTMLElement | null;
532
-
533
- // Set directional CSS custom properties before opening
544
+ // Set directional CSS custom properties before opening. The trigger was
545
+ // captured by the layout effect above, before any descendant effect
546
+ // could move focus.
534
547
  const trigger = triggerElementRef.current;
535
548
  if (trigger && trigger !== document.body) {
536
549
  const dir = getDialogDirection(trigger);
@@ -25,18 +25,4 @@ export const DIALOG_MODAL_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
25
25
  reason:
26
26
  'This migration records the existing focus-order violation without changing the overlapping Dialog implementation path.',
27
27
  },
28
- {
29
- expectation: 'modal-dialog.focus.restored',
30
- binding: 'Dialog',
31
- state: 'conditional-content-focus-restoration',
32
- evidenceLayer: 'real-browser',
33
- failureEquals:
34
- 'closing the modal dialog did not return focus to its still-available invoker',
35
- standardsReference:
36
- 'Astryx component:Dialog FR4 — closing a native Dialog must return focus to its still-connected invoker when it can receive focus.',
37
- userImpact:
38
- 'When focus-moving content mounts only while the dialog is open, closing can leave the user at the document body instead of the control that opened the task.',
39
- reason:
40
- 'This migration records the existing restoration violation without changing the overlapping Dialog implementation path.',
41
- },
42
28
  ];