@astryxdesign/core 0.7.1-canary.df75749 → 0.7.1-canary.e71c4e9

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 (41) hide show
  1. package/dist/CommandPalette/CommandPalette.d.ts +6 -4
  2. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  3. package/dist/CommandPalette/CommandPalette.js +38 -20
  4. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  5. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  6. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  7. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  8. package/dist/CommandPalette/CommandPaletteInput.js +11 -5
  9. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  10. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  11. package/dist/CommandPalette/CommandPaletteList.js +9 -4
  12. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  13. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  14. package/dist/ContextMenu/ContextMenu.js +62 -48
  15. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  16. package/dist/Layer/useLayerDismissal.js +9 -9
  17. package/dist/utils/plainDate.d.ts.map +1 -1
  18. package/dist/utils/plainDate.js +14 -5
  19. package/locales/en.json +1 -1
  20. package/package.json +3 -3
  21. package/src/CommandPalette/CommandPalette.doc.mjs +31 -1
  22. package/src/CommandPalette/CommandPalette.spec.md +95 -46
  23. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  24. package/src/CommandPalette/CommandPalette.tsx +44 -20
  25. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  26. package/src/CommandPalette/CommandPaletteInput.spec.md +7 -4
  27. package/src/CommandPalette/CommandPaletteInput.test.tsx +63 -2
  28. package/src/CommandPalette/CommandPaletteInput.tsx +15 -5
  29. package/src/CommandPalette/CommandPaletteItem.test.tsx +7 -0
  30. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  31. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  32. package/src/CommandPalette/CommandPaletteList.test.tsx +67 -0
  33. package/src/CommandPalette/CommandPaletteList.tsx +9 -4
  34. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  35. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  36. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  37. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  38. package/src/ContextMenu/ContextMenu.tsx +78 -48
  39. package/src/Layer/useLayerDismissal.ts +9 -9
  40. package/src/utils/plainDate.test.ts +99 -0
  41. package/src/utils/plainDate.ts +23 -5
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @file CommandPalette.tsx
3
- * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox, useAnnounce
3
+ * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
4
+ * useCombobox, useAnnounce, useAnnounceRenderedText
4
5
  * @output Exports CommandPalette root component and props
5
6
  * @position Core root component; dialog shell with searchSource-driven items
6
7
  *
@@ -84,9 +85,10 @@ export interface CommandPaletteProps<T extends SearchableItem = SearchableItem>
84
85
  * Uses `searchSource` for all search logic — same interface as Typeahead.
85
86
  * For static lists, use `createStaticSource` from `@astryxdesign/core/Typeahead`.
86
87
  *
87
- * Keyboard navigation is handled by `useCombobox` from Selector,
88
- * ensuring consistent arrow key, Home/End, Enter, and Escape behavior
89
- * across all combobox-pattern components.
88
+ * Keyboard navigation is handled by `useCombobox` from Selector for arrows,
89
+ * PageUp/PageDown, and Enter. Home/End remain native caret keys. Modal Escape
90
+ * dismissal delegates to Dialog's shared layer stack so nested surfaces handle
91
+ * the first press.
90
92
  *
91
93
  * Input and footer are rendered by default — only pass them to replace the defaults.
92
94
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAU/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAI5C,MAAM,WAAW,mBAAmB,CAClC,CAAC,SAAS,cAAc,GAAG,cAAc,CACzC,SAAQ,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACtD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAE9B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,KAAK,SAAS,CAAC;IAEzD;;;OAGG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,CAAC;IAE/B,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8CAA8C;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAiID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,EACxE,GAAG,EACH,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,eAAe,EAAE,wBAAwB,EACzC,kBAAkB,EAAE,2BAA2B,EAC/C,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,KAAK,EAAE,cAAc,EACrB,KAAW,EACX,SAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BAsXxB;yBAvYe,cAAc"}
1
+ {"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAU/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,mBAAmB,CAClC,CAAC,SAAS,cAAc,GAAG,cAAc,CACzC,SAAQ,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACtD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAE9B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,KAAK,SAAS,CAAC;IAEzD;;;OAGG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,CAAC;IAE/B,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8CAA8C;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAiID;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,EACxE,GAAG,EACH,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,eAAe,EAAE,wBAAwB,EACzC,kBAAkB,EAAE,2BAA2B,EAC/C,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,KAAK,EAAE,cAAc,EACrB,KAAW,EACX,SAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BA2YxB;yBA5Ze,cAAc"}
@@ -4,7 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file CommandPalette.tsx
7
- * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox, useAnnounce
7
+ * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
8
+ * useCombobox, useAnnounce, useAnnounceRenderedText
8
9
  * @output Exports CommandPalette root component and props
9
10
  * @position Core root component; dialog shell with searchSource-driven items
10
11
  *
@@ -24,6 +25,7 @@ import { CommandPaletteInput } from "./CommandPaletteInput.js";
24
25
  import { CommandPaletteFooter } from "./CommandPaletteFooter.js";
25
26
  import { CommandPaletteEmpty } from "./CommandPaletteEmpty.js";
26
27
  import { useAnnounce } from "../hooks/useAnnounce.js";
28
+ import { useAnnounceRenderedText } from "../hooks/useAnnounceRenderedText.js";
27
29
  import { useTranslator } from "../i18n/index.js";
28
30
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
29
31
  function getGroup(item) {
@@ -145,9 +147,10 @@ function ItemRenderer({
145
147
  * Uses `searchSource` for all search logic — same interface as Typeahead.
146
148
  * For static lists, use `createStaticSource` from `@astryxdesign/core/Typeahead`.
147
149
  *
148
- * Keyboard navigation is handled by `useCombobox` from Selector,
149
- * ensuring consistent arrow key, Home/End, Enter, and Escape behavior
150
- * across all combobox-pattern components.
150
+ * Keyboard navigation is handled by `useCombobox` from Selector for arrows,
151
+ * PageUp/PageDown, and Enter. Home/End remain native caret keys. Modal Escape
152
+ * dismissal delegates to Dialog's shared layer stack so nested surfaces handle
153
+ * the first press.
151
154
  *
152
155
  * Input and footer are rendered by default — only pass them to replace the defaults.
153
156
  *
@@ -200,6 +203,7 @@ export function CommandPalette({
200
203
  const [optimisticResults, setOptimisticResults] = useOptimistic(searchResults);
201
204
  const isBusy = isPending;
202
205
  const searchVersionRef = useRef(0);
206
+ const emptyStateRef = useRef(null);
203
207
 
204
208
  // Announce search status to screen readers through the shared polite live
205
209
  // region (comboboxes-7 announce path, mirroring Selector / BaseTypeahead).
@@ -267,6 +271,10 @@ export function CommandPalette({
267
271
  const runSearch = useCallback(query => {
268
272
  searchSource.cancel?.();
269
273
  const version = ++searchVersionRef.current;
274
+ // A highlight belongs to the query that created it. Clear it before any
275
+ // optimistic narrowing so aria-activedescendant never shifts to a
276
+ // different command at the same index while the next result set loads.
277
+ combobox.setHighlightedIndex(-1);
270
278
  startTransition(async () => {
271
279
  const isBootstrap = query === '';
272
280
 
@@ -293,18 +301,12 @@ export function CommandPalette({
293
301
 
294
302
  // Announce the outcome from the search commit (not a reactive
295
303
  // effect), matching Selector / BaseTypeahead: exactly one
296
- // announcement per committed query, and the version check above
297
- // already discards stale keystrokes. Bootstrap stays silent — the
298
- // same role PowerSearch's mount guard plays — so opening the
299
- // palette announces nothing; clearing the query only clears any
300
- // lingering status text.
304
+ // announcement per committed non-empty query. Empty results are
305
+ // announced from their rendered content below so a caller-supplied
306
+ // ReactNode reaches assistive technology as written.
301
307
  if (isBootstrap) {
302
308
  announce('');
303
- } else if (items.length === 0) {
304
- announce(t('@astryx.commandPalette.noResultsFor', {
305
- query
306
- }));
307
- } else {
309
+ } else if (items.length > 0) {
308
310
  announce(t('@astryx.commandPalette.resultCount', {
309
311
  count: items.length
310
312
  }));
@@ -346,13 +348,18 @@ export function CommandPalette({
346
348
  }
347
349
  }, [isOpen]);
348
350
 
349
- // Wrap combobox's onKeyDown to intercept Escape (close palette) and
350
- // Enter on highlight (select + close), since we're not using combobox's
351
- // built-in open/close lifecycle.
351
+ // Wrap combobox's onKeyDown to preserve inline-preview Escape handling and
352
+ // select + close on Enter. Modal Escape stays unclaimed here so Dialog's
353
+ // shared dismissal stack can route it to the topmost surface.
352
354
  const handleKeyDown = useCallback(e => {
353
355
  if (e.key === 'Escape') {
354
- e.preventDefault();
355
- handleClose();
356
+ // Modal CommandPalette delegates Escape to Dialog's shared dismissal
357
+ // stack so a nested layer handles the first press. Inline previews do
358
+ // not join that stack, so preserve their local close behavior.
359
+ if (isInline) {
360
+ e.preventDefault();
361
+ handleClose();
362
+ }
356
363
  return;
357
364
  }
358
365
  if (e.key === 'Enter') {
@@ -366,12 +373,17 @@ export function CommandPalette({
366
373
  }
367
374
  return;
368
375
  }
376
+ // Home and End remain native caret-movement keys for this editable
377
+ // combobox. PageUp/PageDown provide first/last result navigation.
378
+ if (e.key === 'Home' || e.key === 'End') {
379
+ return;
380
+ }
369
381
  // Space should type in the input, not trigger selection
370
382
  if (e.key === ' ') {
371
383
  return;
372
384
  }
373
385
  combobox.onKeyDown(e);
374
- }, [combobox, handleClose, selectableItems, selectItem]);
386
+ }, [combobox, handleClose, isInline, selectableItems, selectItem]);
375
387
 
376
388
  // Hover highlight is owned here by a single delegated handler on the list
377
389
  // container: it routes to useCombobox's hover-aware path, which moves the
@@ -427,13 +439,19 @@ export function CommandPalette({
427
439
  // the empty state is never unmounted and re-added mid-search (which flashed).
428
440
  const showEmptyBootstrap = search === '' && optimisticResults.length === 0;
429
441
  const showEmptySearch = search !== '' && optimisticResults.length === 0;
442
+ // Announce only once the search settles: while it is pending, optimistic
443
+ // narrowing can empty the list before the source answers, which would
444
+ // speak "No results" early or wrongly.
445
+ useAnnounceRenderedText(emptyStateRef, showEmptySearch && !isPending, search);
430
446
  let listContent;
431
447
  if (showEmptyBootstrap) {
432
448
  listContent = /*#__PURE__*/_jsx(CommandPaletteEmpty, {
449
+ ref: emptyStateRef,
433
450
  children: emptyBootstrapText
434
451
  });
435
452
  } else if (showEmptySearch) {
436
453
  listContent = /*#__PURE__*/_jsx(CommandPaletteEmpty, {
454
+ ref: emptyStateRef,
437
455
  children: emptySearchText
438
456
  });
439
457
  } else {
@@ -12,13 +12,14 @@ export interface CommandPaletteEmptyProps extends BaseProps<HTMLDivElement> {
12
12
  * - `emptyBootstrapText`: no search term and bootstrap() returns nothing
13
13
  * - `emptySearchText`: a search query returned no results
14
14
  *
15
- * Can also be composed manually inside a custom render function.
15
+ * Can also be composed as a standalone empty result surface. Render actionable
16
+ * empty content outside listbox and option semantics.
16
17
  *
17
18
  * @example
18
19
  * ```
19
20
  * <CommandPalette
20
- * emptyBootstrapText={<CommandPaletteEmpty>Start typing to search</CommandPaletteEmpty>}
21
- * emptySearchText={<CommandPaletteEmpty>No results found</CommandPaletteEmpty>}
21
+ * emptyBootstrapText="Start typing to search"
22
+ * emptySearchText="No results found"
22
23
  * searchSource={source}
23
24
  * />
24
25
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteEmpty.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteEmpty.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAS5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkB5C,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACzE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,yCAAyC;IACzC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,wBAAwB,qBAc1B;yBArBe,mBAAmB"}
1
+ {"version":3,"file":"CommandPaletteEmpty.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteEmpty.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAS5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkB5C,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACzE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,yCAAyC;IACzC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,wBAAwB,qBAc1B;yBArBe,mBAAmB"}
@@ -41,13 +41,14 @@ const styles = {
41
41
  * - `emptyBootstrapText`: no search term and bootstrap() returns nothing
42
42
  * - `emptySearchText`: a search query returned no results
43
43
  *
44
- * Can also be composed manually inside a custom render function.
44
+ * Can also be composed as a standalone empty result surface. Render actionable
45
+ * empty content outside listbox and option semantics.
45
46
  *
46
47
  * @example
47
48
  * ```
48
49
  * <CommandPalette
49
- * emptyBootstrapText={<CommandPaletteEmpty>Start typing to search</CommandPaletteEmpty>}
50
- * emptySearchText={<CommandPaletteEmpty>No results found</CommandPaletteEmpty>}
50
+ * emptyBootstrapText="Start typing to search"
51
+ * emptySearchText="No results found"
51
52
  * searchSource={source}
52
53
  * />
53
54
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteInput.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteInput.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAarE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkE5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,CAAC,gBAAgB,CAAC,EAC3B,UAAU,CACX;IACC,iEAAiE;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAElC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,WAAW,EAAE,oBAAoB,EACjC,KAAK,EACL,YAAmB,EACnB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,wBAAwB,+BAiI1B;yBA/Ie,mBAAmB"}
1
+ {"version":3,"file":"CommandPaletteInput.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteInput.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAarE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkE5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,CAAC,gBAAgB,CAAC,EAC3B,UAAU,CACX;IACC,iEAAiE;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAElC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,WAAW,EAAE,oBAAoB,EACjC,KAAK,EACL,YAAmB,EACnB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,wBAAwB,+BA2I1B;yBAzJe,mBAAmB"}
@@ -17,7 +17,7 @@ import { useCallback, useEffect, useRef } from 'react';
17
17
  import * as stylex from '@stylexjs/stylex';
18
18
  import { Icon } from "../Icon/index.js";
19
19
  import { Spinner } from "../Spinner/index.js";
20
- import { isRenderable, mergeProps } from "../utils/index.js";
20
+ import { isImeKeyEvent, isRenderable, mergeProps } from "../utils/index.js";
21
21
  import "../theme/tokens.stylex.js";
22
22
  import { colorVars, typeScaleVars, spacingVars, typographyVars } from "../theme/tokens.stylex.js";
23
23
  import { useCommandPaletteContext } from "./CommandPaletteContext.js";
@@ -74,6 +74,9 @@ export function CommandPaletteInput({
74
74
  const t = useTranslator();
75
75
  const placeholder = placeholderFromProps ?? t('@astryx.commandPalette.input.placeholder');
76
76
  const ctx = useCommandPaletteContext();
77
+ // Inside CommandPalette the results surface is a listbox only while it holds
78
+ // selectable results; empty and pending states expose no popup.
79
+ const hasListbox = ctx ? ctx.selectableItems.length > 0 : true;
77
80
  const dialogContext = useDialogContext();
78
81
  const inputRef = useRef(null);
79
82
 
@@ -108,7 +111,10 @@ export function CommandPaletteInput({
108
111
  // Keyboard navigation — delegates to useCombobox via context
109
112
  const handleKeyDown = useCallback(e => {
110
113
  onKeyDown?.(e);
111
- if (e.defaultPrevented) {
114
+ if (e.defaultPrevented || isImeKeyEvent(e.nativeEvent)) {
115
+ // An in-progress IME composition owns Enter, Escape, and arrows. Let
116
+ // the input method finish or cancel composition without selecting or
117
+ // dismissing palette content.
112
118
  return;
113
119
  }
114
120
  // Delegate to useCombobox's keyboard handler from context
@@ -133,10 +139,10 @@ export function CommandPaletteInput({
133
139
  },
134
140
  type: "text",
135
141
  role: ctx ? 'combobox' : standaloneRole,
136
- "aria-expanded": ctx ? ctx.isOpen : callerSets('aria-expanded') ? props['aria-expanded'] : callerOwnsPopup || undefined,
142
+ "aria-expanded": ctx ? ctx.isOpen && hasListbox : callerSets('aria-expanded') ? props['aria-expanded'] : callerOwnsPopup || undefined,
137
143
  "aria-autocomplete": ctx ? 'list' : callerSets('aria-autocomplete') ? props['aria-autocomplete'] : callerOwnsPopup ? 'list' : undefined,
138
- "aria-controls": ctx ? ctx.listId : props['aria-controls'],
139
- "aria-activedescendant": ctx ? ctx.highlightedIndex >= 0 ? ctx.getItemId(ctx.highlightedIndex) : undefined : props['aria-activedescendant']
144
+ "aria-controls": ctx ? hasListbox ? ctx.listId : undefined : props['aria-controls'],
145
+ "aria-activedescendant": ctx ? hasListbox && ctx.highlightedIndex >= 0 && ctx.highlightedIndex < ctx.selectableItems.length ? ctx.getItemId(ctx.highlightedIndex) : undefined : props['aria-activedescendant']
140
146
  // Placeholder text is never the field's name (AST-056 AR2).
141
147
  ,
142
148
  "aria-label": accessibleLabel,
@@ -16,8 +16,10 @@ export interface CommandPaletteListProps extends BaseProps<HTMLDivElement> {
16
16
  label?: string;
17
17
  }
18
18
  /**
19
- * Scrollable results container for the command palette.
20
- * Renders as a listbox for ARIA compliance.
19
+ * Scrollable results container for the command palette. Standalone composition
20
+ * renders as a listbox. Inside CommandPalette, listbox semantics are present
21
+ * only while selectable results exist; empty and pending states retain the same
22
+ * styled container without exposing an empty interactive popup.
21
23
  *
22
24
  * When used inside CommandPalette, automatically gets the correct
23
25
  * ID for aria-controls linking with the input.
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteList.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteList.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAmB5C,MAAM,WAAW,uBAAwB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACxE;;OAEG;IACH,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EAAE,cAAc,EACrB,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,uBAAuB,+BAqBzB;yBA7Be,kBAAkB"}
1
+ {"version":3,"file":"CommandPaletteList.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteList.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAmB5C,MAAM,WAAW,uBAAwB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACxE;;OAEG;IACH,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EAAE,cAAc,EACrB,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,uBAAuB,+BAwBzB;yBAhCe,kBAAkB"}
@@ -34,8 +34,10 @@ const styles = {
34
34
  }
35
35
  };
36
36
  /**
37
- * Scrollable results container for the command palette.
38
- * Renders as a listbox for ARIA compliance.
37
+ * Scrollable results container for the command palette. Standalone composition
38
+ * renders as a listbox. Inside CommandPalette, listbox semantics are present
39
+ * only while selectable results exist; empty and pending states retain the same
40
+ * styled container without exposing an empty interactive popup.
39
41
  *
40
42
  * When used inside CommandPalette, automatically gets the correct
41
43
  * ID for aria-controls linking with the input.
@@ -65,12 +67,15 @@ export function CommandPaletteList({
65
67
  const t = useTranslator();
66
68
  const label = labelFromProps ?? t('@astryx.commandPalette.list.label');
67
69
  const ctx = useCommandPaletteContext();
70
+ // Inside CommandPalette the list is a listbox only while it holds selectable
71
+ // results; standalone composition is always a listbox.
72
+ const hasListbox = ctx ? ctx.selectableItems.length > 0 : true;
68
73
  return /*#__PURE__*/_jsx("div", {
69
74
  ref: ref,
70
75
  ...props,
71
76
  id: ctx?.listId ?? props.id,
72
- role: "listbox",
73
- "aria-label": label,
77
+ role: hasListbox ? 'listbox' : undefined,
78
+ "aria-label": hasListbox ? label : undefined,
74
79
  ...mergeProps(themeProps('command-palette-list'), stylex.props(styles.list, xstyle), className, style),
75
80
  children: children
76
81
  });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file ContextMenu.tsx
3
- * @input Uses React, StyleX, useLayer, BottomSheet, useListFocus, and the
4
- * shared menu-presentation resolver
3
+ * @input Uses React, StyleX, useLayer, shared layer dismissal, BottomSheet,
4
+ * useListFocus, and the shared menu-presentation resolver
5
5
  * @output Exports ContextMenu with cursor-popover and touch-sheet presentations
6
6
  * @position Core implementation; consumed by index.ts
7
7
  *
@@ -117,7 +117,7 @@ export type ContextMenuProps = ContextMenuDataProps | ContextMenuCompoundProps;
117
117
  * </ContextMenu>
118
118
  * ```
119
119
  */
120
- export declare function ContextMenu({ children, menuWidth, size, label: labelFromProps, isDisabled, onOpenChange, presentation, triggerAs, ref, className, style, xstyle, triggerXstyle, 'data-testid': testId, ...rest }: ContextMenuProps): React.JSX.Element;
120
+ export declare function ContextMenu({ children, menuWidth, size, label: labelFromProps, isDisabled, onOpenChange, presentation, triggerAs, ref, className, style, xstyle, triggerXstyle, onContextMenu: onContextMenuProp, onTouchStart: onTouchStartProp, onTouchMove: onTouchMoveProp, onTouchEnd: onTouchEndProp, onTouchCancel: onTouchCancelProp, 'data-testid': testId, ...rest }: ContextMenuProps): React.JSX.Element;
121
121
  export declare namespace ContextMenu {
122
122
  var displayName: string;
123
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAkCrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,kCAAkC,CAAC;AA6G1C,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,YAAwB,EACxB,SAAiB,EACjB,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,gBAAgB,qBA6clB;yBA7de,WAAW"}
1
+ {"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAoCrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,kCAAkC,CAAC;AA6G1C,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,YAAwB,EACxB,SAAiB,EACjB,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,iBAAiB,EAChC,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,eAAe,EAC5B,UAAU,EAAE,cAAc,EAC1B,aAAa,EAAE,iBAAiB,EAChC,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,gBAAgB,qBAoelB;yBAzfe,WAAW"}
@@ -3,8 +3,8 @@
3
3
 
4
4
  /**
5
5
  * @file ContextMenu.tsx
6
- * @input Uses React, StyleX, useLayer, BottomSheet, useListFocus, and the
7
- * shared menu-presentation resolver
6
+ * @input Uses React, StyleX, useLayer, shared layer dismissal, BottomSheet,
7
+ * useListFocus, and the shared menu-presentation resolver
8
8
  * @output Exports ContextMenu with cursor-popover and touch-sheet presentations
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
@@ -39,6 +39,8 @@ import { Button } from "../Button/index.js";
39
39
  import { Heading } from "../Heading/index.js";
40
40
  import { Icon } from "../Icon/index.js";
41
41
  import { useLayer } from "../Layer/useLayer.js";
42
+ import { LayerDepthProvider } from "../Layer/LayerDepthContext.js";
43
+ import { useLayerDismissal } from "../Layer/useLayerDismissal.js";
42
44
  import { MenuBottomSheetActionList } from "../DropdownMenu/MenuBottomSheetActionList.js";
43
45
  import { renderDropdownItems } from "../DropdownMenu/renderDropdownItems.js";
44
46
  import { DropdownMenuContext } from "../DropdownMenu/DropdownMenuContext.js";
@@ -52,7 +54,7 @@ import { useLongPress } from "../hooks/useLongPress.js";
52
54
  import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
53
55
  import "../theme/tokens.stylex.js";
54
56
  import { colorVars, spacingVars, radiusVars, durationVars, easeVars, shadowVars } from "../theme/tokens.stylex.js";
55
- import { mergeProps, isImeKeyEvent, rtlStyles } from "../utils/index.js";
57
+ import { mergeProps, rtlStyles } from "../utils/index.js";
56
58
  import { themeProps } from "../utils/themeProps.js";
57
59
  import { useTranslator } from "../i18n/index.js";
58
60
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
@@ -194,6 +196,11 @@ export function ContextMenu({
194
196
  style,
195
197
  xstyle,
196
198
  triggerXstyle,
199
+ onContextMenu: onContextMenuProp,
200
+ onTouchStart: onTouchStartProp,
201
+ onTouchMove: onTouchMoveProp,
202
+ onTouchEnd: onTouchEndProp,
203
+ onTouchCancel: onTouchCancelProp,
197
204
  'data-testid': testId,
198
205
  ...rest
199
206
  }) {
@@ -226,7 +233,6 @@ export function ContextMenu({
226
233
  // Element focused before the menu opened, restored when it closes so focus
227
234
  // does not fall to <body> after Escape or outside-click dismissal.
228
235
  const triggerFocusRef = useRef(null);
229
- const sheetHeadingRef = useRef(null);
230
236
  const [isOpen, setIsOpen] = useState(false);
231
237
  const [submenuPath, setSubmenuPath] = useState([]);
232
238
  const currentSubmenu = submenuPath.at(-1);
@@ -274,16 +280,12 @@ export function ContextMenu({
274
280
  closeMenu();
275
281
  }
276
282
  }, [closeMenu]);
277
- useEffect(() => {
278
- if (!isOpen || submenuPath.length === 0) {
279
- return;
280
- }
281
- const frame = requestAnimationFrame(() => {
282
- sheetHeadingRef.current?.focus({
283
+ const setSheetHeadingRef = useCallback(node => {
284
+ if (node && isOpen && submenuPath.length > 0) {
285
+ node.focus({
283
286
  preventScroll: true
284
287
  });
285
- });
286
- return () => cancelAnimationFrame(frame);
288
+ }
287
289
  }, [isOpen, submenuPath.length]);
288
290
  const {
289
291
  listRef,
@@ -297,8 +299,15 @@ export function ContextMenu({
297
299
  boundarySelector: MENU_BOUNDARY_SELECTOR,
298
300
  // Menus wrap from the last row to the first and back.
299
301
  wrap: true,
300
- hasPaging: true,
301
- onEscape: closeMenu
302
+ hasPaging: true
303
+ });
304
+
305
+ // Join the shared stack so an Escape press dismisses exactly one top-most
306
+ // surface. BottomSheet owns its own dismissal path.
307
+ useLayerDismissal({
308
+ isActive: isOpen && !usesBottomSheet,
309
+ onDismiss: closeMenu,
310
+ getContainer: () => listRef.current
302
311
  });
303
312
 
304
313
  // Typeahead over the enabled menu items, matched on each row's LABEL alone.
@@ -329,32 +338,6 @@ export function ContextMenu({
329
338
  document.removeEventListener('mousedown', handleClickOutside);
330
339
  };
331
340
  }, [isOpen, closeMenu, listRef, usesBottomSheet]);
332
-
333
- // Dismiss on Escape from anywhere while open. The menu div's own onKeyDown
334
- // only fires when focus is inside the menu; a document-level listener is
335
- // kept as a reliable fallback Escape path (e.g. if focus has moved out of
336
- // the menu). Guards against IME composition-cancel.
337
- useEffect(() => {
338
- if (!isOpen || usesBottomSheet) {
339
- return;
340
- }
341
- const handleEscape = e => {
342
- if (e.key !== 'Escape') {
343
- return;
344
- }
345
- if (isImeKeyEvent(e)) {
346
- // Ignore Escape that is committing/cancelling an IME composition;
347
- // see utils/ime.ts for why.
348
- return;
349
- }
350
- e.preventDefault();
351
- closeMenu();
352
- };
353
- document.addEventListener('keydown', handleEscape);
354
- return () => {
355
- document.removeEventListener('keydown', handleEscape);
356
- };
357
- }, [isOpen, closeMenu, usesBottomSheet]);
358
341
  const listKeyDown = useCallback(e => {
359
342
  // A submenu flyout renders inline inside this menu; its key events bubble
360
343
  // up here. Let that level own them — only handle events from this level.
@@ -447,13 +430,42 @@ export function ContextMenu({
447
430
  // held long enough (see useLongPress for timer/move-cancel/cleanup logic).
448
431
  // The hook also returns the imperative start/moveTo/cancel API the menu press
449
432
  // model drives; only its four touch handlers belong on the trigger element.
450
- const longPress = useLongPress({
433
+ const {
434
+ onTouchStart: handleLongPressTouchStart,
435
+ onTouchMove: handleLongPressTouchMove,
436
+ onTouchEnd: handleLongPressTouchEnd,
437
+ onTouchCancel: handleLongPressTouchCancel
438
+ } = useLongPress({
451
439
  disabled: isDisabled,
452
440
  onLongPress: useCallback(point => {
453
441
  const rect = getAnchorBaseRect();
454
442
  openAtLocalPoint(rect ? point.x - rect.left : point.x, rect ? point.y - rect.top : point.y, triggerRef.current);
455
443
  }, [openAtLocalPoint, getAnchorBaseRect])
456
444
  });
445
+
446
+ // ContextMenu owns invocation and long-press cleanup. Consumer handlers are
447
+ // notified too, but preventing their default does not cancel component-owned
448
+ // behavior that the public API does not promise as cancelable.
449
+ const handleTriggerContextMenu = useCallback(event => {
450
+ handleContextMenu(event);
451
+ onContextMenuProp?.(event);
452
+ }, [handleContextMenu, onContextMenuProp]);
453
+ const handleTriggerTouchStart = useCallback(event => {
454
+ handleLongPressTouchStart(event);
455
+ onTouchStartProp?.(event);
456
+ }, [handleLongPressTouchStart, onTouchStartProp]);
457
+ const handleTriggerTouchMove = useCallback(event => {
458
+ handleLongPressTouchMove(event);
459
+ onTouchMoveProp?.(event);
460
+ }, [handleLongPressTouchMove, onTouchMoveProp]);
461
+ const handleTriggerTouchEnd = useCallback(event => {
462
+ handleLongPressTouchEnd();
463
+ onTouchEndProp?.(event);
464
+ }, [handleLongPressTouchEnd, onTouchEndProp]);
465
+ const handleTriggerTouchCancel = useCallback(event => {
466
+ handleLongPressTouchCancel();
467
+ onTouchCancelProp?.(event);
468
+ }, [handleLongPressTouchCancel, onTouchCancelProp]);
457
469
  const popoverXstyle = menuWidth ? styles.popoverCustomWidth(menuWidth) : styles.popover;
458
470
 
459
471
  // The drill-in view stack for sub-menus on a phone; the bottom sheet has
@@ -517,7 +529,7 @@ export function ContextMenu({
517
529
  isIconOnly: true,
518
530
  onClick: () => setSubmenuPath(path => path.slice(0, -1))
519
531
  }), /*#__PURE__*/_jsx(Heading, {
520
- ref: sheetHeadingRef,
532
+ ref: setSheetHeadingRef,
521
533
  level: 3,
522
534
  tabIndex: -1,
523
535
  xstyle: [styles.sheetViewHeading, submenuPath.length === 0 && styles.sheetRootHeading],
@@ -537,11 +549,11 @@ export function ContextMenu({
537
549
  children: [/*#__PURE__*/_jsxs(TriggerElement, {
538
550
  ref: useMergedRefs(ref, triggerRef),
539
551
  ...triggerProps,
540
- onContextMenu: handleContextMenu,
541
- onTouchStart: longPress.onTouchStart,
542
- onTouchMove: longPress.onTouchMove,
543
- onTouchEnd: longPress.onTouchEnd,
544
- onTouchCancel: longPress.onTouchCancel,
552
+ onContextMenu: handleTriggerContextMenu,
553
+ onTouchStart: handleTriggerTouchStart,
554
+ onTouchMove: handleTriggerTouchMove,
555
+ onTouchEnd: handleTriggerTouchEnd,
556
+ onTouchCancel: handleTriggerTouchCancel,
545
557
  "data-testid": testId,
546
558
  ...stylex.props(styles.trigger, ...(triggerXstyle ? Array.isArray(triggerXstyle) ? triggerXstyle : [triggerXstyle] : [])),
547
559
  children: [children, /*#__PURE__*/_jsx("span", {
@@ -564,7 +576,9 @@ export function ContextMenu({
564
576
  label: sheetLabel,
565
577
  children: renderedBottomSheetContent
566
578
  })
567
- }) : layer.render(renderedMenu, {
579
+ }) : layer.render(/*#__PURE__*/_jsx(LayerDepthProvider, {
580
+ children: renderedMenu
581
+ }), {
568
582
  placement: 'below',
569
583
  alignment: 'start',
570
584
  xstyle: [popoverXstyle, layerAnimations.below]
@@ -66,16 +66,16 @@ export interface UseLayerDismissalReturn {
66
66
  *
67
67
  * The layer does NOT attach a key listener — the stack owns one listener and
68
68
  * routes each Escape press to the top-most REGISTERED layer, so one press
69
- * dismisses exactly one of them. Dialog (and what is built on it), Popover and
70
- * the menus built on it, Tooltip, HoverCard, Lightbox, MobileNav, BottomSheet,
71
- * BottomSheetSwitcher and lab's `Drawer` register today.
69
+ * dismisses exactly one of them. Dialog (and what is built on it, including
70
+ * CommandPalette), Popover and the menus built on it, ContextMenu, Tooltip,
71
+ * HoverCard, Lightbox, MobileNav, BottomSheet, BottomSheetSwitcher and lab's
72
+ * `Drawer` register today.
72
73
  *
73
- * `CommandPalette`, `ContextMenu`, `DropdownMenuSubMenu`, and
74
- * `PowerSearchEditPopover` still run their own Escape listeners. They stay safe
75
- * next to the stack only because each claims the press at element level, and the
76
- * stack stands down on an already-`defaultPrevented` press — but a registered
77
- * layer opened inside one of them does not get the press. Migrating them is the
78
- * fix.
74
+ * `DropdownMenuSubMenu` and `PowerSearchEditPopover` still run their own Escape
75
+ * listeners. They stay safe next to the stack only because each claims the press
76
+ * at element level, and the stack stands down on an already-`defaultPrevented`
77
+ * press — but a registered layer opened inside one of them does not get the
78
+ * press. Migrating them is the fix.
79
79
  *
80
80
  * Wrap the layer's own content in `LayerDepthProvider` so anything opened from
81
81
  * inside it registers as nested.
@@ -23,16 +23,16 @@ import { isTextComposing, isTopmostLayer, registerLayer } from "./layerStack.js"
23
23
  *
24
24
  * The layer does NOT attach a key listener — the stack owns one listener and
25
25
  * routes each Escape press to the top-most REGISTERED layer, so one press
26
- * dismisses exactly one of them. Dialog (and what is built on it), Popover and
27
- * the menus built on it, Tooltip, HoverCard, Lightbox, MobileNav, BottomSheet,
28
- * BottomSheetSwitcher and lab's `Drawer` register today.
26
+ * dismisses exactly one of them. Dialog (and what is built on it, including
27
+ * CommandPalette), Popover and the menus built on it, ContextMenu, Tooltip,
28
+ * HoverCard, Lightbox, MobileNav, BottomSheet, BottomSheetSwitcher and lab's
29
+ * `Drawer` register today.
29
30
  *
30
- * `CommandPalette`, `ContextMenu`, `DropdownMenuSubMenu`, and
31
- * `PowerSearchEditPopover` still run their own Escape listeners. They stay safe
32
- * next to the stack only because each claims the press at element level, and the
33
- * stack stands down on an already-`defaultPrevented` press — but a registered
34
- * layer opened inside one of them does not get the press. Migrating them is the
35
- * fix.
31
+ * `DropdownMenuSubMenu` and `PowerSearchEditPopover` still run their own Escape
32
+ * listeners. They stay safe next to the stack only because each claims the press
33
+ * at element level, and the stack stands down on an already-`defaultPrevented`
34
+ * press — but a registered layer opened inside one of them does not get the
35
+ * press. Migrating them is the fix.
36
36
  *
37
37
  * Wrap the layer's own content in `LayerDepthProvider` so anything opened from
38
38
  * inside it registers as nested.
@@ -1 +1 @@
1
- {"version":3,"file":"plainDate.d.ts","sourceRoot":"","sources":["../../src/utils/plainDate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plainDate.d.ts","sourceRoot":"","sources":["../../src/utils/plainDate.ts"],"names":[],"mappings":"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"}