@astryxdesign/core 0.7.2 → 0.7.3-canary.06872b4

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 (100) hide show
  1. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  2. package/dist/CheckboxInput/CheckboxInput.js +19 -24
  3. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  4. package/dist/DropdownMenu/DropdownMenu.js +52 -10
  5. package/dist/DropdownMenu/DropdownMenuContext.d.ts +34 -1
  6. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  7. package/dist/DropdownMenu/DropdownMenuContext.js +15 -0
  8. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +21 -2
  9. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  10. package/dist/DropdownMenu/DropdownMenuSubMenu.js +375 -42
  11. package/dist/DropdownMenu/menuRegionStops.d.ts +2 -2
  12. package/dist/DropdownMenu/menuRegionStops.js +2 -2
  13. package/dist/FileInput/FileInput.d.ts.map +1 -1
  14. package/dist/FileInput/FileInput.js +14 -12
  15. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  16. package/dist/Selector/SelectorOption.js +1 -0
  17. package/dist/Slider/Slider.d.ts.map +1 -1
  18. package/dist/Slider/Slider.js +7 -14
  19. package/dist/Switch/Switch.d.ts.map +1 -1
  20. package/dist/Switch/Switch.js +12 -14
  21. package/dist/Switch/switch.markers.stylex.d.ts +2 -0
  22. package/dist/Switch/switch.markers.stylex.d.ts.map +1 -1
  23. package/dist/Switch/switch.markers.stylex.js +6 -0
  24. package/dist/TimeInput/TimeInput.d.ts +2 -1
  25. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  26. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  27. package/dist/Tooltip/Tooltip.js +36 -14
  28. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  29. package/dist/Tooltip/useTooltip.js +11 -2
  30. package/dist/TopNav/TopNav.d.ts.map +1 -1
  31. package/dist/TopNav/TopNav.js +2 -2
  32. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  33. package/dist/TopNav/TopNavHeading.js +1 -0
  34. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  35. package/dist/Typeahead/Typeahead.js +14 -2
  36. package/dist/astryx.css +4 -2
  37. package/dist/hooks/useMenuPress.d.ts +11 -1
  38. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  39. package/dist/hooks/useMenuPress.js +43 -5
  40. package/dist/utils/timeParser.d.ts +4 -0
  41. package/dist/utils/timeParser.d.ts.map +1 -1
  42. package/dist/utils/timeParser.js +9 -2
  43. package/package.json +4 -4
  44. package/scripts/agent-doc-state.mjs +35 -8
  45. package/src/BottomSheet/BottomSheet.spec.md +13 -6
  46. package/src/BottomSheet/BottomSheetSwitcher.spec.md +121 -51
  47. package/src/CheckboxInput/CheckboxInput.test.tsx +44 -5
  48. package/src/CheckboxInput/CheckboxInput.tsx +16 -26
  49. package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +3 -3
  50. package/src/CheckboxList/CheckboxList.test.tsx +9 -6
  51. package/src/ContextMenu/ContextMenu.test.tsx +41 -0
  52. package/src/DateRangeInput/DateRangeInput.spec.md +13 -13
  53. package/src/DropdownMenu/DropdownMenu.spec.md +33 -2
  54. package/src/DropdownMenu/DropdownMenu.tsx +79 -8
  55. package/src/DropdownMenu/DropdownMenuContext.public.test.ts +22 -0
  56. package/src/DropdownMenu/DropdownMenuContext.tsx +39 -1
  57. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +16 -1
  58. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +623 -1
  59. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +556 -99
  60. package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +267 -0
  61. package/src/DropdownMenu/menuRegionStops.ts +2 -2
  62. package/src/DropdownMenu/modules/useMenuPress.spec.md +9 -0
  63. package/src/FileInput/FileInput.test.tsx +30 -7
  64. package/src/FileInput/FileInput.tsx +21 -11
  65. package/src/Selector/Selector.spec.md +11 -11
  66. package/src/Selector/Selector.test.tsx +16 -0
  67. package/src/Selector/SelectorOption.tsx +4 -0
  68. package/src/SideNav/SideNav.test.tsx +35 -0
  69. package/src/Slider/Slider.spec.md +11 -11
  70. package/src/Slider/Slider.test.tsx +3 -3
  71. package/src/Slider/Slider.tsx +8 -15
  72. package/src/Switch/Switch.test.tsx +29 -8
  73. package/src/Switch/Switch.tsx +13 -14
  74. package/src/Switch/__tests__/Switch.a11y.states.ts +2 -2
  75. package/src/Switch/switch.markers.stylex.ts +4 -0
  76. package/src/Table/Table.spec.md +2 -0
  77. package/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.spec.md +244 -0
  78. package/src/Table/plugins/selection/useTableSelection.test.tsx +5 -3
  79. package/src/Table/plugins/virtualization/useTableVirtualization.spec.md +266 -0
  80. package/src/TimeInput/TimeInput.doc.mjs +5 -3
  81. package/src/TimeInput/TimeInput.test.tsx +20 -0
  82. package/src/TimeInput/TimeInput.tsx +2 -1
  83. package/src/Tokenizer/Tokenizer.spec.md +28 -28
  84. package/src/Tooltip/Tooltip.test.tsx +51 -6
  85. package/src/Tooltip/Tooltip.tsx +41 -18
  86. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +129 -0
  87. package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +86 -0
  88. package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +132 -0
  89. package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +126 -0
  90. package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +74 -0
  91. package/src/Tooltip/useTooltip.test.tsx +23 -0
  92. package/src/Tooltip/useTooltip.tsx +22 -9
  93. package/src/TopNav/TopNav.tsx +11 -1
  94. package/src/TopNav/TopNavHeading.tsx +1 -0
  95. package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +74 -0
  96. package/src/Typeahead/Typeahead.test.tsx +89 -0
  97. package/src/Typeahead/Typeahead.tsx +15 -2
  98. package/src/hooks/useMenuPress.ts +45 -7
  99. package/src/utils/timeParser.test.ts +11 -0
  100. package/src/utils/timeParser.ts +9 -2
@@ -9,7 +9,9 @@
9
9
  * Spinner, and DropdownMenu context + item roles
10
10
  * @output Exports DropdownMenuSubMenu — a single menu row that reveals a nested
11
11
  * menu of its own children/items: a flyout beside the row, or, on a phone,
12
- * a drilled-in view in place of the parent rows.
12
+ * a drilled-in view in place of the parent rows. With a header or footer,
13
+ * that surface is a modal dialog rendered into the root's surface host,
14
+ * beside every menu element.
13
15
  * @position Sub-component; place inside a DropdownMenu (or ContextMenu)
14
16
  * alongside plain items.
15
17
  *
@@ -54,12 +56,13 @@ import * as stylex from '@stylexjs/stylex';
54
56
  import { Icon, renderIconSlot } from "../Icon/index.js";
55
57
  import { Spinner } from "../Spinner/index.js";
56
58
  import { MenuRegionsContext } from "./menuRegionsContext.js";
59
+ import { moveRegionsTab } from "./menuRegionStops.js";
57
60
  import { Item } from "../Item/index.js";
58
61
  import { useLayer } from "../Layer/useLayer.js";
59
62
  import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
60
63
  import { useListFocus } from "../hooks/useListFocus.js";
61
64
  import { useMenuHover } from "../hooks/useMenuHover.js";
62
- import { isMenuPressActivation, useMenuPress } from "../hooks/useMenuPress.js";
65
+ import { isMenuPressActivation, MENU_PRESS_SURFACE_ATTR, useMenuPress } from "../hooks/useMenuPress.js";
63
66
  import { useTypeahead } from "../hooks/useTypeahead.js";
64
67
  import { useMenuOverflow } from "./useMenuOverflow.js";
65
68
  import { resolveMenuWidth } from "./menuWidth.js";
@@ -67,11 +70,14 @@ import "../Layer/layerViewportInset.stylex.js";
67
70
  import { layerViewportInset } from "../Layer/layerViewportInset.stylex.js";
68
71
  import "../theme/tokens.stylex.js";
69
72
  import { colorVars, spacingVars, radiusVars, durationVars, easeVars, shadowVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
70
- import { composeEventHandlers, mergeProps, rtlStyles } from "../utils/index.js";
73
+ import { composeEventHandlers, isImeKeyEvent, isRenderable, mergeProps, rtlStyles } from "../utils/index.js";
74
+ import { focusOutlineStyles } from "../utils/focusOutline.stylex.js";
75
+ import { getInteractionModality } from "../utils/interactionModality.js";
71
76
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
77
+ import { useDevWarning } from "../hooks/useDevWarning.js";
72
78
  import { themeProps } from "../utils/themeProps.js";
73
79
  import { MENU_ITEM_ROLES, MENU_ITEM_SELECTOR, MENU_BOUNDARY_SELECTOR, activateMenuItem } from "./menuItemRoles.js";
74
- import { DropdownMenuContext, DropdownMenuRootOpenContext, useDropdownMenuContext, useDropdownMenuRootOpen } from "./DropdownMenuContext.js";
80
+ import { DropdownMenuContext, DropdownMenuRootOpenContext, DropdownMenuSurfaceContext, useDropdownMenuContext, useDropdownMenuRootOpen } from "./DropdownMenuContext.js";
75
81
  import { focusMenuItemOnHover } from "./menuItemHover.js";
76
82
  import { DropdownMenuItem } from "./DropdownMenuItem.js";
77
83
  import { useTranslator } from "../i18n/index.js";
@@ -129,6 +135,21 @@ const _temp = {
129
135
  "$$css": true
130
136
  };
131
137
  const flyoutStyles = {
138
+ scrollable: {
139
+ kORKVm: "x1odjw0f",
140
+ kXHlph: "x6ikm8r",
141
+ kZeWKH: "xish69e",
142
+ $$css: true
143
+ },
144
+ touchPinchZoom: {
145
+ kFalU9: "x1j4r261",
146
+ $$css: true
147
+ },
148
+ touchPanYPinchZoom: {
149
+ kFalU9: "xge0x1e",
150
+ kZeWKH: "xish69e",
151
+ $$css: true
152
+ },
132
153
  popoverViewport: {
133
154
  kB7OPa: "x9f619",
134
155
  kskxy: "x1uh1i8a",
@@ -151,6 +172,94 @@ const flyoutStyles = {
151
172
 
152
173
  // The drilled-in list shows in place of the parent rows, inside the parent's
153
174
  // own box: no surface, shadow or cap of its own.
175
+ // With a header or footer the sub-menu's surface is a frame holding the
176
+ // header, the sub-menu's own role="menu" element and the footer, in one column
177
+ // under the flyout's block cap: the regions keep their height and the rows
178
+ // shrink and scroll between them.
179
+ // A drilled-in view with a header or footer takes the root menu's place, so
180
+ // it caps where the root does when the root's `menuMaxHeight` lifts the 300px
181
+ // cap; the viewport still bounds it.
182
+ const _temp2 = {
183
+ kskxy: "x1jols5v",
184
+ "$$css": true
185
+ };
186
+ const dynamicRegionStyles = {
187
+ viewMaxHeight: maxHeight => [_temp2, {
188
+ "--x-maxHeight": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(`min(${maxHeight}, ${layerViewportInset.maxBlockSize})`)
189
+ }]
190
+ };
191
+ const regionStyles = {
192
+ frame: {
193
+ kB7OPa: "x9f619",
194
+ k1xSpc: "x78zum5",
195
+ kXwgrk: "xdt5ytf",
196
+ kskxy: "x1uh1i8a",
197
+ "--_dropdown-menu-radius": "x1fcsqxe",
198
+ "--_dropdown-menu-padding": "xgory14",
199
+ kaIpWk: "x1n97fys",
200
+ $$css: true
201
+ },
202
+ menu: {
203
+ kB7OPa: "x9f619",
204
+ k1xSpc: "x78zum5",
205
+ kXwgrk: "xdt5ytf",
206
+ kOIVth: "x1lsbc85",
207
+ kzQI83: "x1iyjqo2",
208
+ kmuXW: "xs83m0k",
209
+ kAzted: "x2lwn1j",
210
+ kmVPX3: "x9epnlk",
211
+ kI3sdo: "x1a2a7pz",
212
+ k7VYTy: "xpdipgo",
213
+ kfSwDN: "x87ps6o",
214
+ $$css: true
215
+ },
216
+ hidden: {
217
+ k1xSpc: "x1s85apg",
218
+ $$css: true
219
+ }
220
+ };
221
+ /**
222
+ * Close every open flyout with a header or footer whose row sits in `menu`.
223
+ * Such a flyout renders beside the root's menu, not inside the flyout that
224
+ * holds its row, so hiding that flyout does not hide it. Each closes through
225
+ * the popover API, as DropdownMenu closes its flyouts when it hides, so its
226
+ * layer's toggle handling resets its state and reports the close; its own
227
+ * nested flyouts close the same way, innermost first.
228
+ */
229
+ function hideRegionedFlyoutsIn(menu) {
230
+ if (menu == null) {
231
+ return;
232
+ }
233
+ const doc = menu.ownerDocument;
234
+ const rows = menu.querySelectorAll('[aria-expanded="true"][aria-controls]');
235
+ for (const row of [...rows].reverse()) {
236
+ const frame = doc.getElementById(row.getAttribute('aria-controls') ?? '');
237
+ if (frame?.getAttribute('data-astryx-submenu-regions') !== 'flyout') {
238
+ continue;
239
+ }
240
+ const flyout = frame.closest('[popover]');
241
+ if (flyout == null) {
242
+ continue;
243
+ }
244
+ if (typeof flyout.hidePopover === 'function') {
245
+ if (flyout.matches(':popover-open')) {
246
+ flyout.hidePopover();
247
+ }
248
+ } else if (flyout.style.display === 'block') {
249
+ // Without the popover API the layer toggles `display`; mirror it and
250
+ // tell its reconciler, as DropdownMenu's own sweep does.
251
+ flyout.style.display = 'none';
252
+ const toggle = new Event('toggle', {
253
+ bubbles: false
254
+ });
255
+ Object.defineProperty(toggle, 'newState', {
256
+ value: 'closed',
257
+ configurable: true
258
+ });
259
+ flyout.dispatchEvent(toggle);
260
+ }
261
+ }
262
+ }
154
263
 
155
264
  /**
156
265
  * A single menu row that reveals a nested flyout of its own items. The row
@@ -182,6 +291,8 @@ export function DropdownMenuSubMenu(props) {
182
291
  menuWidth,
183
292
  onOpenChange,
184
293
  presentation = 'adaptive',
294
+ header,
295
+ footer,
185
296
  children,
186
297
  xstyle,
187
298
  className,
@@ -200,9 +311,25 @@ export function DropdownMenuSubMenu(props) {
200
311
  } = props;
201
312
  const t = useTranslator();
202
313
  const menuCtx = useDropdownMenuContext();
314
+ const rootSurface = use(DropdownMenuSurfaceContext);
203
315
  const rootHasRegions = use(MenuRegionsContext);
204
316
  const menuSize = menuCtx?.menuSize ?? 'md';
205
317
  const canOpen = !isDisabled;
318
+ // With a header or footer the sub-menu's surface holds controls that are not
319
+ // menu items, so it is a modal dialog around the sub-menu's menu, rendered
320
+ // into the root's surface host: outside every menu element. A root that
321
+ // renders no host (no surface context, as in ContextMenu) has nowhere
322
+ // outside its menu to put them, so the sub-menu shows its rows alone there.
323
+ const wantsRegions = isRenderable(header) || isRenderable(footer);
324
+ const rootHostsSurfaces = rootSurface != null;
325
+ const hasRegions = wantsRegions && rootHostsSurfaces;
326
+ const surfaceHost = rootSurface?.host.getElement() ?? null;
327
+ useDevWarning('DropdownMenuSubMenu', 'header and footer show only inside a DropdownMenu; this menu has no place outside its rows for them, so they are not rendered.', wantsRegions && !rootHostsSurfaces);
328
+ const frameId = useId();
329
+ const frameRef = useRef(null);
330
+ const headerRef = useRef(null);
331
+ const footerRef = useRef(null);
332
+ const [isMenuKeyboardFocused, setIsMenuKeyboardFocused] = useState(false);
206
333
  const contentId = useId();
207
334
  const generatedTriggerId = useId();
208
335
  // A caller's `id` names the row; the flyout stays labelled by it.
@@ -225,17 +352,31 @@ export function DropdownMenuSubMenu(props) {
225
352
  }) : t('@astryx.dropdownMenu.back');
226
353
  const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
227
354
  const isOpen = isDrillIn ? isOnStack : isFlyoutOpen;
355
+
356
+ // Whether focus is anywhere in this flyout's frame, a level nested in it
357
+ // included: React brings a nested level's focus events back here through
358
+ // its portal, where the DOM would not.
359
+ const isFocusInFrameRef = useRef(false);
360
+ // A pointer that left while focus was inside: the flyout closes once focus
361
+ // leaves too.
362
+ const closeWhenFocusLeavesRef = useRef(false);
228
363
  const layer = useLayer({
229
364
  mode: 'context',
230
365
  lightDismiss: false,
231
366
  onShow: useCallback(() => {
367
+ closeWhenFocusLeavesRef.current = false;
232
368
  setIsFlyoutOpen(true);
233
369
  onOpenChange?.(true);
234
370
  }, [onOpenChange]),
235
371
  onHide: useCallback(() => {
372
+ closeWhenFocusLeavesRef.current = false;
373
+ // Whatever closed this flyout, a flyout with a header or footer opened
374
+ // from one of its rows closes with it: that flyout renders beside the
375
+ // root's menu, not inside this one, so hiding this one leaves it shown.
376
+ hideRegionedFlyoutsIn(triggerRef.current?.ownerDocument.getElementById(contentId) ?? null);
236
377
  setIsFlyoutOpen(false);
237
378
  onOpenChange?.(false);
238
- }, [onOpenChange])
379
+ }, [onOpenChange, contentId])
239
380
  });
240
381
  const showLayer = useCallback(() => {
241
382
  if (canOpen) {
@@ -243,8 +384,42 @@ export function DropdownMenuSubMenu(props) {
243
384
  }
244
385
  }, [canOpen, layer]);
245
386
  const hideLayer = useCallback(() => {
387
+ // A pointer leaving a flyout with a header or footer does not close it
388
+ // while focus is inside: what was typed into its field stays.
389
+ if (hasRegions && isFocusInFrameRef.current) {
390
+ closeWhenFocusLeavesRef.current = true;
391
+ return;
392
+ }
246
393
  layer.hide();
394
+ }, [layer, hasRegions]);
395
+ const handleFrameFocus = useCallback(() => {
396
+ isFocusInFrameRef.current = true;
397
+ }, []);
398
+ const handleFrameBlur = useCallback(event => {
399
+ isFocusInFrameRef.current = false;
400
+ if (!closeWhenFocusLeavesRef.current) {
401
+ return;
402
+ }
403
+ // Focus moving to another control in the frame keeps it open.
404
+ const next = event.relatedTarget;
405
+ if (next instanceof Node && event.currentTarget.contains(next)) {
406
+ return;
407
+ }
408
+ // Focus moving into a level nested in this one (React brings that
409
+ // focus event back through its portal), or moved by the browser itself
410
+ // (an arrow key in a native radio group, whose focus event follows a
411
+ // microtask checkpoint), arrives after this blur: decide in a task of
412
+ // its own, once the move has completed.
413
+ setTimeout(() => {
414
+ if (!isFocusInFrameRef.current && closeWhenFocusLeavesRef.current && triggerRef.current?.isConnected === true) {
415
+ closeWhenFocusLeavesRef.current = false;
416
+ layer.hide();
417
+ }
418
+ }, 0);
247
419
  }, [layer]);
420
+ const handleFramePointerEnter = useCallback(() => {
421
+ closeWhenFocusLeavesRef.current = false;
422
+ }, []);
248
423
 
249
424
  // Dedicated roving-focus + typeahead for this flyout level. The boundary
250
425
  // selector scopes item collection and key handling to this flyout's own
@@ -267,8 +442,9 @@ export function DropdownMenuSubMenu(props) {
267
442
  focusTrigger: true
268
443
  })
269
444
  });
270
- // A drilled-in list scrolls with the root menu, not on its own.
271
- const hasOverflow = useMenuOverflow(menuRef, children, isOpen && !isDrillIn);
445
+ // A drilled-in list scrolls with the root menu, not on its own, unless it
446
+ // has a header or footer: then its rows scroll between them.
447
+ const hasOverflow = useMenuOverflow(menuRef, children, isOpen && (!isDrillIn || hasRegions));
272
448
  const typeahead = useTypeahead({
273
449
  getItemLabels: () => getItems().map(el => el.textContent),
274
450
  onMatch: focusItem,
@@ -295,10 +471,10 @@ export function DropdownMenuSubMenu(props) {
295
471
  isOpen: isFlyoutOpen,
296
472
  isEnabled: canOpen && !isDrillIn && isRootMenuOpen,
297
473
  // The safe triangle toward the flyout's near edge is built from the
298
- // flyout this component renders, not the hook's own list ref.
299
- flyoutRef: menuRef
474
+ // flyout this component renders, not the hook's own list ref: the
475
+ // frame, with a header or footer.
476
+ flyoutRef: hasRegions ? frameRef : menuRef
300
477
  });
301
-
302
478
  // Where focus lands once a pushed view shows: the first row after Back for
303
479
  // a keyboard open, the list itself for a pointer open; and the row itself
304
480
  // once its view is popped.
@@ -465,14 +641,25 @@ export function DropdownMenuSubMenu(props) {
465
641
  // only act on our own, letting the deeper level keep ownership. No manual
466
642
  // stopPropagation needed — each level self-filters.
467
643
  const handleContentKeyDown = useCallback(e => {
468
- // In a menu with a header or footer, Tab leaves the whole flyout stack:
469
- // every open level closes as the key bubbles past it — this one's own
470
- // rows or a deeper level's — and the root menu moves focus to a region.
471
- if (e.key === 'Tab' && rootHasRegions) {
472
- close({
473
- focusTrigger: false
474
- });
475
- return;
644
+ if (e.key === 'Tab') {
645
+ // With its own header or footer this level is a dialog: Tab walks
646
+ // its regions and its rows as one stop, and wraps. A deeper level
647
+ // has closed itself as the key bubbled up, and the walk continues
648
+ // from this level's rows.
649
+ if (hasRegions) {
650
+ moveRegionsTab(e, menuRef.current, headerRef.current, footerRef.current, !ownsEvent(e));
651
+ return;
652
+ }
653
+ // In a menu with a header or footer above this level, Tab leaves the
654
+ // flyout stack: every open level closes as the key bubbles past it,
655
+ // this one's own rows or a deeper level's, and the level with the
656
+ // regions moves focus on.
657
+ if (rootHasRegions) {
658
+ close({
659
+ focusTrigger: false
660
+ });
661
+ return;
662
+ }
476
663
  }
477
664
  if (!ownsEvent(e)) {
478
665
  return;
@@ -518,7 +705,28 @@ export function DropdownMenuSubMenu(props) {
518
705
  return;
519
706
  }
520
707
  listNavKeyDown(e);
521
- }, [ownsEvent, close, listNavKeyDown, typeahead, menuRef, rootHasRegions]);
708
+ }, [ownsEvent, close, listNavKeyDown, typeahead, menuRef, rootHasRegions, hasRegions]);
709
+
710
+ // A region keeps its own keys: arrows move a field's caret and letters type.
711
+ // Tab moves on through the dialog's stops, and Escape returns to the row as
712
+ // it does from the rows.
713
+ const handleRegionKeyDown = useCallback(e => {
714
+ if (e.key === 'Tab') {
715
+ moveRegionsTab(e, menuRef.current, headerRef.current, footerRef.current);
716
+ return;
717
+ }
718
+ // An Escape that ends an IME composition belongs to the field, and one a
719
+ // control in the region already handled (a menu it opened closing, a
720
+ // field clearing itself) belongs to that control: either closes nothing
721
+ // here, so one Escape affects one surface.
722
+ if (e.key === 'Escape' && !e.defaultPrevented && !isImeKeyEvent(e.nativeEvent)) {
723
+ e.preventDefault();
724
+ e.stopPropagation();
725
+ close({
726
+ focusTrigger: true
727
+ });
728
+ }
729
+ }, [menuRef, close]);
522
730
 
523
731
  // Re-provide the menu context so nested items behave exactly like top-level
524
732
  // ones. Selecting a leaf item must dismiss the WHOLE stack, not just this
@@ -569,29 +777,108 @@ export function DropdownMenuSubMenu(props) {
569
777
  });
570
778
  const resolvedMenuWidth = menuWidth ? resolveMenuWidth(menuWidth, layerViewportInset.maxInlineSizeFallback) : null;
571
779
  const popoverXstyle = resolvedMenuWidth ? resolvedMenuWidth.property === 'inlineSize' ? flyoutStyles.popoverCustomIntrinsicWidth(resolvedMenuWidth.value) : flyoutStyles.popoverCustomWidth(resolvedMenuWidth.value) : flyoutStyles.popover;
572
- const nestedRows = /*#__PURE__*/_jsxs(DropdownMenuContext, {
780
+
781
+ // The flyout is a layer of its own, where layer-scoped contexts reset:
782
+ // nested rows get this level's menu context and the root's surface again.
783
+ const nestedRows = /*#__PURE__*/_jsx(DropdownMenuContext, {
573
784
  value: nestedMenuContext,
574
- children: [isDrillIn && /*#__PURE__*/_jsx(DropdownMenuItem, {
575
- icon: /*#__PURE__*/_jsx(Icon, {
576
- icon: "chevronLeft",
577
- size: "sm",
578
- color: "secondary",
579
- xstyle: rtlStyles.mirror
580
- }),
581
- label: backLabel,
582
- hasCloseOnSelect: false,
583
- onClick: () => close({
584
- focusTrigger: true
585
- })
586
- }), children]
785
+ children: /*#__PURE__*/_jsxs(DropdownMenuSurfaceContext, {
786
+ value: rootSurface,
787
+ children: [isDrillIn && /*#__PURE__*/_jsx(DropdownMenuItem, {
788
+ icon: /*#__PURE__*/_jsx(Icon, {
789
+ icon: "chevronLeft",
790
+ size: "sm",
791
+ color: "secondary",
792
+ xstyle: rtlStyles.mirror
793
+ }),
794
+ label: backLabel,
795
+ hasCloseOnSelect: false,
796
+ onClick: () => close({
797
+ focusTrigger: true
798
+ })
799
+ }), children]
800
+ })
587
801
  });
802
+ // A level with its own header or footer tells the levels below it, so Tab
803
+ // from a deeper flyout closes that flyout and this level walks on.
804
+ //
588
805
  // Nested rows live in this level's flyout, a layer of their own where the
589
- // layer-scoped signal resets; re-provide it so their pending hover intent
590
- // dies with the root menu too.
806
+ // layer-scoped signal resets; re-provide it, as whether every level above
807
+ // them is open, so their pending hover intent dies with the root menu or
808
+ // with this level: a hover-open that fired after this level closed would
809
+ // leave a flyout with a header or footer shown beside the root's menu.
591
810
  const nestedContent = /*#__PURE__*/_jsx(DropdownMenuRootOpenContext, {
592
- value: isRootMenuOpen,
593
- children: nestedRows
811
+ value: isRootMenuOpen && isOpen,
812
+ children: hasRegions ? /*#__PURE__*/_jsx(MenuRegionsContext, {
813
+ value: true,
814
+ children: nestedRows
815
+ }) : nestedRows
816
+ });
817
+
818
+ // The menu of a sub-menu with a header or footer renders beside the root's
819
+ // menu, outside its DOM: it names the root so the press model counts it as
820
+ // nested there, as a flyout inside the root's menu is. Only this menu does:
821
+ // a picker placed in its header or footer stays its own press root.
822
+ const surfaceOwnerProps = hasRegions && rootSurface != null ? {
823
+ [MENU_PRESS_SURFACE_ATTR]: rootSurface.ownerId
824
+ } : undefined;
825
+
826
+ // With a header or footer, the dialog that holds them and the sub-menu's
827
+ // menu. A drilled-in view is named as its list is, by the row's label; the
828
+ // flyout by the row.
829
+ const renderRegionsFrame = (menuElement, frameProps, surface) => /*#__PURE__*/_jsxs("div", {
830
+ ref: frameRef,
831
+ id: frameId,
832
+ role: "dialog",
833
+ "aria-modal": "true"
834
+ // Read by the root's step-aside rule (a shown view) and by the flyout
835
+ // that holds this row when it closes (an open flyout).
836
+ ,
837
+ "data-astryx-submenu-regions": surface,
838
+ ...frameProps,
839
+ ...(surface === 'flyout' ? {
840
+ onFocus: handleFrameFocus,
841
+ onBlur: handleFrameBlur,
842
+ onPointerEnter: handleFramePointerEnter
843
+ } : undefined),
844
+ ...(surface === 'flyout' ?
845
+ // The flyout's frame paints its surface, so the menu target rides
846
+ // on it; a drilled-in view sits inside the root's surface.
847
+ mergeProps(themeProps('dropdown-menu'), {
848
+ className: "x9f619 x78zum5 xdt5ytf x1uh1i8a x1fcsqxe xgory14 x1n97fys xc0b1yq x1prclbq x1i5ehqx x1hc1fzr x19991ni xuedmi6 xlr8y92"
849
+ }) : stylex.props(regionStyles.frame, rootSurface?.menuMaxHeight != null && dynamicRegionStyles.viewMaxHeight(rootSurface.menuMaxHeight), frameProps.hidden === true && regionStyles.hidden)),
850
+ children: [isRenderable(header) && /*#__PURE__*/_jsx("div", {
851
+ ref: headerRef,
852
+ onKeyDown: handleRegionKeyDown,
853
+ ...{
854
+ className: "x2lah0s x9epnlk"
855
+ },
856
+ children: header
857
+ }), menuElement, isRenderable(footer) && /*#__PURE__*/_jsx("div", {
858
+ ref: footerRef,
859
+ onKeyDown: handleRegionKeyDown,
860
+ ...{
861
+ className: "x2lah0s x9epnlk"
862
+ },
863
+ children: footer
864
+ })]
594
865
  });
866
+ // A flyout with a header or footer renders into the root's surface host,
867
+ // beside the root's menu rather than inside it; any other flyout renders
868
+ // in place, inside the menu that holds its row.
869
+ const renderFlyout = flyout => hasRegions && surfaceHost != null ? /*#__PURE__*/createPortal(flyout, surfaceHost) : flyout;
870
+ // Between a header and a footer the rows are one Tab stop, so keyboard focus
871
+ // on the menu element shows the ring the base style otherwise removes.
872
+ const handleMenuFocus = event => {
873
+ if (hasRegions && event.target === event.currentTarget) {
874
+ setIsMenuKeyboardFocused(getInteractionModality() === 'keyboard');
875
+ }
876
+ };
877
+ const handleMenuBlur = event => {
878
+ if (hasRegions && event.target === event.currentTarget) {
879
+ setIsMenuKeyboardFocused(false);
880
+ }
881
+ };
595
882
  return /*#__PURE__*/_jsxs(_Fragment, {
596
883
  children: [/*#__PURE__*/_jsx(Item
597
884
  // Host attributes and handlers first: the row's role, tab stop, popup
@@ -603,9 +890,9 @@ export function DropdownMenuSubMenu(props) {
603
890
  id: triggerId,
604
891
  role: "menuitem",
605
892
  tabIndex: isDisabled ? undefined : -1,
606
- "aria-haspopup": "menu",
893
+ "aria-haspopup": hasRegions ? 'dialog' : 'menu',
607
894
  "aria-expanded": isOpen,
608
- "aria-controls": isOpen ? contentId : undefined,
895
+ "aria-controls": isOpen ? hasRegions ? frameId : contentId : undefined,
609
896
  "aria-disabled": isDisabled || undefined,
610
897
  "data-testid": testId,
611
898
  onMouseEnter: composeEventHandlers(triggerProps.onMouseEnter, onMouseEnter),
@@ -628,7 +915,32 @@ export function DropdownMenuSubMenu(props) {
628
915
  className,
629
916
  style
630
917
  })
631
- }), activeDrillIn != null ? isOnStack && activeDrillIn.host != null && /*#__PURE__*/createPortal(/*#__PURE__*/_jsx("div", {
918
+ }), activeDrillIn != null ? isOnStack && activeDrillIn.host != null && (hasRegions ?
919
+ /*#__PURE__*/
920
+ // A view with a header or footer renders beside the root's menu,
921
+ // in its surface host, while the root steps its menu aside.
922
+ createPortal(renderRegionsFrame(/*#__PURE__*/_jsx("div", {
923
+ ref: menuRef,
924
+ id: contentId,
925
+ role: "menu",
926
+ tabIndex: 0,
927
+ "aria-label": stringLabel,
928
+ "aria-labelledby": stringLabel == null ? triggerId : undefined,
929
+ onKeyDown: handleContentKeyDown,
930
+ onFocus: handleMenuFocus,
931
+ onBlur: handleMenuBlur,
932
+ "data-testid": menuDataTestId,
933
+ ...menuPress.menuProps,
934
+ ...surfaceOwnerProps,
935
+ ...stylex.props(regionStyles.menu, hasOverflow && flyoutStyles.scrollable, hasOverflow ? flyoutStyles.touchPanYPinchZoom : flyoutStyles.touchPinchZoom, isMenuKeyboardFocused && focusOutlineStyles.focusVisible),
936
+ children: nestedContent
937
+ }), {
938
+ 'aria-label': stringLabel,
939
+ 'aria-labelledby': stringLabel == null ? triggerId : undefined,
940
+ // A deeper view shows in its place; this one waits, hidden.
941
+ hidden: !isTopView,
942
+ inert: !isTopView
943
+ }, 'view'), surfaceHost ?? activeDrillIn.host) : /*#__PURE__*/createPortal(/*#__PURE__*/_jsx("div", {
632
944
  ref: menuRef,
633
945
  id: contentId,
634
946
  role: "menu",
@@ -653,7 +965,28 @@ export function DropdownMenuSubMenu(props) {
653
965
  }
654
966
  }[!!!isTopView << 0],
655
967
  children: nestedContent
656
- }), activeDrillIn.host) : layer.render(/*#__PURE__*/_jsx("div", {
968
+ }), activeDrillIn.host)) : renderFlyout(layer.render(hasRegions ? renderRegionsFrame(/*#__PURE__*/_jsx("div", {
969
+ ref: menuRef,
970
+ id: contentId,
971
+ role: "menu"
972
+ // One Tab stop between the regions.
973
+ ,
974
+ tabIndex: 0,
975
+ "aria-labelledby": triggerId,
976
+ onKeyDown: handleContentKeyDown,
977
+ onFocus: handleMenuFocus,
978
+ onBlur: handleMenuBlur,
979
+ "data-testid": menuDataTestId,
980
+ ...menuPress.menuProps,
981
+ ...surfaceOwnerProps,
982
+ ...stylex.props(regionStyles.menu, hasOverflow ? flyoutStyles.touchPanYPinchZoom : flyoutStyles.touchPinchZoom, hasOverflow && flyoutStyles.scrollable, isMenuKeyboardFocused && focusOutlineStyles.focusVisible),
983
+ children: nestedContent
984
+ }), {
985
+ 'aria-labelledby': triggerId,
986
+ // Hovering the regions is hovering the flyout.
987
+ onMouseEnter: contentProps.onMouseEnter,
988
+ onMouseLeave: contentProps.onMouseLeave
989
+ }, 'flyout') : /*#__PURE__*/_jsx("div", {
657
990
  ref: menuRef,
658
991
  id: contentId,
659
992
  role: "menu"
@@ -688,7 +1021,7 @@ export function DropdownMenuSubMenu(props) {
688
1021
  alignment: 'start',
689
1022
  offset: spacingVars['--spacing-1'],
690
1023
  xstyle: [flyoutStyles.popoverViewport, popoverXstyle, layerAnimations.end]
691
- })]
1024
+ }))]
692
1025
  });
693
1026
  }
694
1027
  DropdownMenuSubMenu.displayName = 'DropdownMenuSubMenu';
@@ -4,8 +4,8 @@
4
4
  * @output `tabbablesIn`, `focusRegionControl`, `moveRegionsTab`: the Tab stops
5
5
  * of a header or footer region and the walk through a menu with regions,
6
6
  * with a named radio group as one stop
7
- * @position Internal; DropdownMenu's walk through its header and footer, in a
8
- * module of its own so another menu level with regions can share it.
7
+ * @position Internal; used by DropdownMenu (the root menu's header and footer)
8
+ * and DropdownMenuSubMenu (a sub-menu's header and footer).
9
9
  *
10
10
  * A menu with a header or footer is a dialog: Tab and Shift+Tab walk the
11
11
  * header's controls, the menu as one stop, then the footer's controls, and
@@ -8,8 +8,8 @@
8
8
  * @output `tabbablesIn`, `focusRegionControl`, `moveRegionsTab`: the Tab stops
9
9
  * of a header or footer region and the walk through a menu with regions,
10
10
  * with a named radio group as one stop
11
- * @position Internal; DropdownMenu's walk through its header and footer, in a
12
- * module of its own so another menu level with regions can share it.
11
+ * @position Internal; used by DropdownMenu (the root menu's header and footer)
12
+ * and DropdownMenuSubMenu (a sub-menu's header and footer).
13
13
  *
14
14
  * A menu with a header or footer is a dialog: Tab and Shift+Tab walk the
15
15
  * header's controls, the menu as one stop, then the footer's controls, and
@@ -1 +1 @@
1
- {"version":3,"file":"FileInput.d.ts","sourceRoot":"","sources":["../../src/FileInput/FileInput.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AASlB,YAAY,EACV,WAAW,IAAI,eAAe,EAC9B,eAAe,IAAI,mBAAmB,GACvC,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAyO9C,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,mBAAmB,EACnB,UAAU,GAAG,cAAc,GAAG,OAAO,CACtC;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAC5B;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAqB,EACrB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,UAAkB,EAClB,OAAO,EACP,QAAQ,EACR,UAAkB,EAClB,eAAe,EACf,UAAkB,EAClB,SAAiB,EACjB,MAAM,EAAE,UAAU,EAClB,aAA0B,EAC1B,WAAW,EACX,WAAW,EACX,IAAc,EACd,UAAkB,EAClB,YAAY,EACZ,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,EAAE,EAAE,MAAM,EACV,GAAG,SAAS,EACb,EAAE,cAAc,+BAochB;yBAnee,SAAS"}
1
+ {"version":3,"file":"FileInput.d.ts","sourceRoot":"","sources":["../../src/FileInput/FileInput.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AASlB,YAAY,EACV,WAAW,IAAI,eAAe,EAC9B,eAAe,IAAI,mBAAmB,GACvC,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAmP9C,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,mBAAmB,EACnB,UAAU,GAAG,cAAc,GAAG,OAAO,CACtC;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAC5B;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAqB,EACrB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,UAAkB,EAClB,OAAO,EACP,QAAQ,EACR,UAAkB,EAClB,eAAe,EACf,UAAkB,EAClB,SAAiB,EACjB,MAAM,EAAE,UAAU,EAClB,aAA0B,EAC1B,WAAW,EACX,WAAW,EACX,IAAc,EACd,UAAkB,EAClB,YAAY,EACZ,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,EAAE,EAAE,MAAM,EACV,GAAG,SAAS,EACb,EAAE,cAAc,+BAochB;yBAnee,SAAS"}
@@ -124,7 +124,7 @@ const styles = {
124
124
  dropzoneDisabled: {
125
125
  kkrTdU: "xt0e3qv",
126
126
  kSiTet: "xbyyjgo",
127
- kVAM5u: "xvy26l8",
127
+ kVAM5u: "xvy26l8 x6q1khz",
128
128
  $$css: true
129
129
  },
130
130
  compact: {
@@ -153,7 +153,7 @@ const styles = {
153
153
  compactDisabled: {
154
154
  kkrTdU: "xt0e3qv",
155
155
  kSiTet: "xbyyjgo",
156
- kVAM5u: "xvy26l8",
156
+ kVAM5u: "xvy26l8 x6q1khz",
157
157
  $$css: true
158
158
  }
159
159
  };
@@ -526,15 +526,10 @@ export function FileInput({
526
526
  children: [/*#__PURE__*/_jsx(VisuallyHidden, {
527
527
  children: /*#__PURE__*/_jsx("button", {
528
528
  type: "button"
529
- // With a disabledMessage the trigger keeps focusability via
530
- // aria-disabled so the reason is focus-discoverable; opening the
531
- // picker is still blocked by the isDisabled guards in the handlers.
532
- // Otherwise the native disabled attribute removes it from the tab
533
- // order.
529
+ // A disabled trigger keeps its Tab stop (spec:AST-085 FR1).
534
530
  ,
535
- disabled: isDisabled && !showsDisabledMessage,
536
- tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
537
- "aria-disabled": showsDisabledMessage ? 'true' : undefined,
531
+ tabIndex: 0,
532
+ "aria-disabled": isDisabled ? 'true' : undefined,
538
533
  onClick: handleClick,
539
534
  onKeyDown: handleKeyDown
540
535
  // aria-label suppresses child content from the accessible name, so
@@ -556,8 +551,15 @@ export function FileInput({
556
551
  id: id,
557
552
  type: "file",
558
553
  accept: accept,
559
- multiple: isMultiple,
560
- disabled: isDisabled,
554
+ multiple: isMultiple
555
+ // Detached from its form while disabled, so selected files are not
556
+ // submitted and `required` does not block submission (FR3).
557
+ ,
558
+ form: isDisabled ? '' : undefined
559
+ // A label or description click activates this input directly; cancel
560
+ // it so a disabled input never opens the picker (FR2).
561
+ ,
562
+ onClick: isDisabled ? event => event.preventDefault() : rest.onClick,
561
563
  onChange: handleInputChange
562
564
  // The visually-hidden native input is never focused (tabIndex={-1});
563
565
  // its describedby/required/invalid live on the trigger button.
@@ -1 +1 @@
1
- {"version":3,"file":"SelectorOption.d.ts","sourceRoot":"","sources":["../../src/Selector/SelectorOption.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AActD,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAE5B;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAE9B;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,EACN,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,GACN,EAAE,mBAAmB,+BAoBrB;yBA7Be,cAAc"}
1
+ {"version":3,"file":"SelectorOption.d.ts","sourceRoot":"","sources":["../../src/Selector/SelectorOption.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAkBtD,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAE5B;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAE9B;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,EACN,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,GACN,EAAE,mBAAmB,+BAoBrB;yBA7Be,cAAc"}