@astryxdesign/core 0.7.1-canary.fe8cad0 → 0.7.1

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 (93) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  3. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  4. package/dist/Chat/ChatComposerInput.js +5 -0
  5. package/dist/Chat/ChatMessage.d.ts +11 -0
  6. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  7. package/dist/Chat/ChatMessage.js +18 -9
  8. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  9. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  10. package/dist/Chat/useTriggerMenu.js +222 -50
  11. package/dist/CommandPalette/CommandPalette.d.ts +6 -4
  12. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  13. package/dist/CommandPalette/CommandPalette.js +38 -20
  14. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  15. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  16. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +11 -5
  19. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  20. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  21. package/dist/CommandPalette/CommandPaletteList.js +9 -4
  22. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  23. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  24. package/dist/ContextMenu/ContextMenu.js +62 -48
  25. package/dist/Divider/Divider.d.ts +1 -0
  26. package/dist/Divider/Divider.d.ts.map +1 -1
  27. package/dist/Divider/Divider.js +5 -4
  28. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  29. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  31. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  32. package/dist/Layer/useLayerDismissal.js +9 -9
  33. package/dist/Markdown/Markdown.d.ts.map +1 -1
  34. package/dist/Markdown/Markdown.js +6 -1
  35. package/dist/astryx.css +2 -0
  36. package/dist/reset.a11y.chromium.spec.js +6 -1
  37. package/dist/utils/plainDate.d.ts.map +1 -1
  38. package/dist/utils/plainDate.js +14 -5
  39. package/locales/en.json +1 -1
  40. package/package.json +4 -4
  41. package/src/AlertDialog/AlertDialog.spec.md +1 -1
  42. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  43. package/src/Chat/ChatComposerInput.spec.md +64 -61
  44. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  45. package/src/Chat/ChatComposerInput.tsx +13 -0
  46. package/src/Chat/ChatMessage.doc.mjs +31 -16
  47. package/src/Chat/ChatMessage.spec.md +265 -0
  48. package/src/Chat/ChatMessage.test.tsx +246 -0
  49. package/src/Chat/ChatMessage.tsx +30 -7
  50. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  51. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  52. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  53. package/src/Chat/useTriggerMenu.tsx +278 -46
  54. package/src/CommandPalette/CommandPalette.doc.mjs +31 -1
  55. package/src/CommandPalette/CommandPalette.spec.md +95 -46
  56. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  57. package/src/CommandPalette/CommandPalette.tsx +44 -20
  58. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  59. package/src/CommandPalette/CommandPaletteInput.spec.md +7 -4
  60. package/src/CommandPalette/CommandPaletteInput.test.tsx +63 -2
  61. package/src/CommandPalette/CommandPaletteInput.tsx +15 -5
  62. package/src/CommandPalette/CommandPaletteItem.test.tsx +7 -0
  63. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  64. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  65. package/src/CommandPalette/CommandPaletteList.test.tsx +67 -0
  66. package/src/CommandPalette/CommandPaletteList.tsx +9 -4
  67. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  68. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  69. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  70. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  71. package/src/ContextMenu/ContextMenu.tsx +78 -48
  72. package/src/Divider/Divider.doc.mjs +5 -3
  73. package/src/Divider/Divider.spec.md +140 -54
  74. package/src/Divider/Divider.test.tsx +15 -0
  75. package/src/Divider/Divider.tsx +6 -4
  76. package/src/DropdownMenu/DropdownMenu.spec.md +34 -17
  77. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  78. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  79. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  80. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  81. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  82. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  83. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  84. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  85. package/src/Layer/useLayerDismissal.ts +9 -9
  86. package/src/Markdown/Markdown.doc.mjs +1 -1
  87. package/src/Markdown/Markdown.spec.md +15 -7
  88. package/src/Markdown/Markdown.tsx +4 -0
  89. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +5 -0
  90. package/src/reset.a11y.chromium.spec.ts +9 -1
  91. package/src/reset.css +4 -1
  92. package/src/utils/plainDate.test.ts +99 -0
  93. package/src/utils/plainDate.ts +23 -5
@@ -10,7 +10,9 @@
10
10
  *
11
11
  * Detects trigger characters (@ / etc.) typed inside a contentEditable,
12
12
  * opens a popover at the cursor position with filtered items, handles
13
- * keyboard navigation, and inserts tokens or text on selection.
13
+ * keyboard navigation, and inserts tokens or text on selection. Triggers
14
+ * opt into multi-word queries with hasMultiWordQuery; results apply only
15
+ * while their query is current (component:ChatComposerInput FR10–FR16).
14
16
  *
15
17
  * Reuses SearchSource from Typeahead for async/sync search with
16
18
  * cancel() support and debounce.
@@ -25,7 +27,7 @@ import { usePopover } from "../Popover/usePopover.js";
25
27
  import { useHighlightedOptionScroll } from "../hooks/useHighlightedOptionScroll.js";
26
28
  import "../theme/tokens.stylex.js";
27
29
  import { colorVars, spacingVars, radiusVars, typeScaleVars, typographyVars } from "../theme/tokens.stylex.js";
28
- import { mergeProps, groupItems } from "../utils/index.js";
30
+ import { mergeProps, groupItems, isImeKeyEvent } from "../utils/index.js";
29
31
  import { themeProps } from "../utils/themeProps.js";
30
32
  import { useTranslator } from "../i18n/index.js";
31
33
 
@@ -48,6 +50,15 @@ const styles = {
48
50
  // Helpers
49
51
  // =============================================================================
50
52
 
53
+ /** The text node holding a collapsed caret inside `editable`, if any. */
54
+ function getCaretTextNode(editable) {
55
+ const selection = window.getSelection();
56
+ if (!selection || selection.rangeCount === 0) {
57
+ return null;
58
+ }
59
+ const node = selection.getRangeAt(0).startContainer;
60
+ return node.nodeType === Node.TEXT_NODE && editable.contains(node) ? node : null;
61
+ }
51
62
  function getTextBeforeCursor(editable) {
52
63
  const selection = window.getSelection();
53
64
  if (!selection || selection.rangeCount === 0) {
@@ -66,27 +77,73 @@ function getTextBeforeCursor(editable) {
66
77
  }
67
78
  return null;
68
79
  }
80
+ const MAX_MULTI_WORD_QUERY_LENGTH = 64;
81
+
82
+ /**
83
+ * An unmatched multi-word query hides the menu instead of covering prose with
84
+ * an empty panel, unless the trigger supplies its own empty message.
85
+ */
86
+ function isUnmatchedMultiWordQuery(trigger, items, isLoading) {
87
+ return trigger?.hasMultiWordQuery === true &&
88
+ // `null` means "not given", exactly as `undefined` does.
89
+ trigger.emptySearchText == null && items.length === 0 && !isLoading;
90
+ }
91
+ function isTriggerBoundary(char) {
92
+ return char === null || char === ' ' || char === '\t' || char === '\n';
93
+ }
94
+
95
+ /**
96
+ * Finds the trigger query the caret is in (component:ChatComposerInput FR10,
97
+ * FR11). A trigger character activates at the start of the text or after a
98
+ * space, tab, or line break. A single-token query ends at whitespace. A
99
+ * multi-word query ends at a line break, past the length bound, or where the
100
+ * same trigger's character starts a new query; another trigger's character
101
+ * inside it is ordinary text.
102
+ */
69
103
  function findActiveTrigger(textBeforeCursor, triggers) {
70
- for (let i = textBeforeCursor.length - 1; i >= 0; i--) {
104
+ let active = null;
105
+ for (let i = 0; i < textBeforeCursor.length; i++) {
71
106
  const char = textBeforeCursor[i];
72
- if (char === ' ' || char === '\n') {
73
- return null;
107
+ if (char === '\n') {
108
+ active = null;
109
+ continue;
74
110
  }
75
- for (const trigger of triggers) {
76
- if (char === trigger.character) {
77
- const prevChar = i > 0 ? textBeforeCursor[i - 1] : null;
78
- if (prevChar === null || prevChar === ' ' || prevChar === '\n') {
79
- const query = textBeforeCursor.slice(i + 1);
80
- return {
81
- trigger,
82
- query,
83
- triggerStart: i
84
- };
85
- }
111
+ if (active && !active.trigger.hasMultiWordQuery) {
112
+ if (char === ' ' || char === '\t') {
113
+ active = null;
86
114
  }
115
+ continue;
116
+ }
117
+ if (active && i - active.triggerStart > MAX_MULTI_WORD_QUERY_LENGTH) {
118
+ active = null;
119
+ }
120
+ if (!isTriggerBoundary(i > 0 ? textBeforeCursor[i - 1] : null)) {
121
+ continue;
122
+ }
123
+ const trigger = triggers.find(t => t.character === char);
124
+ if (!trigger) {
125
+ continue;
87
126
  }
127
+ if (active && trigger.character !== active.trigger.character) {
128
+ continue;
129
+ }
130
+ active = {
131
+ trigger,
132
+ triggerStart: i
133
+ };
88
134
  }
89
- return null;
135
+ if (!active) {
136
+ return null;
137
+ }
138
+ const query = textBeforeCursor.slice(active.triggerStart + 1);
139
+ if (active.trigger.hasMultiWordQuery && query.length > MAX_MULTI_WORD_QUERY_LENGTH) {
140
+ return null;
141
+ }
142
+ return {
143
+ trigger: active.trigger,
144
+ query,
145
+ triggerStart: active.triggerStart
146
+ };
90
147
  }
91
148
  function deleteTriggerText(editable, triggerStart) {
92
149
  const selection = window.getSelection();
@@ -131,11 +188,17 @@ export function useTriggerMenu(options) {
131
188
  query: '',
132
189
  items: [],
133
190
  highlightedIndex: 0,
134
- isLoading: false
191
+ isLoading: false,
192
+ isHidden: false
135
193
  });
136
194
  const triggerStartRef = useRef(-1);
137
195
  const searchTimeoutRef = useRef(null);
196
+ const searchRequestIdRef = useRef(0);
197
+ const dismissedRef = useRef(null);
138
198
  const anchorSpanRef = useRef(null);
199
+ // True while the popover is closed only because the query is unmatched; the
200
+ // trigger query itself is still active.
201
+ const isMenuSuppressedRef = useRef(false);
139
202
  const removeAnchorSpan = useCallback(() => {
140
203
  if (anchorSpanRef.current) {
141
204
  anchorSpanRef.current.remove();
@@ -144,6 +207,10 @@ export function useTriggerMenu(options) {
144
207
  }, []);
145
208
  const popover = usePopover({
146
209
  onHide: useCallback(() => {
210
+ if (isMenuSuppressedRef.current) {
211
+ return;
212
+ }
213
+ searchRequestIdRef.current++;
147
214
  removeAnchorSpan();
148
215
  setState(prev => ({
149
216
  ...prev,
@@ -152,7 +219,8 @@ export function useTriggerMenu(options) {
152
219
  query: '',
153
220
  items: [],
154
221
  highlightedIndex: 0,
155
- isLoading: false
222
+ isLoading: false,
223
+ isHidden: false
156
224
  }));
157
225
  }, [removeAnchorSpan]),
158
226
  hasLightDismiss: true,
@@ -162,6 +230,10 @@ export function useTriggerMenu(options) {
162
230
  // the contenteditable composer, so a modal dialog wrapper is incorrect.
163
231
  role: 'none'
164
232
  });
233
+ const {
234
+ show: showPopover,
235
+ hide: hidePopover
236
+ } = popover;
165
237
 
166
238
  // Cleanup on unmount
167
239
  useEffect(() => {
@@ -177,14 +249,29 @@ export function useTriggerMenu(options) {
177
249
  clearTimeout(searchTimeoutRef.current);
178
250
  searchTimeoutRef.current = null;
179
251
  }
252
+ searchRequestIdRef.current++;
180
253
  // Cancel any in-flight search on the active trigger's searchSource
181
254
  const trigger = state.activeTrigger;
182
255
  if (trigger?.searchSource) {
183
256
  trigger.searchSource.cancel?.();
184
257
  }
185
258
  removeAnchorSpan();
259
+ isMenuSuppressedRef.current = false;
260
+ // Ending the query also forgets an Escape on it; a send or clear must not
261
+ // leave the next message's mention at the same offset suppressed.
262
+ dismissedRef.current = null;
186
263
  popover.hide();
187
264
  triggerStartRef.current = -1;
265
+ setState(prev => ({
266
+ ...prev,
267
+ isActive: false,
268
+ activeTrigger: null,
269
+ query: '',
270
+ items: [],
271
+ highlightedIndex: 0,
272
+ isLoading: false,
273
+ isHidden: false
274
+ }));
188
275
  }, [popover, state.activeTrigger, removeAnchorSpan]);
189
276
 
190
277
  // Anchor the popover to the cursor position (not the entire input).
@@ -232,9 +319,46 @@ export function useTriggerMenu(options) {
232
319
  clearTimeout(searchTimeoutRef.current);
233
320
  searchTimeoutRef.current = null;
234
321
  }
322
+ const requestId = ++searchRequestIdRef.current;
323
+ // A result applies only while its request is the latest one and the
324
+ // caret is still inside the same trigger query. Submitting or clearing
325
+ // the editor ends the query without an input event, so check the DOM.
326
+ const isCurrent = () => {
327
+ if (requestId !== searchRequestIdRef.current) {
328
+ return false;
329
+ }
330
+ const editable = editableRef.current;
331
+ const textBefore = editable ? getTextBeforeCursor(editable) : null;
332
+ const found = textBefore === null || !triggers ? null : findActiveTrigger(textBefore, triggers);
333
+ if (found?.trigger.character !== trigger.character || found.triggerStart !== triggerStartRef.current) {
334
+ reset();
335
+ return false;
336
+ }
337
+ return true;
338
+ };
339
+ const applyResults = items => {
340
+ const isHidden = isUnmatchedMultiWordQuery(trigger, items, false);
341
+ setState(prev => ({
342
+ ...prev,
343
+ items,
344
+ highlightedIndex: items.length > 0 ? 0 : -1,
345
+ isLoading: false,
346
+ isHidden
347
+ }));
348
+ // A hidden menu closes its popover, which also releases the popover's
349
+ // Escape and light-dismiss handling; matches reopen it at the caret.
350
+ if (isHidden && !isMenuSuppressedRef.current) {
351
+ isMenuSuppressedRef.current = true;
352
+ hidePopover();
353
+ } else if (!isHidden && isMenuSuppressedRef.current) {
354
+ isMenuSuppressedRef.current = false;
355
+ setAnchor();
356
+ showPopover();
357
+ }
358
+ };
235
359
  const doSearch = () => {
236
360
  if (trigger.searchSource) {
237
- // Use SearchSource \u2014 cancel previous, then search
361
+ // Use SearchSource — cancel previous, then search
238
362
  trigger.searchSource.cancel?.();
239
363
  setState(prev => ({
240
364
  ...prev,
@@ -242,19 +366,15 @@ export function useTriggerMenu(options) {
242
366
  }));
243
367
  const result = trigger.searchSource.search(query);
244
368
  Promise.resolve(result).then(items => {
245
- setState(prev => ({
246
- ...prev,
247
- items,
248
- highlightedIndex: items.length > 0 ? 0 : -1,
249
- isLoading: false
250
- }));
369
+ if (!isCurrent()) {
370
+ return;
371
+ }
372
+ applyResults(items);
251
373
  }, () => {
252
- setState(prev => ({
253
- ...prev,
254
- items: [],
255
- highlightedIndex: -1,
256
- isLoading: false
257
- }));
374
+ if (!isCurrent()) {
375
+ return;
376
+ }
377
+ applyResults([]);
258
378
  });
259
379
  }
260
380
  };
@@ -274,7 +394,7 @@ export function useTriggerMenu(options) {
274
394
  doSearch();
275
395
  }
276
396
  }
277
- }, [debounceMs]);
397
+ }, [debounceMs, editableRef, triggers, reset, setAnchor, hidePopover, showPopover]);
278
398
  const selectItem = useCallback(item => {
279
399
  const trigger = state.activeTrigger;
280
400
  if (!trigger) {
@@ -309,6 +429,7 @@ export function useTriggerMenu(options) {
309
429
  }
310
430
  const textBefore = getTextBeforeCursor(editable);
311
431
  if (textBefore === null) {
432
+ dismissedRef.current = null;
312
433
  if (state.isActive) {
313
434
  reset();
314
435
  }
@@ -316,6 +437,7 @@ export function useTriggerMenu(options) {
316
437
  }
317
438
  const found = findActiveTrigger(textBefore, triggers);
318
439
  if (!found) {
440
+ dismissedRef.current = null;
319
441
  if (state.isActive) {
320
442
  reset();
321
443
  }
@@ -326,14 +448,27 @@ export function useTriggerMenu(options) {
326
448
  query,
327
449
  triggerStart
328
450
  } = found;
329
- if (!state.isActive || state.activeTrigger !== trigger) {
451
+
452
+ // After Escape, keep the menu hidden while that same query keeps growing
453
+ const dismissed = dismissedRef.current;
454
+ if (dismissed && dismissed.character === trigger.character && dismissed.triggerStart === triggerStart && dismissed.textNode !== null && dismissed.textNode.isConnected && dismissed.textNode === getCaretTextNode(editable)) {
455
+ if (query.startsWith(dismissed.dismissedQuery)) {
456
+ return;
457
+ }
458
+ dismissedRef.current = null;
459
+ } else {
460
+ dismissedRef.current = null;
461
+ }
462
+ if (!state.isActive || state.activeTrigger?.character !== trigger.character || triggerStartRef.current !== triggerStart) {
330
463
  triggerStartRef.current = triggerStart;
464
+ isMenuSuppressedRef.current = false;
331
465
  setState(prev => ({
332
466
  ...prev,
333
467
  isActive: true,
334
468
  activeTrigger: trigger,
335
469
  query,
336
- highlightedIndex: 0
470
+ highlightedIndex: 0,
471
+ isHidden: false
337
472
  }));
338
473
  setAnchor();
339
474
  searchItems(trigger, query);
@@ -341,13 +476,50 @@ export function useTriggerMenu(options) {
341
476
  } else if (state.query !== query) {
342
477
  setState(prev => ({
343
478
  ...prev,
344
- query
479
+ query,
480
+ activeTrigger: trigger
345
481
  }));
346
482
  searchItems(trigger, query);
347
483
  }
348
484
  }, [triggers, editableRef, state.isActive, state.activeTrigger, state.query, reset, setAnchor, searchItems, popover]);
485
+ const isMenuVisible = state.isActive && !state.isHidden;
349
486
  const handleKeyDown = useCallback(e => {
350
- if (!state.isActive || !popover.isOpen) {
487
+ // Never intercept IME composition keystrokes (e.g. Enter committing candidates)
488
+ if (isImeKeyEvent(e.nativeEvent)) {
489
+ return false;
490
+ }
491
+
492
+ // A hidden menu consumes nothing: arrows move the caret, Enter submits,
493
+ // and Escape reaches the surrounding surface.
494
+ if (!isMenuVisible) {
495
+ return false;
496
+ }
497
+ if (e.key === 'Escape') {
498
+ e.preventDefault();
499
+ const editable = editableRef.current;
500
+ const dismissed = state.activeTrigger ? {
501
+ character: state.activeTrigger.character,
502
+ triggerStart: triggerStartRef.current,
503
+ dismissedQuery: state.query,
504
+ textNode: editable ? getCaretTextNode(editable) : null
505
+ } : null;
506
+ reset();
507
+ // Remembered after reset, which clears it: the menu stays hidden while
508
+ // this same query keeps growing.
509
+ dismissedRef.current = dismissed;
510
+ return true;
511
+ }
512
+
513
+ // A visible menu keeps the arrows even when it has nothing to pick (rows
514
+ // still loading, or the empty message), so they never recall history
515
+ // under an open panel. Enter and Tab pick only a settled row; otherwise
516
+ // they keep their editor meaning, so Enter sends the message as typed.
517
+ const hasPickableRows = !state.isLoading && state.items.length > 0;
518
+ if (!hasPickableRows && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
519
+ e.preventDefault();
520
+ return true;
521
+ }
522
+ if (!hasPickableRows) {
351
523
  return false;
352
524
  }
353
525
  switch (e.key) {
@@ -379,16 +551,10 @@ export function useTriggerMenu(options) {
379
551
  }
380
552
  return false;
381
553
  }
382
- case 'Escape':
383
- {
384
- e.preventDefault();
385
- reset();
386
- return true;
387
- }
388
554
  default:
389
555
  return false;
390
556
  }
391
- }, [state.isActive, state.highlightedIndex, state.items, popover.isOpen, selectItem, reset]);
557
+ }, [isMenuVisible, editableRef, state.activeTrigger, state.query, state.isLoading, state.highlightedIndex, state.items, selectItem, reset]);
392
558
  const getItemId = useCallback(index => `${listboxId}-option-${index}`, [listboxId]);
393
559
 
394
560
  // Keep the highlighted option visible during keyboard navigation; hover
@@ -416,7 +582,7 @@ export function useTriggerMenu(options) {
416
582
  const ariaProps = !hasTriggers ? {
417
583
  role: 'textbox',
418
584
  'aria-multiline': 'true'
419
- } : state.isActive && popover.isOpen ? {
585
+ } : isMenuVisible ? {
420
586
  role: 'combobox',
421
587
  'aria-expanded': true,
422
588
  'aria-controls': listboxId,
@@ -428,9 +594,15 @@ export function useTriggerMenu(options) {
428
594
  'aria-haspopup': 'listbox'
429
595
  };
430
596
  const renderMenu = useCallback(() => {
431
- const trigger = state.activeTrigger;
432
- const emptyText = trigger?.emptySearchText ?? 'No results';
433
- const loadingText = trigger?.loadingText ?? 'Searching\u2026';
597
+ const activeTrigger = state.activeTrigger;
598
+ if (!activeTrigger) {
599
+ return null;
600
+ }
601
+ if (state.isHidden) {
602
+ return null;
603
+ }
604
+ const emptyText = activeTrigger.emptySearchText ?? 'No results';
605
+ const loadingText = activeTrigger.loadingText ?? 'Searching\u2026';
434
606
  let listContent;
435
607
  if (state.isLoading) {
436
608
  listContent = /*#__PURE__*/_jsx("div", {
@@ -471,7 +643,7 @@ export function useTriggerMenu(options) {
471
643
  className: "x9f619 x78zum5 x6s0dn4 xh8yej3 xlsj2fj xh6dtrn x1ypdohk x16khyan x1a2a7pz xc342km xng3xce x1yc453h x9ynric xjm74w1 xw6l6zx x1tgivj0 x1lmrjuc"
472
644
  }
473
645
  }[!!(idx === state.highlightedIndex) << 0],
474
- children: trigger?.renderItem ? trigger.renderItem(item) : /*#__PURE__*/_jsx("span", {
646
+ children: activeTrigger.renderItem ? activeTrigger.renderItem(item) : /*#__PURE__*/_jsx("span", {
475
647
  ...{
476
648
  className: "x98rzlu xeuugli xb3r6kr xlyipyv xuxw1ft"
477
649
  },
@@ -498,7 +670,7 @@ export function useTriggerMenu(options) {
498
670
  return popover.render(/*#__PURE__*/_jsx("div", {
499
671
  id: listboxId,
500
672
  role: "listbox",
501
- "aria-label": trigger?.menuLabel ?? t('@astryx.chatTriggerMenu.suggestions'),
673
+ "aria-label": activeTrigger.menuLabel ?? t('@astryx.chatTriggerMenu.suggestions'),
502
674
  ...mergeProps(themeProps('trigger-menu'), {
503
675
  className: "x9f619 x1xiyphd x1odjw0f x9epnlk xfb3i0g"
504
676
  }),
@@ -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"}