@astryxdesign/core 0.5.0-canary.86c10d7 → 0.5.0-canary.8985bb9

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.
@@ -175,6 +175,23 @@ export interface MultiSelectorProps<T extends MultiSelectorOptionType = MultiSel
175
175
  * @default 'Search...'
176
176
  */
177
177
  searchPlaceholder?: string;
178
+ /**
179
+ * Content shown in the panel when there are no options to show, and
180
+ * announced in a polite live region when the panel opens. Not shown while
181
+ * `isLoading` — the options have not arrived yet.
182
+ * @default 'No options'
183
+ */
184
+ emptyText?: ReactNode;
185
+ /**
186
+ * Content shown in the panel when a search query matches no options, and
187
+ * announced in a polite live region at the same time.
188
+ *
189
+ * The panel message is `role="presentation"`, so the live region is the only
190
+ * route to assistive tech: a string is announced verbatim, a richer node
191
+ * falls back to the default text since it cannot be spoken.
192
+ * @default 'No results found'
193
+ */
194
+ emptySearchText?: ReactNode;
178
195
  /**
179
196
  * How to display selected items in the trigger.
180
197
  * - 'count': "3 selected"
@@ -236,7 +253,7 @@ export interface MultiSelectorProps<T extends MultiSelectorOptionType = MultiSel
236
253
  * />
237
254
  * ```
238
255
  */
239
- export declare function MultiSelector<T extends MultiSelectorOptionType>({ label, isLabelHidden, description, isOptional, isRequired, isDisabled, disabledMessage, options, value, onChange, changeAction, isLoading, placeholder: placeholderFromProps, size: sizeProp, variant, status, statusVariant, labelTooltip, startIcon, hasClear, hasSelectAll, selectAllLabel: selectAllLabelFromProps, hasSearch, searchPlaceholder: searchPlaceholderFromProps, triggerDisplay, formatValue, maxBadges, renderOption, indicatorPosition, isDefaultOpen, 'data-testid': testId, htmlName, width, xstyle, className, style, }: MultiSelectorProps<T>): React.JSX.Element;
256
+ export declare function MultiSelector<T extends MultiSelectorOptionType>({ label, isLabelHidden, description, isOptional, isRequired, isDisabled, disabledMessage, options, value, onChange, changeAction, isLoading, placeholder: placeholderFromProps, size: sizeProp, variant, status, statusVariant, labelTooltip, startIcon, hasClear, hasSelectAll, selectAllLabel: selectAllLabelFromProps, hasSearch, searchPlaceholder: searchPlaceholderFromProps, emptyText: emptyTextFromProps, emptySearchText: emptySearchTextFromProps, triggerDisplay, formatValue, maxBadges, renderOption, indicatorPosition, isDefaultOpen, 'data-testid': testId, htmlName, width, xstyle, className, style, }: MultiSelectorProps<T>): React.JSX.Element;
240
257
  export declare namespace MultiSelector {
241
258
  var displayName: string;
242
259
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"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"}
@@ -275,6 +275,8 @@ export function MultiSelector({
275
275
  selectAllLabel: selectAllLabelFromProps,
276
276
  hasSearch = false,
277
277
  searchPlaceholder: searchPlaceholderFromProps,
278
+ emptyText: emptyTextFromProps,
279
+ emptySearchText: emptySearchTextFromProps,
278
280
  triggerDisplay = 'count',
279
281
  formatValue,
280
282
  maxBadges = 3,
@@ -296,6 +298,8 @@ export function MultiSelector({
296
298
  const placeholder = placeholderFromProps ?? t('@astryx.multiSelector.selectPlaceholder');
297
299
  const selectAllLabel = selectAllLabelFromProps ?? t('@astryx.multiSelector.selectAll');
298
300
  const searchPlaceholder = searchPlaceholderFromProps ?? t('@astryx.multiSelector.searchPlaceholder');
301
+ const emptyText = emptyTextFromProps ?? t('@astryx.multiSelector.empty');
302
+ const emptySearchText = emptySearchTextFromProps ?? t('@astryx.multiSelector.emptySearchResults');
299
303
  const size = useSize(sizeProp, 'md');
300
304
  const effectiveStatusVariant = variant === 'ghost' && statusVariant === 'attached' ? 'detached' : statusVariant;
301
305
  const triggerId = useId();
@@ -348,6 +352,13 @@ export function MultiSelector({
348
352
  // Announce selection-count changes politely (comboboxes-7 announce path).
349
353
  // Toggling options / select-all previously produced no audible feedback.
350
354
  const announce = useAnnounce();
355
+
356
+ // The panel's empty message is role="presentation" and reaches assistive tech
357
+ // only through this live region, so the region has to speak whatever the
358
+ // panel shows. A ReactNode override cannot be spoken; fall back to the
359
+ // catalog copy for that case rather than announcing nothing.
360
+ const emptyAnnouncement = typeof emptyText === 'string' ? emptyText : t('@astryx.multiSelector.empty');
361
+ const emptySearchAnnouncement = typeof emptySearchText === 'string' ? emptySearchText : t('@astryx.multiSelector.emptySearchResults');
351
362
  const announceSelection = useCallback(nextValue => {
352
363
  const total = selectableItems.length;
353
364
  const selectableSet = new Set(selectableItems.map(item => item.value));
@@ -454,11 +465,38 @@ export function MultiSelector({
454
465
  announce('');
455
466
  return;
456
467
  }
468
+ // While isLoading the panel deliberately shows nothing, so announcing a
469
+ // result would put a claim in the one channel the screen has gone quiet
470
+ // for.
471
+ if (isLoading) {
472
+ announce('');
473
+ return;
474
+ }
457
475
  const count = filterOptionsByQuery(selectableItems, nextQuery).length;
458
- announce(count === 0 ? t('@astryx.multiSelector.emptySearchResults') : t('@astryx.multiSelector.resultCount', {
476
+ announce(count === 0 ? emptySearchAnnouncement : t('@astryx.multiSelector.resultCount', {
459
477
  count
460
478
  }));
461
- }, [announce, selectableItems, t]);
479
+ }, [announce, isLoading, selectableItems, emptySearchAnnouncement, t]);
480
+
481
+ // The panel's empty message is role="presentation", so this region is the
482
+ // only route to assistive tech. It has to watch the STATE rather than the
483
+ // open event: the panel can become empty either on open or when a fetch
484
+ // lands with nothing in it, and an open-only announcement leaves the second
485
+ // case silent while the message sits on screen. The ref makes it fire once
486
+ // per arrival at that state rather than on every re-render.
487
+ const announcedEmptyRef = useRef(null);
488
+ useEffect(() => {
489
+ const isPanelEmpty = popover.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
490
+ if (!isPanelEmpty) {
491
+ announcedEmptyRef.current = null;
492
+ return;
493
+ }
494
+ if (announcedEmptyRef.current === emptyAnnouncement) {
495
+ return;
496
+ }
497
+ announcedEmptyRef.current = emptyAnnouncement;
498
+ announce(emptyAnnouncement);
499
+ }, [popover.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
462
500
 
463
501
  // Handle toggle
464
502
  // Clear all selected values. Shared by the clear button and the keyboard
@@ -800,13 +838,16 @@ export function MultiSelector({
800
838
  // message out of the listbox's accessibility tree (role="listbox" only
801
839
  // permits option/group children); the no-results outcome is announced
802
840
  // via the result-count live region instead.
803
- if (realItemCount === 0) {
841
+ // While isLoading the options have not arrived yet, so asserting either
842
+ // message would be a claim the component cannot make; the trigger's
843
+ // spinner covers it.
844
+ if (realItemCount === 0 && !isLoading) {
804
845
  elements.push(/*#__PURE__*/_jsx("div", {
805
846
  role: "presentation",
806
847
  ...mergeProps(themeProps('multi-selector-empty-state'), {
807
848
  className: "x1b2ylru x2b8uid xv1l7n4 x9ynric xcr08ib"
808
849
  }),
809
- children: "No results found"
850
+ children: searchQuery ? emptySearchText : emptyText
810
851
  }, "empty"));
811
852
  return elements;
812
853
  }
@@ -877,7 +918,7 @@ export function MultiSelector({
877
918
  }
878
919
  flushPending();
879
920
  return elements;
880
- }, [options, renderItem, sortedItems, searchQuery, hasSelectAll]);
921
+ }, [options, renderItem, sortedItems, searchQuery, hasSelectAll, isLoading, emptyText, emptySearchText]);
881
922
 
882
923
  // The detached message box renders its own leading status icon, so the
883
924
  // on-field icon would duplicate it — keep the chevron indicator instead.
@@ -7,9 +7,21 @@
7
7
  * People paste numbers out of spreadsheets, and a spreadsheet writes them
8
8
  * grouped, signed, and decorated. This reads those back. Everything locale
9
9
  * specific — the grouping and decimal separators, the group sizes, the digit
10
- * script — is derived from Intl and Unicode rather than a table, and anything
11
- * that could mean two different numbers returns null so the field stays
12
- * visibly invalid instead of committing a guess.
10
+ * script — is derived from Intl and Unicode rather than a table, and text that
11
+ * no reading fits returns null so the field stays visibly invalid instead of
12
+ * committing a guess. Where both readings are well formed the locale's wins,
13
+ * the way utils/dateParser.ts settles an ambiguous date by locale preference.
14
+ * Grouping is well formed only when every group fits the locale's own sizes:
15
+ * the last exactly the primary size, each inner one exactly the secondary, the
16
+ * first no longer than that. So de-DE `1.234` is 1234, while `1.2345` (last
17
+ * group too long) and `1234.567` (too much ahead of it) keep the full stop as
18
+ * a decimal point. Groups of three fit every locale, so a separator repeated
19
+ * with three-digit groups is grouping in all of them and `1,234,567` reads the
20
+ * same everywhere; for a space or apostrophe, which no locale writes as a
21
+ * decimal point, one occurrence is enough, so en-US `1 234` is 1234.
22
+ *
23
+ * That paragraph is executable: numberParser.docblock.test.ts implements it as
24
+ * code, quote by quote, and fails when the parser and the words disagree.
13
25
  *
14
26
  * Which characters may separate digits at all is the one thing that is NOT
15
27
  * read off the input: SEPARATOR_CHARS below is a bounded alphabet, the same
@@ -18,6 +30,7 @@
18
30
  * SYNC: When modified, update these files to stay in sync:
19
31
  * - /packages/core/src/NumberInput/NumberInput.tsx
20
32
  * - /packages/core/src/NumberInput/numberParser.test.ts
33
+ * - /packages/core/src/NumberInput/numberParser.docblock.test.ts
21
34
  */
22
35
  import type { Locale } from '../i18n';
23
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"numberParser.d.ts","sourceRoot":"","sources":["../../src/NumberInput/numberParser.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,SAAS,CAAC;AA6OpC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CA8Ef;AAaD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3E"}
1
+ {"version":3,"file":"numberParser.d.ts","sourceRoot":"","sources":["../../src/NumberInput/numberParser.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,SAAS,CAAC;AAiPpC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CA8Ef;AAaD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3E"}
@@ -9,9 +9,21 @@
9
9
  * People paste numbers out of spreadsheets, and a spreadsheet writes them
10
10
  * grouped, signed, and decorated. This reads those back. Everything locale
11
11
  * specific — the grouping and decimal separators, the group sizes, the digit
12
- * script — is derived from Intl and Unicode rather than a table, and anything
13
- * that could mean two different numbers returns null so the field stays
14
- * visibly invalid instead of committing a guess.
12
+ * script — is derived from Intl and Unicode rather than a table, and text that
13
+ * no reading fits returns null so the field stays visibly invalid instead of
14
+ * committing a guess. Where both readings are well formed the locale's wins,
15
+ * the way utils/dateParser.ts settles an ambiguous date by locale preference.
16
+ * Grouping is well formed only when every group fits the locale's own sizes:
17
+ * the last exactly the primary size, each inner one exactly the secondary, the
18
+ * first no longer than that. So de-DE `1.234` is 1234, while `1.2345` (last
19
+ * group too long) and `1234.567` (too much ahead of it) keep the full stop as
20
+ * a decimal point. Groups of three fit every locale, so a separator repeated
21
+ * with three-digit groups is grouping in all of them and `1,234,567` reads the
22
+ * same everywhere; for a space or apostrophe, which no locale writes as a
23
+ * decimal point, one occurrence is enough, so en-US `1 234` is 1234.
24
+ *
25
+ * That paragraph is executable: numberParser.docblock.test.ts implements it as
26
+ * code, quote by quote, and fails when the parser and the words disagree.
15
27
  *
16
28
  * Which characters may separate digits at all is the one thing that is NOT
17
29
  * read off the input: SEPARATOR_CHARS below is a bounded alphabet, the same
@@ -20,6 +32,7 @@
20
32
  * SYNC: When modified, update these files to stay in sync:
21
33
  * - /packages/core/src/NumberInput/NumberInput.tsx
22
34
  * - /packages/core/src/NumberInput/numberParser.test.ts
35
+ * - /packages/core/src/NumberInput/numberParser.docblock.test.ts
23
36
  */
24
37
 
25
38
  const symbolsByLocale = new Map();
@@ -64,15 +77,19 @@ const INVISIBLES = /[\u200B-\u200D\u200E\u200F\u061C\u2066-\u2069\uFEFF]/g;
64
77
  const MINUS_SIGNS = '\u2212\u2012\u2013-';
65
78
 
66
79
  /**
67
- * Every character a locale writes between digits: comma, full stop, space
68
- * (the whole space family folds to it under NFKC), the Swiss apostrophes, the
69
- * Catalan middle dot, and the Arabic decimal and thousands separators.
80
+ * Every character a locale writes between digits. Sweeping every language ICU
81
+ * resolves, in every region, yields seven: comma, full stop, apostrophe,
82
+ * space (the whole space family folds to it under NFKC), the Arabic decimal
83
+ * and thousands separators, and U+060C — which only nqo writes, and which
84
+ * this alphabet leaves out. U+2019 is here in its place: no locale writes it,
85
+ * but the corpus pins it as the Swiss spelling.
70
86
  *
71
87
  * The alphabet is bounded on purpose. Read the candidates off the input
72
88
  * instead and any repeated character is grouping, so a hyphenated ID commits
73
- * as a number.
89
+ * as a number. It bounds characters and not shapes: a full stop is a real
90
+ * separator, so `192.168.100.200` is four well-formed groups.
74
91
  */
75
- const SEPARATOR_CHARS = ",.' \u2019\u00B7\u066B\u066C";
92
+ const SEPARATOR_CHARS = ",.' \u2019\u066B\u066C";
76
93
  const NUMBER_BODY = new RegExp(`^[0-9${SEPARATOR_CHARS}]+$`);
77
94
  /** No grouping and at most one full stop: the format `Number()` reads. */
78
95
  const MACHINE_NUMBER = /^(?:\d+\.?\d*|\.\d+)$/;
@@ -167,7 +184,7 @@ function readUnder(core, group, decimal, primaryGroupSize, secondaryGroupSize, m
167
184
  * beats the decimal-point reading.
168
185
  */
169
186
  function isGroupOnlySeparator(separator) {
170
- return /^[\s'\u2019\u00B7]$/.test(separator);
187
+ return /^[\s'\u2019]$/.test(separator);
171
188
  }
172
189
  function toPlainNumber(core, locale) {
173
190
  if (!NUMBER_BODY.test(core)) {
@@ -189,6 +189,23 @@ interface SelectorPropsBase<T extends SelectorOptionType = SelectorOptionType> e
189
189
  * @default 'Search...'
190
190
  */
191
191
  searchPlaceholder?: string;
192
+ /**
193
+ * Content shown in the panel when there are no options to show, and
194
+ * announced in a polite live region when the panel opens. Not shown while
195
+ * `isLoading` — the options have not arrived yet.
196
+ * @default 'No options'
197
+ */
198
+ emptyText?: ReactNode;
199
+ /**
200
+ * Content shown in the panel when a search query matches no options, and
201
+ * announced in a polite live region at the same time.
202
+ *
203
+ * The panel message is `role="presentation"`, so the live region is the only
204
+ * route to assistive tech: a string is announced verbatim, a richer node
205
+ * falls back to the default text since it cannot be spoken.
206
+ * @default 'No results found'
207
+ */
208
+ emptySearchText?: ReactNode;
192
209
  /**
193
210
  * Position placement relative to the trigger.
194
211
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"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"}
@@ -308,6 +308,8 @@ export function Selector(props) {
308
308
  indicatorPosition = 'end',
309
309
  hasSearch = false,
310
310
  searchPlaceholder: searchPlaceholderFromProps,
311
+ emptyText: emptyTextFromProps,
312
+ emptySearchText: emptySearchTextFromProps,
311
313
  placement,
312
314
  isDefaultOpen = false,
313
315
  'data-testid': testId,
@@ -324,6 +326,8 @@ export function Selector(props) {
324
326
  });
325
327
  const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
326
328
  const searchPlaceholder = searchPlaceholderFromProps ?? t('@astryx.selector.searchPlaceholder');
329
+ const emptyText = emptyTextFromProps ?? t('@astryx.selector.empty');
330
+ const emptySearchText = emptySearchTextFromProps ?? t('@astryx.selector.emptySearchResults');
327
331
  const hasClear = hasClearProp === true;
328
332
  const size = useSize(sizeProp, 'md');
329
333
  const effectiveStatusVariant = variant === 'ghost' && statusVariant === 'attached' ? 'detached' : statusVariant;
@@ -343,6 +347,9 @@ export function Selector(props) {
343
347
  const searchRef = useRef(null);
344
348
  const inputGroup = useInputGroup();
345
349
  const [searchQuery, setSearchQuery] = useState('');
350
+ // Mirrors searchQuery for the seed path, which runs before focus reaches the
351
+ // input and so cannot read the rendered state.
352
+ const searchQueryRef = useRef('');
346
353
  // A typed query shows the search row's clear (✕) button, which becomes
347
354
  // the next tab stop after the search input.
348
355
  const hasQuery = searchQuery.length > 0;
@@ -351,6 +358,13 @@ export function Selector(props) {
351
358
  const isBusy = isLoading || optimisticValue !== normalizedValue;
352
359
  const announce = useAnnounce();
353
360
 
361
+ // The panel's empty message is role="presentation" and reaches assistive tech
362
+ // only through this live region, so the region has to speak whatever the
363
+ // panel shows. A ReactNode override cannot be spoken; fall back to the
364
+ // catalog copy for that case rather than announcing nothing.
365
+ const emptyAnnouncement = typeof emptyText === 'string' ? emptyText : t('@astryx.selector.empty');
366
+ const emptySearchAnnouncement = typeof emptySearchText === 'string' ? emptySearchText : t('@astryx.selector.emptySearchResults');
367
+
354
368
  // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
355
369
  // tooltip listeners attach to the trigger container (which already exists)
356
370
  // and the trigger button stays perceivable via aria-disabled instead of the
@@ -398,6 +412,7 @@ export function Selector(props) {
398
412
  // Layer for dropdown positioning
399
413
  const handleLayerHide = useCallback(() => {
400
414
  setSearchQuery('');
415
+ searchQueryRef.current = '';
401
416
  resetTypeaheadRef.current();
402
417
  // Clear any lingering result count when the popover closes so stale status
403
418
  // text does not linger in the a11y tree.
@@ -425,22 +440,55 @@ export function Selector(props) {
425
440
  // eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
426
441
  }, []);
427
442
 
428
- // Announce the filtered result count from the query-change handler (matching
443
+ // Announce the filtered result count from the query-change handlers (matching
429
444
  // BaseTypeahead) rather than a reactive effect: computing the count for the
430
445
  // next query here fires the announcement exactly once per keystroke and does
431
- // not re-speak on unrelated re-renders.
432
- const handleSearchChange = useCallback(nextQuery => {
433
- setSearchQuery(nextQuery);
446
+ // not re-speak on unrelated re-renders. Split out from handleSearchChange so
447
+ // the type-to-open seed below reaches the same live region — a query the user
448
+ // typed is a query however it arrived.
449
+ const announceSearchResults = useCallback(nextQuery => {
434
450
  if (nextQuery.length === 0) {
435
451
  // Emptying the query clears the region rather than announcing a count.
436
452
  announce('');
437
453
  return;
438
454
  }
455
+ // While isLoading the panel deliberately shows nothing, so announcing a
456
+ // result would put a claim in the one channel the screen has gone quiet
457
+ // for.
458
+ if (isLoading) {
459
+ announce('');
460
+ return;
461
+ }
439
462
  const count = filterOptionsByQuery(selectableItems, nextQuery).length;
440
- announce(count === 0 ? t('@astryx.selector.emptySearchResults') : t('@astryx.selector.resultCount', {
463
+ announce(count === 0 ? emptySearchAnnouncement : t('@astryx.selector.resultCount', {
441
464
  count
442
465
  }));
443
- }, [announce, selectableItems, t]);
466
+ }, [announce, isLoading, selectableItems, emptySearchAnnouncement, t]);
467
+ const handleSearchChange = useCallback(nextQuery => {
468
+ setSearchQuery(nextQuery);
469
+ searchQueryRef.current = nextQuery;
470
+ announceSearchResults(nextQuery);
471
+ }, [announceSearchResults]);
472
+
473
+ // The panel's empty message is role="presentation", so this region is the
474
+ // only route to assistive tech. It has to watch the STATE rather than the
475
+ // open event: the panel can become empty either on open or when a fetch
476
+ // lands with nothing in it, and an open-only announcement leaves the second
477
+ // case silent while the message sits on screen. The ref makes it fire once
478
+ // per arrival at that state rather than on every re-render.
479
+ const announcedEmptyRef = useRef(null);
480
+ useEffect(() => {
481
+ const isPanelEmpty = popover.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
482
+ if (!isPanelEmpty) {
483
+ announcedEmptyRef.current = null;
484
+ return;
485
+ }
486
+ if (announcedEmptyRef.current === emptyAnnouncement) {
487
+ return;
488
+ }
489
+ announcedEmptyRef.current = emptyAnnouncement;
490
+ announce(emptyAnnouncement);
491
+ }, [popover.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
444
492
 
445
493
  // Calculate offset to position selected item over trigger. Explicit
446
494
  // placement opts out of the selector-specific overlay behavior and uses the
@@ -477,10 +525,17 @@ export function Selector(props) {
477
525
  }, [onChange, changeAction, startTransition, setOptimisticValue]);
478
526
 
479
527
  // Type-to-find appends to the query rather than replacing it: characters
480
- // typed before focus reaches the search input must not be dropped.
528
+ // typed before focus reaches the search input must not be dropped. The ref
529
+ // is what makes that safe without a state updater — it advances
530
+ // synchronously, so a second character seeded in the same tick still sees
531
+ // the first, and the announcement can be computed here rather than inside a
532
+ // setState callback.
481
533
  const appendSearchQuery = useCallback(char => {
482
- setSearchQuery(query => query + char);
483
- }, []);
534
+ const nextQuery = searchQueryRef.current + char;
535
+ searchQueryRef.current = nextQuery;
536
+ setSearchQuery(nextQuery);
537
+ announceSearchResults(nextQuery);
538
+ }, [announceSearchResults]);
484
539
  const commitValue = useCallback(newValue => {
485
540
  onChange?.(newValue);
486
541
  if (changeAction) {
@@ -732,8 +787,11 @@ export function Selector(props) {
732
787
  const renderOptions = useCallback(() => {
733
788
  const isSearching = hasSearch && Boolean(searchQuery);
734
789
 
735
- // Nothing matched across every group/option: show the empty state.
736
- if (isSearching && filteredItems.length === 0) {
790
+ // Nothing to show — either the query matched nothing, or no options were
791
+ // given at all. Both render the same slot with different copy. While
792
+ // isLoading the options have not arrived yet, so asserting either would be
793
+ // a claim the component cannot make; the trigger's spinner covers it.
794
+ if (filteredItems.length === 0 && !isLoading) {
737
795
  // role="presentation" keeps the message out of the listbox's
738
796
  // accessibility tree (role="listbox" only permits option/group
739
797
  // children); the no-results outcome is announced via the
@@ -743,7 +801,7 @@ export function Selector(props) {
743
801
  ...mergeProps(themeProps('selector-empty-state'), {
744
802
  className: "x1b2ylru x2b8uid xv1l7n4 x9ynric xcr08ib"
745
803
  }),
746
- children: "No results found"
804
+ children: isSearching ? emptySearchText : emptyText
747
805
  }, "empty")];
748
806
  }
749
807
  let flatIndex = 0;
@@ -805,7 +863,7 @@ export function Selector(props) {
805
863
  }
806
864
  }
807
865
  return elements;
808
- }, [options, renderItem, hasSearch, searchQuery, filteredItems]);
866
+ }, [options, renderItem, hasSearch, searchQuery, filteredItems, isLoading, emptyText, emptySearchText]);
809
867
 
810
868
  // The detached message box renders its own leading status icon, so the
811
869
  // on-field icon would duplicate it — keep the chevron indicator instead.
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../src/Slider/Slider.tsx"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAIhD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAQ9C,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC3C,8DAA8D;IAC9D,KAAK,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,8CAA8C;IAC9C,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC7C,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAChD,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAoS/D;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,WAAW,+BAmnBlD;yBAnnBe,MAAM"}
1
+ {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../src/Slider/Slider.tsx"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAQhD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAQ9C,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC3C,8DAA8D;IAC9D,KAAK,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,8CAA8C;IAC9C,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC7C,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAChD,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAoS/D;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,WAAW,+BAgqBlD;yBAhqBe,MAAM"}
@@ -15,7 +15,7 @@
15
15
  * - /apps/storybook/stories/Slider.stories.tsx
16
16
  * - /packages/cli/assets/templates/blocks/components/Slider/ (showcase blocks)
17
17
  */
18
- import { useId, useMemo, useRef, useState, useCallback } from 'react';
18
+ import { useEffect, useId, useMemo, useRef, useState, useCallback } from 'react';
19
19
  import * as stylex from '@stylexjs/stylex';
20
20
  import "../theme/tokens.stylex.js";
21
21
  import { colorVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
@@ -25,6 +25,7 @@ import { useTooltip } from "../Tooltip/index.js";
25
25
  import { VisuallyHidden } from "../VisuallyHidden/index.js";
26
26
  import { mergeProps, rtlStyles } from "../utils/index.js";
27
27
  import { focusOutlineStyles } from "../utils/focusOutline.stylex.js";
28
+ import { getInteractionModality, trackInteractionModality } from "../utils/interactionModality.js";
28
29
  import { isRtlElement } from "../hooks/isRtlElement.js";
29
30
  import { themeProps } from "../utils/themeProps.js";
30
31
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
@@ -249,6 +250,24 @@ export function Slider({
249
250
  const draggingThumbRef = useRef(null);
250
251
  const [draggingThumb, setDraggingThumb] = useState(null);
251
252
 
253
+ // A thumb is a div[role="slider"], and `handlePointerDown` focuses it from
254
+ // script after preventDefault — which Chromium treats as focus-visible, so
255
+ // dragging with a mouse drew the keyboard ring (measured: `:focus-visible`
256
+ // true on pointerdown). Gate the ring on how the user last interacted; the
257
+ // CSS condition stays `:focus-visible`, this only narrows it.
258
+ const [keyboardFocusThumb, setKeyboardFocusThumb] = useState(null);
259
+ useEffect(() => {
260
+ trackInteractionModality();
261
+ }, []);
262
+ const handleThumbFocus = useCallback((thumbIndex, _e) => {
263
+ // A disabled thumb draws no ring even when it stays focusable for its
264
+ // reason tooltip, so there is nothing to track for one.
265
+ setKeyboardFocusThumb(!isDisabled && getInteractionModality() === 'keyboard' ? thumbIndex : null);
266
+ }, [isDisabled]);
267
+ const handleThumbBlur = useCallback(_e => {
268
+ setKeyboardFocusThumb(null);
269
+ }, []);
270
+
252
271
  // Disabled-reason tooltip. This is a *separate* useTooltip instance from the
253
272
  // per-thumb value bubble (the `<Tooltip>` component below): it anchors to the
254
273
  // track container and fires on hover/focus of the whole control. Disabled
@@ -387,6 +406,10 @@ export function Slider({
387
406
  const thumbs = track.querySelectorAll('[role="slider"]');
388
407
  thumbs[thumbIndex]?.focus();
389
408
  }
409
+ // Also clear it explicitly: focusing an already-focused thumb fires no
410
+ // focus event, so a thumb the user had tabbed to would keep its ring
411
+ // through the drag.
412
+ setKeyboardFocusThumb(null);
390
413
  if (typeof e.currentTarget.setPointerCapture === 'function') {
391
414
  e.currentTarget.setPointerCapture(e.pointerId);
392
415
  }
@@ -411,6 +434,13 @@ export function Slider({
411
434
  if (isDisabled) {
412
435
  return;
413
436
  }
437
+ // Unlike a text field, a thumb has no caret to show where input is
438
+ // going, so a keypress after a mouse drag must bring the ring back.
439
+ // Ask the utility rather than assuming: a modifier chord (⌘R, ⌃C) is
440
+ // not navigation and must not re-ring a thumb the mouse is holding.
441
+ if (getInteractionModality() === 'keyboard') {
442
+ setKeyboardFocusThumb(thumbIndex);
443
+ }
414
444
  const currentVal = values[thumbIndex];
415
445
  let newVal;
416
446
  switch (e.key) {
@@ -517,10 +547,12 @@ export function Slider({
517
547
  "aria-labelledby": !isRange ? labelID : undefined,
518
548
  "aria-describedby": ariaDescribedBy,
519
549
  onKeyDown: e => handleKeyDown(thumbIndex, e),
550
+ onFocus: e => handleThumbFocus(thumbIndex, e),
551
+ onBlur: handleThumbBlur,
520
552
  ...mergeProps(themeProps('slider-thumb', {
521
553
  orientation,
522
554
  disabled: isDisabled ? 'disabled' : null
523
- }), stylex.props(styles.thumb, isHorizontal ? styles.thumbHorizontal : rtlStyles.centerInline('50%'), !isDisabled && styles.thumbHover, !isDisabled && focusOutlineStyles.focusVisible, isDisabled && styles.thumbDisabled), undefined, positionStyle)
555
+ }), stylex.props(styles.thumb, isHorizontal ? styles.thumbHorizontal : rtlStyles.centerInline('50%'), !isDisabled && styles.thumbHover, !isDisabled && keyboardFocusThumb === thumbIndex && focusOutlineStyles.focusVisible, isDisabled && styles.thumbDisabled), undefined, positionStyle)
524
556
  }, thumbIndex);
525
557
  if (useValueTooltip) {
526
558
  return /*#__PURE__*/_jsx(Tooltip, {
package/locales/en.json CHANGED
@@ -511,6 +511,10 @@
511
511
  "defaultMessage": "Search options",
512
512
  "description": "Screen-reader-only accessible name for that same search input inside a MultiSelector."
513
513
  },
514
+ "@astryx.multiSelector.empty": {
515
+ "defaultMessage": "No options",
516
+ "description": "Shown in a MultiSelector's dropdown panel when it was given no options at all, and announced in a polite live region on open. Very short (2 words); neutral tone, not error-y."
517
+ },
514
518
  "@astryx.multiSelector.selectAllPartiallySelected": {
515
519
  "defaultMessage": "{label}, partially selected",
516
520
  "description": "Accessible name for the MultiSelector select-all option while only some options are selected. `{label}` is the visible select-all label (e.g. \"Select all\"). ARIA forbids aria-selected=\"mixed\" on options, so the indeterminate state is conveyed through the name instead. \"Partially\" = some but not all."
@@ -527,6 +531,10 @@
527
531
  "defaultMessage": "Search options",
528
532
  "description": "\"Options\" = the list of choices in the dropdown. Screen-reader-only accessible name for the search input inside a Selector."
529
533
  },
534
+ "@astryx.selector.empty": {
535
+ "defaultMessage": "No options",
536
+ "description": "Shown in a Selector's dropdown panel when it was given no options at all, and announced in a polite live region on open. Very short (2 words); neutral tone, not error-y."
537
+ },
530
538
  "@astryx.sideNav.label": {
531
539
  "defaultMessage": "Side navigation",
532
540
  "description": "Screen-reader-only accessible name for the primary vertical sidebar nav (usually on the left)."
@@ -1205,7 +1213,7 @@
1205
1213
  },
1206
1214
  "@astryx.multiSelector.emptySearchResults": {
1207
1215
  "defaultMessage": "No results found",
1208
- "description": "Polite screen-reader announcement when a MultiSelector search query matches no options. Live-region text, never shown on screen; neutral tone (not error-y)."
1216
+ "description": "Shown in a MultiSelector's dropdown panel when the search query matches no options, and announced in a polite live region at the same time. Short; neutral tone (not error-y)."
1209
1217
  },
1210
1218
  "@astryx.multiSelector.resultCount": {
1211
1219
  "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
@@ -1213,7 +1221,7 @@
1213
1221
  },
1214
1222
  "@astryx.selector.emptySearchResults": {
1215
1223
  "defaultMessage": "No results found",
1216
- "description": "Polite screen-reader announcement when a Selector search query matches no options. Live-region text, never shown on screen; neutral tone (not error-y)."
1224
+ "description": "Shown in a Selector's dropdown panel when the search query matches no options, and announced in a polite live region at the same time. Short; neutral tone (not error-y)."
1217
1225
  },
1218
1226
  "@astryx.selector.resultCount": {
1219
1227
  "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
@@ -383,6 +383,9 @@
383
383
  "@astryx.multiSelector.searchOptions": {
384
384
  "defaultMessage": "⟦Šéàřçĥ óþţíóñš⟧"
385
385
  },
386
+ "@astryx.multiSelector.empty": {
387
+ "defaultMessage": "⟦Ñó óþţíóñš⟧"
388
+ },
386
389
  "@astryx.multiSelector.selectAllPartiallySelected": {
387
390
  "defaultMessage": "⟦{label}, þàřţíàłłý šéłéçţéð⟧"
388
391
  },
@@ -395,6 +398,9 @@
395
398
  "@astryx.selector.searchOptions": {
396
399
  "defaultMessage": "⟦Šéàřçĥ óþţíóñš⟧"
397
400
  },
401
+ "@astryx.selector.empty": {
402
+ "defaultMessage": "⟦Ñó óþţíóñš⟧"
403
+ },
398
404
  "@astryx.sideNav.label": {
399
405
  "defaultMessage": "⟦Šíðé ñàṽíĝàţíóñ⟧"
400
406
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.5.0-canary.86c10d7",
3
+ "version": "0.5.0-canary.8985bb9",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -661,7 +661,7 @@
661
661
  "react-dom": ">=19.0.0"
662
662
  },
663
663
  "devDependencies": {
664
- "@astryxdesign/cli": "0.5.0-canary.86c10d7",
664
+ "@astryxdesign/cli": "0.5.0-canary.8985bb9",
665
665
  "@babel/cli": "^8.0.4",
666
666
  "@babel/core": "^7.29.7",
667
667
  "@babel/preset-react": "^8.0.1",