@astryxdesign/core 0.7.7-canary.f9ac85f → 0.7.7

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 (198) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
  3. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
  4. package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
  5. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheet.js +79 -9
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +1 -1
  9. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  10. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
  14. package/dist/Button/Button.d.ts.map +1 -1
  15. package/dist/Button/Button.js +30 -2
  16. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  17. package/dist/CheckboxList/CheckboxList.js +7 -1
  18. package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
  19. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  20. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  21. package/dist/CheckboxList/CheckboxListItem.js +10 -2
  22. package/dist/DateInput/DateInput.d.ts.map +1 -1
  23. package/dist/DateInput/DateInput.js +5 -2
  24. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  25. package/dist/DateInput/MonthScroller.js +13 -27
  26. package/dist/DateInput/Wheel.d.ts.map +1 -1
  27. package/dist/DateInput/Wheel.js +22 -41
  28. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  29. package/dist/DateTimeInput/DateTimeInput.js +5 -2
  30. package/dist/Dialog/Dialog.d.ts.map +1 -1
  31. package/dist/Dialog/Dialog.js +4 -3
  32. package/dist/Drawer/Drawer.js +0 -1
  33. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  34. package/dist/Drawer/useDrawerDialogPresence.js +5 -1
  35. package/dist/Field/PanelSearchInput.d.ts +9 -1
  36. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  37. package/dist/Field/PanelSearchInput.js +3 -1
  38. package/dist/InfoTip/InfoTip.d.ts +2 -2
  39. package/dist/InfoTip/InfoTip.js +4 -4
  40. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  41. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  42. package/dist/Layer/layerAnimations.stylex.js +25 -20
  43. package/dist/Layer/usePressPin.d.ts +3 -5
  44. package/dist/Layer/usePressPin.d.ts.map +1 -1
  45. package/dist/Layer/usePressPin.js +4 -6
  46. package/dist/MobileNav/MobileNav.js +0 -1
  47. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  48. package/dist/MultiSelector/hooks.js +2 -4
  49. package/dist/Outline/Outline.d.ts +2 -4
  50. package/dist/Outline/Outline.d.ts.map +1 -1
  51. package/dist/Outline/Outline.js +4 -52
  52. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  53. package/dist/Outline/useOutlineFromMarkdown.js +1 -0
  54. package/dist/Outline/useScrollSpy.js +3 -3
  55. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  56. package/dist/PowerSearch/PowerSearch.js +134 -179
  57. package/dist/RadioList/RadioList.d.ts.map +1 -1
  58. package/dist/RadioList/RadioList.js +5 -0
  59. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  60. package/dist/Resizable/ResizeHandle.js +118 -260
  61. package/dist/Resizable/resizeGesture.d.ts +91 -0
  62. package/dist/Resizable/resizeGesture.d.ts.map +1 -0
  63. package/dist/Resizable/resizeGesture.js +251 -0
  64. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  65. package/dist/SideNav/SideNavItem.js +3 -9
  66. package/dist/Token/Token.d.ts +2 -2
  67. package/dist/Token/Token.d.ts.map +1 -1
  68. package/dist/Token/Token.js +12 -4
  69. package/dist/Token/TokenNamingContext.d.ts +14 -0
  70. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  71. package/dist/Token/TokenNamingContext.js +23 -0
  72. package/dist/Tokenizer/Tokenizer.d.ts +46 -59
  73. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  74. package/dist/Tokenizer/Tokenizer.js +35 -58
  75. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
  76. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  77. package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
  78. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
  79. package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
  80. package/dist/Tooltip/Tooltip.d.ts +25 -30
  81. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  82. package/dist/Tooltip/Tooltip.js +24 -5
  83. package/dist/Tooltip/index.d.ts +1 -1
  84. package/dist/Tooltip/index.d.ts.map +1 -1
  85. package/dist/Tooltip/useTooltip.d.ts +28 -29
  86. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  87. package/dist/Tooltip/useTooltip.js +20 -8
  88. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  89. package/dist/TopNav/TopNavItem.js +4 -7
  90. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  91. package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
  92. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  93. package/dist/TopNav/TopNavMenu.js +4 -7
  94. package/dist/astryx.css +5 -5
  95. package/dist/gestures/GestureHost.d.ts +27 -1
  96. package/dist/gestures/GestureHost.d.ts.map +1 -1
  97. package/dist/gestures/GestureHost.js +90 -8
  98. package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
  99. package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
  100. package/dist/hooks/useHighlightedOptionScroll.js +103 -5
  101. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  102. package/dist/hooks/useMenuPress.js +4 -2
  103. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
  104. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
  105. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
  106. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
  107. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  108. package/dist/i18n/generated-locales/en.generated.js +1 -0
  109. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  110. package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
  111. package/dist/internal/SheetConfirmation.d.ts +11 -0
  112. package/dist/internal/SheetConfirmation.d.ts.map +1 -0
  113. package/dist/internal/SheetConfirmation.js +92 -0
  114. package/dist/internal/sheetDraft.d.ts +35 -0
  115. package/dist/internal/sheetDraft.d.ts.map +1 -0
  116. package/dist/internal/sheetDraft.js +65 -0
  117. package/dist/internal/touch-sheet.d.ts +21 -0
  118. package/dist/internal/touch-sheet.d.ts.map +1 -0
  119. package/dist/internal/touch-sheet.js +19 -0
  120. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
  121. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  122. package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
  123. package/dist/motion/reducedMotion.d.ts +24 -0
  124. package/dist/motion/reducedMotion.d.ts.map +1 -1
  125. package/dist/motion/reducedMotion.js +78 -1
  126. package/dist/utils/clickGuard.d.ts +8 -1
  127. package/dist/utils/clickGuard.d.ts.map +1 -1
  128. package/dist/utils/clickGuard.js +24 -1
  129. package/locales/en.json +4 -0
  130. package/locales/pseudo.json +3 -0
  131. package/package.json +9 -9
  132. package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
  133. package/src/BottomSheet/BottomSheet.tsx +95 -10
  134. package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
  135. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  136. package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
  137. package/src/Button/Button.tsx +37 -1
  138. package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
  139. package/src/CheckboxList/CheckboxList.tsx +6 -0
  140. package/src/CheckboxList/CheckboxListContext.tsx +6 -0
  141. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  142. package/src/DateInput/DateInput.tsx +8 -2
  143. package/src/DateInput/MonthScroller.tsx +18 -22
  144. package/src/DateInput/Wheel.tsx +30 -32
  145. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  146. package/src/Dialog/Dialog.tsx +19 -4
  147. package/src/Drawer/Drawer.tsx +1 -1
  148. package/src/Drawer/useDrawerDialogPresence.ts +5 -1
  149. package/src/Field/PanelSearchInput.tsx +11 -0
  150. package/src/InfoTip/InfoTip.doc.mjs +3 -3
  151. package/src/InfoTip/InfoTip.tsx +5 -5
  152. package/src/Layer/layerAnimations.stylex.ts +24 -16
  153. package/src/Layer/usePressPin.ts +5 -11
  154. package/src/MobileNav/MobileNav.tsx +1 -1
  155. package/src/MultiSelector/hooks.ts +2 -4
  156. package/src/Outline/Outline.doc.mjs +177 -51
  157. package/src/Outline/Outline.tsx +5 -57
  158. package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
  159. package/src/Outline/useOutlineFromMarkdown.ts +1 -0
  160. package/src/Outline/useScrollSpy.ts +3 -3
  161. package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
  162. package/src/PowerSearch/PowerSearch.tsx +196 -183
  163. package/src/RadioList/RadioList.doc.mjs +4 -2
  164. package/src/RadioList/RadioList.tsx +4 -0
  165. package/src/Resizable/ResizeHandle.tsx +133 -328
  166. package/src/Resizable/resizeGesture.ts +368 -0
  167. package/src/SideNav/SideNavItem.tsx +3 -7
  168. package/src/Token/Token.tsx +13 -4
  169. package/src/Token/TokenNamingContext.ts +28 -0
  170. package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
  171. package/src/Tokenizer/Tokenizer.tsx +81 -86
  172. package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
  173. package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
  174. package/src/Tooltip/Tooltip.doc.mjs +123 -54
  175. package/src/Tooltip/Tooltip.tsx +36 -39
  176. package/src/Tooltip/index.ts +0 -1
  177. package/src/Tooltip/useTooltip.doc.mjs +147 -33
  178. package/src/Tooltip/useTooltip.tsx +39 -32
  179. package/src/TopNav/TopNavItem.tsx +6 -5
  180. package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
  181. package/src/TopNav/TopNavMenu.tsx +6 -5
  182. package/src/gestures/GestureHost.ts +105 -10
  183. package/src/hooks/useHighlightedOptionScroll.ts +113 -9
  184. package/src/hooks/useMenuPress.ts +8 -2
  185. package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
  186. package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
  187. package/src/i18n/generated-locales/en.generated.ts +1 -0
  188. package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
  189. package/src/internal/SheetConfirmation.tsx +101 -0
  190. package/src/internal/sheetDraft.ts +80 -0
  191. package/src/internal/touch-sheet.ts +21 -0
  192. package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
  193. package/src/motion/reducedMotion.ts +79 -1
  194. package/src/utils/clickGuard.ts +37 -1
  195. package/dist/internal/tokenizer-presentation.d.ts +0 -21
  196. package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
  197. package/dist/internal/tokenizer-presentation.js +0 -21
  198. package/src/internal/tokenizer-presentation.ts +0 -25
@@ -34,6 +34,7 @@ import {TokenizerClearAllMessageContext} from '../Tokenizer/TokenizerClearAllMes
34
34
  import {TokenizerItemRemovableContext} from '../Tokenizer/TokenizerItemRemovableContext';
35
35
  import {TypeaheadItem} from '../Typeahead/TypeaheadItem';
36
36
  import {Token} from '../Token';
37
+ import {TokenNamingContext} from '../Token/TokenNamingContext';
37
38
  import {Avatar} from '../Avatar';
38
39
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
39
40
  import {Icon} from '../Icon';
@@ -62,6 +63,7 @@ import {themeProps} from '../utils/themeProps';
62
63
  import {truncateCharacters} from '../utils/characters';
63
64
  import {useLocale} from '../i18n/useLocale';
64
65
  import type {Locale} from '../i18n/types';
66
+ import type {SearchableItem} from '../Typeahead/types';
65
67
  import type {
66
68
  PowerSearchConfig,
67
69
  PowerSearchFilter,
@@ -74,6 +76,7 @@ import type {
74
76
  OperatorValue,
75
77
  EnumItem,
76
78
  PowerSearchComponents,
79
+ PowerSearchOperator,
77
80
  } from './types';
78
81
 
79
82
  import {useMergedRefs} from '../hooks/useMergedRefs';
@@ -157,52 +160,35 @@ function getEnumLabel(values: ReadonlyArray<EnumItem>, value: string): string {
157
160
  return values.find(v => v.value === value)?.label ?? value;
158
161
  }
159
162
 
160
- function PowerSearchTokenValue({
161
- operatorValue,
162
- filterValue,
163
- maxLength,
164
- locale,
165
- }: {
166
- operatorValue: OperatorValue;
167
- filterValue: FilterValue;
168
- maxLength: number;
169
- locale: Locale;
170
- }) {
163
+ /**
164
+ * The visible text of a filter token's value. The token renders it, and the
165
+ * token's remove button and editor dialog are named by the same text.
166
+ */
167
+ function tokenValueText(
168
+ operatorValue: OperatorValue,
169
+ filterValue: FilterValue,
170
+ maxLength: number,
171
+ locale: Locale,
172
+ ): string | null {
171
173
  switch (filterValue.type) {
172
174
  case 'empty':
173
175
  return null;
174
176
 
175
177
  case 'string':
176
- return (
177
- <span {...stylex.props(tokenValueStyles.value)}>
178
- {truncateString(filterValue.value, maxLength)}
179
- </span>
180
- );
178
+ return truncateString(filterValue.value, maxLength);
181
179
 
182
180
  case 'integer':
183
181
  case 'float':
184
- return (
185
- <span {...stylex.props(tokenValueStyles.value)}>
186
- {filterValue.value}
187
- </span>
188
- );
182
+ return String(filterValue.value);
189
183
 
190
184
  case 'enum':
191
185
  if (operatorValue.type === 'enum') {
192
- return (
193
- <span {...stylex.props(tokenValueStyles.value)}>
194
- {truncateString(
195
- getEnumLabel(operatorValue.values, filterValue.value),
196
- maxLength,
197
- )}
198
- </span>
186
+ return truncateString(
187
+ getEnumLabel(operatorValue.values, filterValue.value),
188
+ maxLength,
199
189
  );
200
190
  }
201
- return (
202
- <span {...stylex.props(tokenValueStyles.value)}>
203
- {truncateString(filterValue.value, maxLength)}
204
- </span>
205
- );
191
+ return truncateString(filterValue.value, maxLength);
206
192
 
207
193
  case 'string_list': {
208
194
  const items = filterValue.value;
@@ -210,25 +196,13 @@ function PowerSearchTokenValue({
210
196
  return null;
211
197
  }
212
198
  if (items.length === 1) {
213
- return (
214
- <span {...stylex.props(tokenValueStyles.value)}>
215
- {truncateString(items[0], maxLength)}
216
- </span>
217
- );
199
+ return truncateString(items[0], maxLength);
218
200
  }
219
201
  const totalLength = items.reduce((sum, s) => sum + s.length, 0);
220
202
  if (totalLength > maxLength) {
221
- return (
222
- <span {...stylex.props(tokenValueStyles.value)}>
223
- {items.length} items
224
- </span>
225
- );
203
+ return `${items.length} items`;
226
204
  }
227
- return (
228
- <span {...stylex.props(tokenValueStyles.value)}>
229
- {items.join(', ')}
230
- </span>
231
- );
205
+ return items.join(', ');
232
206
  }
233
207
 
234
208
  case 'enum_list': {
@@ -239,38 +213,18 @@ function PowerSearchTokenValue({
239
213
  if (operatorValue.type === 'enum_list') {
240
214
  const labels = items.map(v => getEnumLabel(operatorValue.values, v));
241
215
  if (labels.length === 1) {
242
- return (
243
- <span {...stylex.props(tokenValueStyles.value)}>
244
- {truncateString(labels[0], maxLength)}
245
- </span>
246
- );
216
+ return truncateString(labels[0], maxLength);
247
217
  }
248
218
  const totalLength = labels.reduce((sum, s) => sum + s.length, 0);
249
219
  if (totalLength > maxLength) {
250
- return (
251
- <span {...stylex.props(tokenValueStyles.value)}>
252
- {labels.length} items
253
- </span>
254
- );
220
+ return `${labels.length} items`;
255
221
  }
256
- return (
257
- <span {...stylex.props(tokenValueStyles.value)}>
258
- {labels.join(', ')}
259
- </span>
260
- );
222
+ return labels.join(', ');
261
223
  }
262
224
  if (items.length === 1) {
263
- return (
264
- <span {...stylex.props(tokenValueStyles.value)}>
265
- {truncateString(items[0], maxLength)}
266
- </span>
267
- );
225
+ return truncateString(items[0], maxLength);
268
226
  }
269
- return (
270
- <span {...stylex.props(tokenValueStyles.value)}>
271
- {items.length} items
272
- </span>
273
- );
227
+ return `${items.length} items`;
274
228
  }
275
229
 
276
230
  case 'entity_list': {
@@ -279,80 +233,42 @@ function PowerSearchTokenValue({
279
233
  return null;
280
234
  }
281
235
  if (entities.length === 1) {
282
- return (
283
- <span {...stylex.props(tokenValueStyles.value)}>
284
- {truncateString(entities[0].label, maxLength)}
285
- </span>
286
- );
236
+ return truncateString(entities[0].label, maxLength);
287
237
  }
288
238
  const totalLength = entities.reduce((sum, e) => sum + e.label.length, 0);
289
239
  if (totalLength > maxLength) {
290
- return (
291
- <span {...stylex.props(tokenValueStyles.value)}>
292
- {entities.length} items
293
- </span>
294
- );
240
+ return `${entities.length} items`;
295
241
  }
296
- return (
297
- <span {...stylex.props(tokenValueStyles.value)}>
298
- {entities.map(e => e.label).join(', ')}
299
- </span>
300
- );
242
+ return entities.map(e => e.label).join(', ');
301
243
  }
302
244
 
303
245
  case 'time':
304
- return (
305
- <span {...stylex.props(tokenValueStyles.value)}>
306
- {filterValue.value}
307
- </span>
308
- );
246
+ return filterValue.value;
309
247
 
310
- case 'date_absolute': {
311
- const formatted = formatDateAbsoluteCompact(
312
- filterValue.unixSeconds,
313
- locale,
248
+ case 'date_absolute':
249
+ return truncateString(
250
+ formatDateAbsoluteCompact(filterValue.unixSeconds, locale),
251
+ maxLength,
314
252
  );
315
- return (
316
- <span {...stylex.props(tokenValueStyles.value)}>
317
- {truncateString(formatted, maxLength)}
318
- </span>
319
- );
320
- }
321
253
 
322
254
  case 'date_relative':
323
- return (
324
- <span {...stylex.props(tokenValueStyles.value)}>
325
- {truncateString(filterValue.value, maxLength)}
326
- </span>
327
- );
255
+ return truncateString(filterValue.value, maxLength);
328
256
 
329
257
  case 'date_range':
330
- return <span {...stylex.props(tokenValueStyles.value)}>date range</span>;
258
+ return 'date range';
331
259
 
332
260
  case 'custom':
333
261
  if (operatorValue.type === 'custom') {
334
- return (
335
- <span {...stylex.props(tokenValueStyles.value)}>
336
- {truncateString(
337
- operatorValue.getString(filterValue.value),
338
- maxLength,
339
- )}
340
- </span>
262
+ return truncateString(
263
+ operatorValue.getString(filterValue.value),
264
+ maxLength,
341
265
  );
342
266
  }
343
- return (
344
- <span {...stylex.props(tokenValueStyles.value)}>
345
- {filterValue.value}
346
- </span>
347
- );
267
+ return filterValue.value;
348
268
 
349
269
  case 'nested': {
350
270
  const count = filterValue.value.length;
351
- return (
352
- <span {...stylex.props(tokenValueStyles.value)}>
353
- {count === 1 ? '1 filter' : `${count} filters`}
354
- </span>
355
- );
271
+ return count === 1 ? '1 filter' : `${count} filters`;
356
272
  }
357
273
 
358
274
  default:
@@ -360,6 +276,10 @@ function PowerSearchTokenValue({
360
276
  }
361
277
  }
362
278
 
279
+ function PowerSearchTokenValue({text}: {text: string}) {
280
+ return <span {...stylex.props(tokenValueStyles.value)}>{text}</span>;
281
+ }
282
+
363
283
  // =============================================================================
364
284
  // Types
365
285
  // =============================================================================
@@ -490,6 +410,20 @@ type PopoverState =
490
410
  // Component
491
411
  // =============================================================================
492
412
 
413
+ /**
414
+ * Whether a press on a token with this operator commits the filter instead of
415
+ * opening its editor: an `empty` operator with no `empty` Editor override.
416
+ */
417
+ function commitsWithoutEditor(
418
+ operator: PowerSearchOperator | undefined,
419
+ componentOverrides: PowerSearchComponents | undefined,
420
+ ): boolean {
421
+ return (
422
+ operator?.value.type === 'empty' &&
423
+ componentOverrides?.empty?.Editor == null
424
+ );
425
+ }
426
+
493
427
  /**
494
428
  * Structured filter bar where each token represents a filter (field + operator + value).
495
429
  *
@@ -607,6 +541,47 @@ export function PowerSearch({
607
541
  type: 'idle',
608
542
  });
609
543
 
544
+ // A default filter token's visible text: its label (field and operator)
545
+ // and its value. The token's remove button and editor dialog are named by
546
+ // the same text.
547
+ const tokenText = useCallback(
548
+ (filter: PowerSearchFilter) => {
549
+ const field = config.getField(filter.field);
550
+ const operator = config.getOperator(filter.field, filter.operator);
551
+ const fieldLabel = field?.label ?? '';
552
+ const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
553
+ const label = `${fieldLabel}: ${operatorLabel}`.trim();
554
+ const value = operator
555
+ ? tokenValueText(
556
+ operator.value,
557
+ filter.value,
558
+ Math.max(
559
+ maxTokenLength - fieldLabel.length - operatorLabel.length,
560
+ 10,
561
+ ),
562
+ locale,
563
+ )
564
+ : null;
565
+ return {label, value, full: value != null ? `${label} ${value}` : label};
566
+ },
567
+ [config, maxTokenLength, t, locale],
568
+ );
569
+ // The built-in token being edited. A token from a `components` override
570
+ // names itself, so its editor keeps the plain popover.
571
+ const editedFilter =
572
+ popoverState.type === 'editing'
573
+ ? filters[popoverState.filterIndex]
574
+ : undefined;
575
+ const editedOperator =
576
+ editedFilter != null
577
+ ? config.getOperator(editedFilter.field, editedFilter.operator)
578
+ : undefined;
579
+ const editedBuiltInFilter =
580
+ editedOperator == null ||
581
+ componentOverrides?.[editedOperator.value.type]?.Token == null
582
+ ? editedFilter
583
+ : undefined;
584
+
610
585
  // Layer for positioning the edit popover anchored to the tokenizer
611
586
  const handleLayerHide = useCallback(() => {
612
587
  setPopoverStateRaw({type: 'idle'});
@@ -617,9 +592,16 @@ export function PowerSearch({
617
592
  hasLightDismiss: true,
618
593
  hasCloseButton: false,
619
594
  hasAutoFocus: false,
620
- // The popup's own listbox/menu content is the exposed semantics; focus
621
- // stays on the tokenizer input, so a modal dialog wrapper is incorrect.
622
- role: 'none',
595
+ // Adding: the popup's own listbox/menu content is the exposed semantics,
596
+ // and focus stays on the tokenizer input, so a dialog wrapper is
597
+ // incorrect. Editing a token: focus moves into the editor's controls, so
598
+ // the editor is a non-modal dialog named by the token's visible text.
599
+ role: editedBuiltInFilter != null ? 'dialog' : 'none',
600
+ isModal: false,
601
+ dialogLabel:
602
+ editedBuiltInFilter != null
603
+ ? tokenText(editedBuiltInFilter).full
604
+ : undefined,
623
605
  });
624
606
 
625
607
  // Wrapper that manages layer visibility and tokenizer focus alongside state
@@ -739,22 +721,38 @@ export function PowerSearch({
739
721
  value: undefined,
740
722
  },
741
723
  });
742
- } else if (change.type === 'remove' && change.item) {
743
- const auxData = change.item.auxiliaryData as PowerSearchAuxData;
744
- if (auxData?.filterIndex != null) {
745
- const newFilters = filters.filter((_, i) =>
746
- _items.some(item => item.auxiliaryData?.filterIndex === i),
724
+ } else if (change.type === 'remove') {
725
+ const keptIndices = new Set(
726
+ _items.map(item => item.auxiliaryData?.filterIndex),
727
+ );
728
+ const firstRemovedIndex = filters.findIndex(
729
+ (_, i) => !keptIndices.has(i),
730
+ );
731
+ if (firstRemovedIndex >= 0) {
732
+ onChange(
733
+ filters.filter((_, i) => keptIndices.has(i)),
734
+ 'remove',
735
+ firstRemovedIndex,
747
736
  );
748
- onChange(newFilters, 'remove', auxData.filterIndex);
749
737
  }
750
738
  }
751
739
  },
752
740
  [config, filters, onChange, setPopoverState],
753
741
  );
754
742
 
755
- // Tokenizer's Backspace does not go through renderToken. This predicate
756
- // locks every token of a read-only field.
757
- const isItemRemovable = useCallback(() => !isReadOnly, [isReadOnly]);
743
+ // Tokenizer's Backspace and Clear all do not go through renderToken. This
744
+ // predicate locks each read-only filter, and every token of a read-only
745
+ // field.
746
+ const isItemRemovable = useCallback(
747
+ (item: SearchableItem) => {
748
+ if (isReadOnly) {
749
+ return false;
750
+ }
751
+ const filterIndex = (item as PowerSearchItem).auxiliaryData?.filterIndex;
752
+ return filterIndex == null || !filters[filterIndex]?.isReadOnly;
753
+ },
754
+ [filters, isReadOnly],
755
+ );
758
756
 
759
757
  // Handle clicking a token to edit
760
758
  const handleTokenClick = useCallback(
@@ -772,10 +770,7 @@ export function PowerSearch({
772
770
  // edit: commit it as that editor's save would, the way adding one does,
773
771
  // instead of opening it. A consumer's `empty` Editor override still opens.
774
772
  const operator = config.getOperator(filter.field, filter.operator);
775
- if (
776
- operator?.value.type === 'empty' &&
777
- componentOverrides?.empty?.Editor == null
778
- ) {
773
+ if (commitsWithoutEditor(operator, componentOverrides)) {
779
774
  const newFilters = [...filters];
780
775
  newFilters[index] = {
781
776
  field: filter.field,
@@ -880,20 +875,16 @@ export function PowerSearch({
880
875
  // Default token rendering
881
876
  const fieldLabel = field?.label ?? '';
882
877
  const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
883
- const tokenLabel = `${fieldLabel}: ${operatorLabel}`.trim();
884
- const adjustedMaxLength = Math.max(
885
- maxTokenLength - fieldLabel.length - operatorLabel.length,
886
- 10,
887
- );
888
-
878
+ const text =
879
+ filter != null
880
+ ? tokenText(filter)
881
+ : {label: `${fieldLabel}: ${operatorLabel}`.trim(), value: null};
882
+ const tokenLabel = text.label;
883
+ const fullText =
884
+ text.value != null ? `${text.label} ${text.value}` : text.label;
889
885
  const valueContent =
890
- operator && filter ? (
891
- <PowerSearchTokenValue
892
- operatorValue={operator.value}
893
- filterValue={filter.value}
894
- maxLength={adjustedMaxLength}
895
- locale={locale}
896
- />
886
+ text.value != null ? (
887
+ <PowerSearchTokenValue text={text.value} />
897
888
  ) : undefined;
898
889
 
899
890
  // Show entity photo as token icon for single-entity filters
@@ -907,32 +898,49 @@ export function PowerSearch({
907
898
  tokenIcon = <Avatar src={entity.photo} name={entity.label} size={16} />;
908
899
  }
909
900
 
901
+ // A token that opens its editor says that it opens a dialog, and the
902
+ // naming context names it by all its visible text.
903
+ const editorProps =
904
+ handleClick != null &&
905
+ !commitsWithoutEditor(operator, componentOverrides)
906
+ ? {
907
+ 'aria-haspopup': 'dialog' as const,
908
+ 'aria-expanded':
909
+ popoverState.type === 'editing' &&
910
+ popoverState.filterIndex === filterIndex,
911
+ }
912
+ : null;
913
+
910
914
  return (
911
- <Token
912
- label={tokenLabel}
913
- size={size}
914
- icon={tokenIcon}
915
- endContent={valueContent}
916
- onMouseDown={
917
- handleClick
918
- ? e => {
919
- // Keep a token press from entering Tokenizer's input-focus
920
- // redirect. Click still edits/removes; Tab entry is unchanged.
921
- e.preventDefault();
922
- }
923
- : undefined
924
- }
925
- onClick={
926
- handleClick
927
- ? (e: React.MouseEvent) => {
928
- e.stopPropagation();
929
- handleClick();
930
- }
931
- : undefined
932
- }
933
- onRemove={handleRemove}
934
- isDisabled={isDisabled}
935
- />
915
+ <TokenNamingContext
916
+ value={{actionName: fullText, removeName: fullText}}>
917
+ <Token
918
+ label={tokenLabel}
919
+ size={size}
920
+ icon={tokenIcon}
921
+ {...editorProps}
922
+ endContent={valueContent}
923
+ onMouseDown={
924
+ handleClick
925
+ ? e => {
926
+ // Keep a token press from entering Tokenizer's input-focus
927
+ // redirect. Click still edits/removes; Tab entry is unchanged.
928
+ e.preventDefault();
929
+ }
930
+ : undefined
931
+ }
932
+ onClick={
933
+ handleClick
934
+ ? (e: React.MouseEvent) => {
935
+ e.stopPropagation();
936
+ handleClick();
937
+ }
938
+ : undefined
939
+ }
940
+ onRemove={handleRemove}
941
+ isDisabled={isDisabled}
942
+ />
943
+ </TokenNamingContext>
936
944
  );
937
945
  },
938
946
  [
@@ -940,13 +948,14 @@ export function PowerSearch({
940
948
  config,
941
949
  configProp,
942
950
  maxTokenLength,
943
- locale,
944
951
  size,
945
952
  isReadOnly,
946
953
  isDisabled,
947
954
  handleTokenClick,
948
955
  componentOverrides,
949
956
  t,
957
+ popoverState,
958
+ tokenText,
950
959
  ],
951
960
  );
952
961
 
@@ -1077,9 +1086,13 @@ export function PowerSearch({
1077
1086
  const announce = useAnnounce();
1078
1087
  // PowerSearch words its Clear all as its record does; the field announces
1079
1088
  // these words instead of Tokenizer's (component:Tokenizer AR7).
1089
+ const hasReadOnlyFilter = filters.some(filter => filter.isReadOnly);
1080
1090
  const clearAllMessage = useMemo(
1081
- () => t('@astryx.powersearch.allFiltersRemoved'),
1082
- [t],
1091
+ () =>
1092
+ hasReadOnlyFilter
1093
+ ? t('@astryx.powersearch.editableFiltersRemoved')
1094
+ : t('@astryx.powersearch.allFiltersRemoved'),
1095
+ [hasReadOnlyFilter, t],
1083
1096
  );
1084
1097
  const announcedResultCountRef = useRef(resultCountText);
1085
1098
  useEffect(() => {
@@ -148,12 +148,14 @@ export const docs = {
148
148
  {
149
149
  name: 'containerClassName',
150
150
  type: 'string',
151
- description: 'Class name for the field container. Same targeting as `containerXstyle`.',
151
+ description:
152
+ 'Class name for the field container. Same targeting as `containerXstyle`.',
152
153
  },
153
154
  {
154
155
  name: 'containerStyle',
155
156
  type: 'CSSProperties',
156
- description: 'Inline styles for the field container. Same targeting as `containerXstyle`.',
157
+ description:
158
+ 'Inline styles for the field container. Same targeting as `containerXstyle`.',
157
159
  },
158
160
  {
159
161
  name: 'controlXstyle',
@@ -403,6 +403,10 @@ export function RadioList({
403
403
  disabledMessageTooltip.ref(el);
404
404
  }}
405
405
  role="radiogroup"
406
+ // Screen readers announce the whole group as unavailable. Its dimmed
407
+ // label then labels a disabled control. An enabled list keeps a
408
+ // caller's own value.
409
+ aria-disabled={isDisabled || rest['aria-disabled']}
406
410
  // A caller aria-labelledby names the group together with its label.
407
411
  aria-labelledby={joinAriaIDs(labelID, ariaLabelledByProp)}
408
412
  onFocus={composeEventHandlers(handleFocus, onFocus)}