@astryxdesign/core 0.7.6-canary.19ae6fc → 0.7.6-canary.25f61ea
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.
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +27 -6
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +37 -30
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +15 -4
- package/dist/Tokenizer/TokenizerClearAllMessageContext.d.ts +2 -0
- package/dist/Tokenizer/TokenizerClearAllMessageContext.d.ts.map +1 -0
- package/dist/Tokenizer/TokenizerClearAllMessageContext.js +17 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/locales/en.json +5 -1
- package/locales/pseudo.json +3 -0
- package/package.json +4 -4
- package/src/CodeBlock/__tests__/CodeBlockCopyClearance.a11y.chromium.spec.ts +38 -3
- package/src/Layer/useLayer.test.tsx +78 -3
- package/src/Layer/useLayer.tsx +27 -6
- package/src/PowerSearch/PowerSearch.test.tsx +24 -0
- package/src/PowerSearch/PowerSearch.tsx +42 -31
- package/src/Tokenizer/Tokenizer.spec.md +13 -1
- package/src/Tokenizer/Tokenizer.test.tsx +27 -0
- package/src/Tokenizer/Tokenizer.tsx +24 -10
- package/src/Tokenizer/TokenizerBottomSheet.test.tsx +8 -0
- package/src/Tokenizer/TokenizerClearAllMessageContext.ts +21 -0
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
- package/src/motion/README.md +2 -1
- package/src/motion/roles.test.ts +5 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"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"}
|
package/dist/Layer/useLayer.js
CHANGED
|
@@ -328,8 +328,9 @@ function failsAnchor(layer, anchor) {
|
|
|
328
328
|
* every show and remount, and while the layer is open whenever its size, its
|
|
329
329
|
* content's size, its anchor's size or the viewport changes; each of those
|
|
330
330
|
* reads lands before the next frame paints. A same-size anchor that moves on
|
|
331
|
-
* screen, in layout or because something scrolled, is
|
|
332
|
-
* the anchor-move observer
|
|
331
|
+
* screen, in layout or because something scrolled, is reported after paint by
|
|
332
|
+
* the anchor-move observer and read in the next frame, before it paints (FR8).
|
|
333
|
+
* Scroll events are not read: during one the
|
|
333
334
|
* anchor has moved but the layer anchored to it not yet, so the read would
|
|
334
335
|
* compare a moved anchor with a stale layer. Once switched, a layer keeps its
|
|
335
336
|
* anchor-clear list until it next opens, so it never jumps back over its
|
|
@@ -469,9 +470,16 @@ function observeMove(element, onMove) {
|
|
|
469
470
|
* range (spec:AST-059 FR4, FR8): the layer, its content or the anchor
|
|
470
471
|
* resizing (ResizeObserver, before paint), the viewport resizing (before
|
|
471
472
|
* paint), and the anchor moving on screen without resizing, in layout or by a
|
|
472
|
-
* scroll (`observeMove`, after paint). Each calls `keepAnchorClear`
|
|
473
|
-
* `isCurrent` says they are still the live
|
|
474
|
-
*
|
|
473
|
+
* scroll (`observeMove`, reported after paint). Each calls `keepAnchorClear`
|
|
474
|
+
* for this layer and anchor while `isCurrent` says they are still the live
|
|
475
|
+
* pair. A move is read in the next animation frame, not in the task that
|
|
476
|
+
* reports it: placing the layer afresh there, right after the frame that
|
|
477
|
+
* scrolled its anchor, lets Chromium lay it out for one more frame against
|
|
478
|
+
* the anchor's position before the scroll, over the anchor, so the second
|
|
479
|
+
* painted frame would still cover it. Read in the next frame, before it
|
|
480
|
+
* paints, the fresh placement uses the moved anchor. Reports in one frame
|
|
481
|
+
* share one read. Returns the function that removes every observer and
|
|
482
|
+
* listener it added.
|
|
475
483
|
*/
|
|
476
484
|
function observeAnchorClearance(layer, anchor, isCurrent) {
|
|
477
485
|
const view = layer.ownerDocument.defaultView;
|
|
@@ -492,11 +500,24 @@ function observeAnchorClearance(layer, anchor, isCurrent) {
|
|
|
492
500
|
}
|
|
493
501
|
observer.observe(anchor);
|
|
494
502
|
}
|
|
495
|
-
|
|
503
|
+
let moveFrame = null;
|
|
504
|
+
const stopObservingMove = observeMove(anchor, () => {
|
|
505
|
+
if (moveFrame !== null) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
moveFrame = view.requestAnimationFrame(() => {
|
|
509
|
+
moveFrame = null;
|
|
510
|
+
reread();
|
|
511
|
+
});
|
|
512
|
+
});
|
|
496
513
|
view.addEventListener('resize', reread);
|
|
497
514
|
return () => {
|
|
498
515
|
observer?.disconnect();
|
|
499
516
|
stopObservingMove();
|
|
517
|
+
if (moveFrame !== null) {
|
|
518
|
+
view.cancelAnimationFrame(moveFrame);
|
|
519
|
+
moveFrame = null;
|
|
520
|
+
}
|
|
500
521
|
view.removeEventListener('resize', reread);
|
|
501
522
|
};
|
|
502
523
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAGL,KAAK,yBAAyB,EAC/B,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"PowerSearch.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearch.tsx"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
17
17
|
import * as stylex from '@stylexjs/stylex';
|
|
18
18
|
import { Tokenizer } from "../Tokenizer/index.js";
|
|
19
|
+
import { TokenizerClearAllMessageContext } from "../Tokenizer/TokenizerClearAllMessageContext.js";
|
|
19
20
|
import { TypeaheadItem } from "../Typeahead/TypeaheadItem.js";
|
|
20
21
|
import { Token } from "../Token/index.js";
|
|
21
22
|
import { Avatar } from "../Avatar/index.js";
|
|
@@ -769,6 +770,9 @@ export function PowerSearch({
|
|
|
769
770
|
// replay (which resets a flag and would speak the count present on mount
|
|
770
771
|
// unprompted) announces nothing until the count changes.
|
|
771
772
|
const announce = useAnnounce();
|
|
773
|
+
// PowerSearch words its Clear all as its record does; the field announces
|
|
774
|
+
// these words instead of Tokenizer's (component:Tokenizer AR7).
|
|
775
|
+
const clearAllMessage = useMemo(() => t('@astryx.powersearch.allFiltersRemoved'), [t]);
|
|
772
776
|
const announcedResultCountRef = useRef(resultCountText);
|
|
773
777
|
useEffect(() => {
|
|
774
778
|
if (resultCountText === announcedResultCountRef.current) {
|
|
@@ -799,36 +803,39 @@ export function PowerSearch({
|
|
|
799
803
|
children: [/*#__PURE__*/_jsx("div", {
|
|
800
804
|
ref: useMergedRefs(ref, popover.triggerRef),
|
|
801
805
|
...themeProps('power-search'),
|
|
802
|
-
children: /*#__PURE__*/_jsx(
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
806
|
+
children: /*#__PURE__*/_jsx(TokenizerClearAllMessageContext, {
|
|
807
|
+
value: clearAllMessage,
|
|
808
|
+
children: /*#__PURE__*/_jsx(Tokenizer, {
|
|
809
|
+
handleRef: tokenizerRef,
|
|
810
|
+
label: label,
|
|
811
|
+
isLabelHidden: isLabelHidden,
|
|
812
|
+
searchSource: searchSource,
|
|
813
|
+
value: tokenizerValue,
|
|
814
|
+
onChange: handleTokenizerChange,
|
|
815
|
+
renderToken: renderToken,
|
|
816
|
+
renderItem: renderItem,
|
|
817
|
+
maxMenuItems: MAX_BROWSE_MENU_ITEMS,
|
|
818
|
+
menuWidth: menuWidth,
|
|
819
|
+
placeholder: filters.length === 0 ? placeholder : '',
|
|
820
|
+
hasAutoFocus: hasAutoFocus,
|
|
821
|
+
hasClear: hasClear && !isReadOnly,
|
|
822
|
+
startIcon: startIcon,
|
|
823
|
+
endContent: combinedEndContent,
|
|
824
|
+
isDisabled: isDisabled,
|
|
825
|
+
disabledMessage: disabledMessage,
|
|
826
|
+
size: size,
|
|
827
|
+
tokenOverflowBehavior: tokenOverflowBehavior,
|
|
828
|
+
hasEntriesOnFocus: true,
|
|
829
|
+
debounceMs: 0,
|
|
830
|
+
status: status,
|
|
831
|
+
statusVariant: statusVariant,
|
|
832
|
+
onFocus: onFocus,
|
|
833
|
+
onBlur: onBlur,
|
|
834
|
+
containerXstyle: xstyle,
|
|
835
|
+
containerClassName: className,
|
|
836
|
+
containerStyle: style,
|
|
837
|
+
"data-testid": testId
|
|
838
|
+
})
|
|
832
839
|
})
|
|
833
840
|
}), popover.render(popoverContent, {
|
|
834
841
|
placement: 'below',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tokenizer.d.ts","sourceRoot":"","sources":["../../src/Tokenizer/Tokenizer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Tokenizer.d.ts","sourceRoot":"","sources":["../../src/Tokenizer/Tokenizer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -23,12 +23,13 @@
|
|
|
23
23
|
* - /apps/storybook/stories/Tokenizer.stories.tsx
|
|
24
24
|
* - /packages/cli/assets/templates/blocks/components/Tokenizer/ (showcase blocks)
|
|
25
25
|
*/
|
|
26
|
-
import React, { lazy, Suspense, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
26
|
+
import React, { lazy, Suspense, use, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
27
27
|
import * as stylex from '@stylexjs/stylex';
|
|
28
28
|
import { BusyIndicatorLaneProvider, createBusyIndicatorLane, useIsBusy } from "../Typeahead/busyIndicatorLane.js";
|
|
29
29
|
import { TrailingKeyDownContext } from "../Typeahead/trailingKeyDown.js";
|
|
30
30
|
import { useFieldInputStyling } from "../Field/fieldParts.js";
|
|
31
31
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
32
|
+
import { TokenizerClearAllMessageContext } from "./TokenizerClearAllMessageContext.js";
|
|
32
33
|
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
33
34
|
import { useAdaptivePresentation } from "../hooks/useAdaptivePresentation.js";
|
|
34
35
|
import { useIsHydrating } from "../hooks/useIsHydrating.js";
|
|
@@ -832,7 +833,10 @@ export function Tokenizer({
|
|
|
832
833
|
}, [value, onChange, isDisabled, announce, t]);
|
|
833
834
|
|
|
834
835
|
// Handle clearing all items
|
|
835
|
-
|
|
836
|
+
// A composing component that words its own Clear all (PowerSearch) provides
|
|
837
|
+
// its words; otherwise the field's own (component:Tokenizer AR7).
|
|
838
|
+
const clearAllMessage = use(TokenizerClearAllMessageContext) ?? t('@astryx.tokenizer.allRemoved');
|
|
839
|
+
const handleClearAll = useCallback(options => {
|
|
836
840
|
if (isDisabled || value.length === 0) {
|
|
837
841
|
return;
|
|
838
842
|
}
|
|
@@ -842,8 +846,13 @@ export function Tokenizer({
|
|
|
842
846
|
item: lastItem,
|
|
843
847
|
type: 'remove'
|
|
844
848
|
});
|
|
849
|
+
// Focus moves to the input, so the emptied selection is announced, in
|
|
850
|
+
// the sheet's words (WCAG 4.1.3). The sheet announces its own Clear all.
|
|
851
|
+
if (!options?.silent) {
|
|
852
|
+
announce(clearAllMessage);
|
|
853
|
+
}
|
|
845
854
|
inputRef.current?.focus();
|
|
846
|
-
}, [value, onChange, isDisabled]);
|
|
855
|
+
}, [value, onChange, isDisabled, announce, clearAllMessage]);
|
|
847
856
|
|
|
848
857
|
// Handle backspace on empty input — remove last token
|
|
849
858
|
const handleKeyDown = useCallback(e => {
|
|
@@ -1149,7 +1158,9 @@ export function Tokenizer({
|
|
|
1149
1158
|
emptySearchText: emptySearchText,
|
|
1150
1159
|
onAdd: handleAdd,
|
|
1151
1160
|
onRemove: handleRemove,
|
|
1152
|
-
onClearAll: handleClearAll
|
|
1161
|
+
onClearAll: () => handleClearAll({
|
|
1162
|
+
silent: true
|
|
1163
|
+
}),
|
|
1153
1164
|
onChangeQuery: handleSheetQueryChange
|
|
1154
1165
|
})]
|
|
1155
1166
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenizerClearAllMessageContext.d.ts","sourceRoot":"","sources":["../../src/Tokenizer/TokenizerClearAllMessageContext.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,+BAA+B,wCAE3C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file TokenizerClearAllMessageContext.ts
|
|
7
|
+
* @input None
|
|
8
|
+
* @output A context carrying the words a composing component announces for
|
|
9
|
+
* the field's Clear all
|
|
10
|
+
* @position Internal to Core: not exported from the Tokenizer barrel. A
|
|
11
|
+
* component that composes Tokenizer and words its own Clear all (PowerSearch:
|
|
12
|
+
* "All filters removed") provides it; Tokenizer otherwise announces its own
|
|
13
|
+
* words (component:Tokenizer AR7).
|
|
14
|
+
*/
|
|
15
|
+
import { createContext } from 'react';
|
|
16
|
+
export const TokenizerClearAllMessageContext = /*#__PURE__*/createContext(null);
|
|
17
|
+
TokenizerClearAllMessageContext.displayName = 'TokenizerClearAllMessageContext';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/en.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"en.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/en.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0mBpD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -288,6 +288,7 @@ const runtimeCatalog = {
|
|
|
288
288
|
'@astryx.outline.label': 'Table of contents',
|
|
289
289
|
'@astryx.powersearch.label': 'Search',
|
|
290
290
|
'@astryx.powersearch.placeholder': 'Search…',
|
|
291
|
+
'@astryx.powersearch.allFiltersRemoved': 'All filters removed',
|
|
291
292
|
'@astryx.resizable.collapsed': 'Collapsed',
|
|
292
293
|
'@astryx.resizable.handle.label': 'Resize handle',
|
|
293
294
|
'@astryx.selector.placeholder': 'Select…',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pseudo.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/pseudo.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"pseudo.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/pseudo.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqnBpD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -288,6 +288,7 @@ const runtimeCatalog = {
|
|
|
288
288
|
'@astryx.outline.label': '⟦Ţàƀłé óƒ çóñţéñţš⟧',
|
|
289
289
|
'@astryx.powersearch.label': '⟦Šéàřçĥ⟧',
|
|
290
290
|
'@astryx.powersearch.placeholder': '⟦Šéàřçĥ…⟧',
|
|
291
|
+
'@astryx.powersearch.allFiltersRemoved': '⟦Àłł ƒíłţéřš řéɱóṽéð⟧',
|
|
291
292
|
'@astryx.resizable.collapsed': '⟦Çółłàþšéð⟧',
|
|
292
293
|
'@astryx.resizable.handle.label': '⟦Řéšížé ĥàñðłé⟧',
|
|
293
294
|
'@astryx.selector.placeholder': '⟦Šéłéçţ…⟧',
|
package/locales/en.json
CHANGED
|
@@ -1123,6 +1123,10 @@
|
|
|
1123
1123
|
"defaultMessage": "Search…",
|
|
1124
1124
|
"description": "Grey placeholder text inside the empty PowerSearch input. Imperative verb; trailing `…` is one character."
|
|
1125
1125
|
},
|
|
1126
|
+
"@astryx.powersearch.allFiltersRemoved": {
|
|
1127
|
+
"defaultMessage": "All filters removed",
|
|
1128
|
+
"description": "Screen-reader announcement after a PowerSearch field's Clear all removes every filter. Distinct from @astryx.tokenizer.allRemoved: PowerSearch words its items as filters."
|
|
1129
|
+
},
|
|
1126
1130
|
"@astryx.resizable.collapsed": {
|
|
1127
1131
|
"defaultMessage": "Collapsed",
|
|
1128
1132
|
"description": "aria-valuetext for the Resizable drag handle (role=separator) while its panel is collapsed to zero size. Replaces the numeric value announcement, since the numeric value is clamped to the minimum while collapsed. Adjective describing the panel state, not a verb/command."
|
|
@@ -1741,7 +1745,7 @@
|
|
|
1741
1745
|
},
|
|
1742
1746
|
"@astryx.tokenizer.allRemoved": {
|
|
1743
1747
|
"defaultMessage": "All items removed",
|
|
1744
|
-
"description": "Polite screen-reader announcement after a
|
|
1748
|
+
"description": "Polite screen-reader announcement after a Tokenizer's Clear all has removed every selected item: the inline Clear all button, or a confirmed Clear all in the bottom sheet. Live-region text, never shown on screen."
|
|
1745
1749
|
},
|
|
1746
1750
|
"@astryx.tokenizer.selectedCount": {
|
|
1747
1751
|
"defaultMessage": "{count, number} selected",
|
package/locales/pseudo.json
CHANGED
|
@@ -842,6 +842,9 @@
|
|
|
842
842
|
"@astryx.powersearch.placeholder": {
|
|
843
843
|
"defaultMessage": "⟦Šéàřçĥ…⟧"
|
|
844
844
|
},
|
|
845
|
+
"@astryx.powersearch.allFiltersRemoved": {
|
|
846
|
+
"defaultMessage": "⟦Àłł ƒíłţéřš řéɱóṽéð⟧"
|
|
847
|
+
},
|
|
845
848
|
"@astryx.resizable.collapsed": {
|
|
846
849
|
"defaultMessage": "⟦Çółłàþšéð⟧"
|
|
847
850
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.6-canary.
|
|
3
|
+
"version": "0.7.6-canary.25f61ea",
|
|
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",
|
|
@@ -847,9 +847,9 @@
|
|
|
847
847
|
"react-dom": ">=19.0.0"
|
|
848
848
|
},
|
|
849
849
|
"devDependencies": {
|
|
850
|
-
"@astryxdesign/a11y-spec": "0.7.6-canary.
|
|
851
|
-
"@astryxdesign/cli": "0.7.6-canary.
|
|
852
|
-
"@astryxdesign/interaction-harness": "0.7.6-canary.
|
|
850
|
+
"@astryxdesign/a11y-spec": "0.7.6-canary.25f61ea",
|
|
851
|
+
"@astryxdesign/cli": "0.7.6-canary.25f61ea",
|
|
852
|
+
"@astryxdesign/interaction-harness": "0.7.6-canary.25f61ea",
|
|
853
853
|
"@babel/cli": "^7.29.7",
|
|
854
854
|
"@babel/core": "^7.29.7",
|
|
855
855
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -39,9 +39,24 @@ async function mount(
|
|
|
39
39
|
);
|
|
40
40
|
await expect(copyButton(page)).toBeVisible();
|
|
41
41
|
if (Object.keys(args).length > 0) {
|
|
42
|
+
// Measure only the story as these args render it. Storybook emits
|
|
43
|
+
// `storyRendered` for the story's own first render too, shortly after it
|
|
44
|
+
// is visible, so the update waits for that render to finish first; the
|
|
45
|
+
// `storyRendered` it then waits for is the re-render with the args, and a
|
|
46
|
+
// re-render that never comes fails here instead of being measured. Each
|
|
47
|
+
// arg the cases pass is also checked in the page, so a slow runner can
|
|
48
|
+
// never measure the default story in its place.
|
|
49
|
+
await page.waitForFunction(
|
|
50
|
+
() =>
|
|
51
|
+
(
|
|
52
|
+
window as unknown as {
|
|
53
|
+
__STORYBOOK_PREVIEW__?: {currentRender?: {phase?: string}};
|
|
54
|
+
}
|
|
55
|
+
).__STORYBOOK_PREVIEW__?.currentRender?.phase === 'finished',
|
|
56
|
+
);
|
|
42
57
|
await page.evaluate(
|
|
43
58
|
async ({storyId, updatedArgs}) =>
|
|
44
|
-
new Promise<void>(resolve => {
|
|
59
|
+
new Promise<void>((resolve, reject) => {
|
|
45
60
|
const channel = (
|
|
46
61
|
window as unknown as {
|
|
47
62
|
__STORYBOOK_ADDONS_CHANNEL__: {
|
|
@@ -50,12 +65,32 @@ async function mount(
|
|
|
50
65
|
};
|
|
51
66
|
}
|
|
52
67
|
).__STORYBOOK_ADDONS_CHANNEL__;
|
|
53
|
-
|
|
68
|
+
const timer = setTimeout(
|
|
69
|
+
() =>
|
|
70
|
+
reject(new Error('the story did not re-render with its args')),
|
|
71
|
+
20_000,
|
|
72
|
+
);
|
|
73
|
+
channel.once('storyRendered', () => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve();
|
|
76
|
+
});
|
|
54
77
|
channel.emit('updateStoryArgs', {storyId, updatedArgs});
|
|
55
|
-
setTimeout(resolve, 1500);
|
|
56
78
|
}),
|
|
57
79
|
{storyId: STORY, updatedArgs: args},
|
|
58
80
|
);
|
|
81
|
+
if (typeof args.code === 'string') {
|
|
82
|
+
await expect(
|
|
83
|
+
page.locator('pre.astryx-code-block [data-line]'),
|
|
84
|
+
).toHaveCount(args.code.split('\n').length);
|
|
85
|
+
}
|
|
86
|
+
if (typeof args.title === 'string') {
|
|
87
|
+
await expect(page.getByText(args.title, {exact: true})).toBeVisible();
|
|
88
|
+
}
|
|
89
|
+
if (args.isWrapped === true) {
|
|
90
|
+
await expect(
|
|
91
|
+
page.locator('pre.astryx-code-block [data-line="1"]'),
|
|
92
|
+
).toHaveCSS('white-space', 'pre-wrap');
|
|
93
|
+
}
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
96
|
|
|
@@ -2233,6 +2233,16 @@ describe('a layer that keeps its anchor clear (spec:AST-059 FR4)', () => {
|
|
|
2233
2233
|
|
|
2234
2234
|
const TRIGGER = rect(300, 400, 30, 100);
|
|
2235
2235
|
|
|
2236
|
+
/** Let the next animation frame run, as the browser does before it paints. */
|
|
2237
|
+
async function nextFrame() {
|
|
2238
|
+
await act(
|
|
2239
|
+
async () =>
|
|
2240
|
+
new Promise<void>(resolve => {
|
|
2241
|
+
requestAnimationFrame(() => resolve());
|
|
2242
|
+
}),
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2236
2246
|
/** Give the document a 720px-tall viewport for the duration of a test. */
|
|
2237
2247
|
function viewport720() {
|
|
2238
2248
|
return vi
|
|
@@ -2540,10 +2550,74 @@ describe('a layer that keeps its anchor clear (spec:AST-059 FR4)', () => {
|
|
|
2540
2550
|
placeBoxes(TRIGGER, rect(16, 350, 688, 300));
|
|
2541
2551
|
const before = observers.length;
|
|
2542
2552
|
act(() => move?.callback([{intersectionRatio: 0.4}]));
|
|
2543
|
-
|
|
2544
|
-
// Re-armed on the new rect.
|
|
2553
|
+
// Re-armed on the new rect at once; read in the next frame.
|
|
2545
2554
|
expect(move?.disconnected).toBe(true);
|
|
2546
2555
|
expect(observers.length).toBe(before + 1);
|
|
2556
|
+
await nextFrame();
|
|
2557
|
+
expect(layerEl).toHaveAttribute('data-astryx-clears-anchor');
|
|
2558
|
+
vi.unstubAllGlobals();
|
|
2559
|
+
});
|
|
2560
|
+
|
|
2561
|
+
it('reads a move its observer reports after paint in the next frame, before that frame paints, never in the reporting task', async () => {
|
|
2562
|
+
// Placing a layer afresh in the task right after the frame that scrolled
|
|
2563
|
+
// its anchor lets Chromium lay it out for one more frame against the
|
|
2564
|
+
// anchor's position before the scroll, over the anchor (spec:AST-059 FR8:
|
|
2565
|
+
// clear from the second painted frame). The read waits for the next
|
|
2566
|
+
// frame, before it paints, and several reports in one frame share it.
|
|
2567
|
+
const observers: {
|
|
2568
|
+
callback: (entries: {intersectionRatio: number}[]) => void;
|
|
2569
|
+
options: IntersectionObserverInit;
|
|
2570
|
+
}[] = [];
|
|
2571
|
+
vi.stubGlobal(
|
|
2572
|
+
'IntersectionObserver',
|
|
2573
|
+
class {
|
|
2574
|
+
constructor(
|
|
2575
|
+
callback: (entries: {intersectionRatio: number}[]) => void,
|
|
2576
|
+
options: IntersectionObserverInit,
|
|
2577
|
+
) {
|
|
2578
|
+
observers.push({callback, options});
|
|
2579
|
+
}
|
|
2580
|
+
observe() {}
|
|
2581
|
+
disconnect() {}
|
|
2582
|
+
unobserve() {}
|
|
2583
|
+
},
|
|
2584
|
+
);
|
|
2585
|
+
const latestMove = () =>
|
|
2586
|
+
observers.filter(o => o.options?.rootMargin != null).at(-1);
|
|
2587
|
+
const user = userEvent.setup();
|
|
2588
|
+
const spy = placeBoxes(TRIGGER, rect(200, 350, 100, 200));
|
|
2589
|
+
const {container, getByText} = render(<ClearHarness />);
|
|
2590
|
+
await user.click(getByText('Trigger'));
|
|
2591
|
+
const layerEl = container.querySelector('[popover]') as HTMLElement;
|
|
2592
|
+
act(() => latestMove()?.callback([{intersectionRatio: 1}]));
|
|
2593
|
+
await nextFrame();
|
|
2594
|
+
expect(layerEl).not.toHaveAttribute('data-astryx-clears-anchor');
|
|
2595
|
+
|
|
2596
|
+
// A scroll carries the trigger under the layer; the observer reports it
|
|
2597
|
+
// after the frame that painted the move, twice before the next frame.
|
|
2598
|
+
spy.mockRestore();
|
|
2599
|
+
const reads = vi.fn();
|
|
2600
|
+
const covering = rect(16, 350, 688, 300);
|
|
2601
|
+
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(
|
|
2602
|
+
function (this: HTMLElement) {
|
|
2603
|
+
if (!this.hasAttribute('popover')) {
|
|
2604
|
+
return TRIGGER;
|
|
2605
|
+
}
|
|
2606
|
+
reads();
|
|
2607
|
+
return covering;
|
|
2608
|
+
},
|
|
2609
|
+
);
|
|
2610
|
+
act(() => {
|
|
2611
|
+
latestMove()?.callback([{intersectionRatio: 0.4}]);
|
|
2612
|
+
latestMove()?.callback([{intersectionRatio: 0.6}]);
|
|
2613
|
+
});
|
|
2614
|
+
expect(layerEl).not.toHaveAttribute('data-astryx-clears-anchor');
|
|
2615
|
+
expect(reads).not.toHaveBeenCalled();
|
|
2616
|
+
|
|
2617
|
+
// The next frame reads it once, switches it, and places it afresh.
|
|
2618
|
+
await nextFrame();
|
|
2619
|
+
expect(layerEl).toHaveAttribute('data-astryx-clears-anchor');
|
|
2620
|
+
expect(reads).toHaveBeenCalled();
|
|
2547
2621
|
vi.unstubAllGlobals();
|
|
2548
2622
|
});
|
|
2549
2623
|
|
|
@@ -2595,9 +2669,10 @@ describe('a layer that keeps its anchor clear (spec:AST-059 FR4)', () => {
|
|
|
2595
2669
|
spy = placeBoxes(TRIGGER, rect(16, 350, 688, 300));
|
|
2596
2670
|
let armed = moves().length;
|
|
2597
2671
|
act(() => latest().callback([{intersectionRatio: 0.7}]));
|
|
2598
|
-
expect(layerEl).toHaveAttribute('data-astryx-clears-anchor');
|
|
2599
2672
|
expect(moves().length).toBe(armed + 1);
|
|
2600
2673
|
expect(latest().options.threshold).toBe(0.7);
|
|
2674
|
+
await nextFrame();
|
|
2675
|
+
expect(layerEl).toHaveAttribute('data-astryx-clears-anchor');
|
|
2601
2676
|
|
|
2602
2677
|
// A first report of 0 for a trigger that moved since the observer read
|
|
2603
2678
|
// its rectangle re-arms at once on the new rectangle.
|
package/src/Layer/useLayer.tsx
CHANGED
|
@@ -627,8 +627,9 @@ function failsAnchor(layer: HTMLElement, anchor: HTMLElement): boolean {
|
|
|
627
627
|
* every show and remount, and while the layer is open whenever its size, its
|
|
628
628
|
* content's size, its anchor's size or the viewport changes; each of those
|
|
629
629
|
* reads lands before the next frame paints. A same-size anchor that moves on
|
|
630
|
-
* screen, in layout or because something scrolled, is
|
|
631
|
-
* the anchor-move observer
|
|
630
|
+
* screen, in layout or because something scrolled, is reported after paint by
|
|
631
|
+
* the anchor-move observer and read in the next frame, before it paints (FR8).
|
|
632
|
+
* Scroll events are not read: during one the
|
|
632
633
|
* anchor has moved but the layer anchored to it not yet, so the read would
|
|
633
634
|
* compare a moved anchor with a stale layer. Once switched, a layer keeps its
|
|
634
635
|
* anchor-clear list until it next opens, so it never jumps back over its
|
|
@@ -779,9 +780,16 @@ function observeMove(element: HTMLElement, onMove: () => void): () => void {
|
|
|
779
780
|
* range (spec:AST-059 FR4, FR8): the layer, its content or the anchor
|
|
780
781
|
* resizing (ResizeObserver, before paint), the viewport resizing (before
|
|
781
782
|
* paint), and the anchor moving on screen without resizing, in layout or by a
|
|
782
|
-
* scroll (`observeMove`, after paint). Each calls `keepAnchorClear`
|
|
783
|
-
* `isCurrent` says they are still the live
|
|
784
|
-
*
|
|
783
|
+
* scroll (`observeMove`, reported after paint). Each calls `keepAnchorClear`
|
|
784
|
+
* for this layer and anchor while `isCurrent` says they are still the live
|
|
785
|
+
* pair. A move is read in the next animation frame, not in the task that
|
|
786
|
+
* reports it: placing the layer afresh there, right after the frame that
|
|
787
|
+
* scrolled its anchor, lets Chromium lay it out for one more frame against
|
|
788
|
+
* the anchor's position before the scroll, over the anchor, so the second
|
|
789
|
+
* painted frame would still cover it. Read in the next frame, before it
|
|
790
|
+
* paints, the fresh placement uses the moved anchor. Reports in one frame
|
|
791
|
+
* share one read. Returns the function that removes every observer and
|
|
792
|
+
* listener it added.
|
|
785
793
|
*/
|
|
786
794
|
function observeAnchorClearance(
|
|
787
795
|
layer: HTMLElement,
|
|
@@ -806,11 +814,24 @@ function observeAnchorClearance(
|
|
|
806
814
|
}
|
|
807
815
|
observer.observe(anchor);
|
|
808
816
|
}
|
|
809
|
-
|
|
817
|
+
let moveFrame: number | null = null;
|
|
818
|
+
const stopObservingMove = observeMove(anchor, () => {
|
|
819
|
+
if (moveFrame !== null) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
moveFrame = view.requestAnimationFrame(() => {
|
|
823
|
+
moveFrame = null;
|
|
824
|
+
reread();
|
|
825
|
+
});
|
|
826
|
+
});
|
|
810
827
|
view.addEventListener('resize', reread);
|
|
811
828
|
return () => {
|
|
812
829
|
observer?.disconnect();
|
|
813
830
|
stopObservingMove();
|
|
831
|
+
if (moveFrame !== null) {
|
|
832
|
+
view.cancelAnimationFrame(moveFrame);
|
|
833
|
+
moveFrame = null;
|
|
834
|
+
}
|
|
814
835
|
view.removeEventListener('resize', reread);
|
|
815
836
|
};
|
|
816
837
|
}
|
|
@@ -476,6 +476,30 @@ describe('PowerSearch', () => {
|
|
|
476
476
|
});
|
|
477
477
|
});
|
|
478
478
|
|
|
479
|
+
it('announces Clear all in its own words, "All filters removed" (component:PowerSearch; Tokenizer AR7)', async () => {
|
|
480
|
+
const onChange = vi.fn();
|
|
481
|
+
render(
|
|
482
|
+
<PowerSearch
|
|
483
|
+
config={config}
|
|
484
|
+
filters={[
|
|
485
|
+
{
|
|
486
|
+
field: 'status',
|
|
487
|
+
operator: 'is',
|
|
488
|
+
value: {type: 'enum', value: 'open'},
|
|
489
|
+
},
|
|
490
|
+
]}
|
|
491
|
+
onChange={onChange}
|
|
492
|
+
/>,
|
|
493
|
+
);
|
|
494
|
+
fireEvent.click(screen.getByRole('button', {name: 'Clear all'}));
|
|
495
|
+
expect(onChange).toHaveBeenCalledWith([], 'remove', 0);
|
|
496
|
+
await waitFor(() => {
|
|
497
|
+
expect(politeRegion()).toHaveTextContent('All filters removed');
|
|
498
|
+
});
|
|
499
|
+
// Not the Tokenizer's own words.
|
|
500
|
+
expect(politeRegion()).not.toHaveTextContent('All items removed');
|
|
501
|
+
});
|
|
502
|
+
|
|
479
503
|
it('does not announce the result count present on mount under StrictMode, only a later change', async () => {
|
|
480
504
|
// StrictMode replays the mount effects: a mounted-flag guard is reset by
|
|
481
505
|
// the replay and speaks the initial count unprompted; comparing against
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type TokenizerHandle,
|
|
31
31
|
type TokenizerOverflowBehavior,
|
|
32
32
|
} from '../Tokenizer';
|
|
33
|
+
import {TokenizerClearAllMessageContext} from '../Tokenizer/TokenizerClearAllMessageContext';
|
|
33
34
|
import {TypeaheadItem} from '../Typeahead/TypeaheadItem';
|
|
34
35
|
import {Token} from '../Token';
|
|
35
36
|
import {Avatar} from '../Avatar';
|
|
@@ -1062,6 +1063,12 @@ export function PowerSearch({
|
|
|
1062
1063
|
// replay (which resets a flag and would speak the count present on mount
|
|
1063
1064
|
// unprompted) announces nothing until the count changes.
|
|
1064
1065
|
const announce = useAnnounce();
|
|
1066
|
+
// PowerSearch words its Clear all as its record does; the field announces
|
|
1067
|
+
// these words instead of Tokenizer's (component:Tokenizer AR7).
|
|
1068
|
+
const clearAllMessage = useMemo(
|
|
1069
|
+
() => t('@astryx.powersearch.allFiltersRemoved'),
|
|
1070
|
+
[t],
|
|
1071
|
+
);
|
|
1065
1072
|
const announcedResultCountRef = useRef(resultCountText);
|
|
1066
1073
|
useEffect(() => {
|
|
1067
1074
|
if (resultCountText === announcedResultCountRef.current) {
|
|
@@ -1099,37 +1106,41 @@ export function PowerSearch({
|
|
|
1099
1106
|
popover.triggerRef as React.Ref<HTMLDivElement>,
|
|
1100
1107
|
)}
|
|
1101
1108
|
{...themeProps('power-search')}>
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1109
|
+
{/* The field's Clear all announces PowerSearch's own words
|
|
1110
|
+
(component:PowerSearch; component:Tokenizer AR7). */}
|
|
1111
|
+
<TokenizerClearAllMessageContext value={clearAllMessage}>
|
|
1112
|
+
<Tokenizer
|
|
1113
|
+
handleRef={tokenizerRef}
|
|
1114
|
+
label={label}
|
|
1115
|
+
isLabelHidden={isLabelHidden}
|
|
1116
|
+
searchSource={searchSource}
|
|
1117
|
+
value={tokenizerValue}
|
|
1118
|
+
onChange={handleTokenizerChange}
|
|
1119
|
+
renderToken={renderToken}
|
|
1120
|
+
renderItem={renderItem}
|
|
1121
|
+
maxMenuItems={MAX_BROWSE_MENU_ITEMS}
|
|
1122
|
+
menuWidth={menuWidth}
|
|
1123
|
+
placeholder={filters.length === 0 ? placeholder : ''}
|
|
1124
|
+
hasAutoFocus={hasAutoFocus}
|
|
1125
|
+
hasClear={hasClear && !isReadOnly}
|
|
1126
|
+
startIcon={startIcon}
|
|
1127
|
+
endContent={combinedEndContent}
|
|
1128
|
+
isDisabled={isDisabled}
|
|
1129
|
+
disabledMessage={disabledMessage}
|
|
1130
|
+
size={size}
|
|
1131
|
+
tokenOverflowBehavior={tokenOverflowBehavior}
|
|
1132
|
+
hasEntriesOnFocus
|
|
1133
|
+
debounceMs={0}
|
|
1134
|
+
status={status}
|
|
1135
|
+
statusVariant={statusVariant}
|
|
1136
|
+
onFocus={onFocus}
|
|
1137
|
+
onBlur={onBlur}
|
|
1138
|
+
containerXstyle={xstyle}
|
|
1139
|
+
containerClassName={className}
|
|
1140
|
+
containerStyle={style}
|
|
1141
|
+
data-testid={testId}
|
|
1142
|
+
/>
|
|
1143
|
+
</TokenizerClearAllMessageContext>
|
|
1133
1144
|
</div>
|
|
1134
1145
|
{popover.render(popoverContent, {
|
|
1135
1146
|
placement: 'below',
|
|
@@ -16,6 +16,7 @@ verified_by:
|
|
|
16
16
|
packages/core/src/Tokenizer/Tokenizer.ssr.test.tsx,
|
|
17
17
|
packages/core/src/Tokenizer/TokenizerBottomSheet.test.tsx,
|
|
18
18
|
packages/core/src/Tokenizer/Tokenizer.props-type.test.tsx,
|
|
19
|
+
packages/core/src/PowerSearch/PowerSearch.test.tsx,
|
|
19
20
|
packages/core/src/internal/tokenizer-presentation.test.ts,
|
|
20
21
|
packages/core/src/Typeahead/Typeahead.test.tsx,
|
|
21
22
|
packages/core/src/Layer/useLayer.test.tsx,
|
|
@@ -325,7 +326,7 @@ presentation, which has no inline combobox, suggestion menu, or end lane.
|
|
|
325
326
|
|
|
326
327
|
## Accessibility contract
|
|
327
328
|
|
|
328
|
-
AR1–AR5 describe the `popover` presentation; AR6 describes the sheet
|
|
329
|
+
AR1–AR5 and AR7 describe the `popover` presentation; AR6 describes the sheet
|
|
329
330
|
presentation.
|
|
330
331
|
|
|
331
332
|
- **AR1 — Modality equivalence.** The same input or token/control produces the
|
|
@@ -382,6 +383,16 @@ presentation.
|
|
|
382
383
|
through `spec:AST-020/FR1`). After the sheet closes focus returns to the
|
|
383
384
|
trigger and the field's announcements follow the same FR14 route from
|
|
384
385
|
wherever the field renders.
|
|
386
|
+
- **AR7 — Clear all is announced.** In the `popover` presentation, the field's
|
|
387
|
+
Clear all, which empties every token at once and moves focus to the input,
|
|
388
|
+
announces "All items removed" (`@astryx.tokenizer.allRemoved`, the words of
|
|
389
|
+
the sheet's Clear all in AR6) the same way the field announces a token added
|
|
390
|
+
or removed: politely, through the shared channel (`spec:AST-086/FR2`, FR5).
|
|
391
|
+
It is announced once per Clear all (`spec:AST-086/FR6`); a Clear all
|
|
392
|
+
confirmed in the sheet is announced by the sheet alone (AR6), and the field
|
|
393
|
+
stays silent for it (WCAG 2.2 SC 4.1.3 through `spec:AST-020/FR9`). A
|
|
394
|
+
component that composes Tokenizer and words its own Clear all announces its
|
|
395
|
+
own words instead (`component:PowerSearch`).
|
|
385
396
|
|
|
386
397
|
## Design relationships
|
|
387
398
|
|
|
@@ -462,6 +473,7 @@ become Tokenizer policy.
|
|
|
462
473
|
| FR13–FR15, AR5 | Focused Tokenizer/BaseTypeahead tests and exact-head Chromium interaction evidence | pointer/keyboard commit, first/middle/last choice, rejected/deferred control, loading, disabled, max, exhaustion, Escape, Tab, outside focus | Selected values remain available, order changes, focus leaves the combobox, active descendant names no option, stale work reopens, or a terminal state stays interactive. | `audit:Tokenizer/multi-select` |
|
|
463
474
|
| PR1–PR5 | Instrumented reads, pending-frame cleanup tests, and first-frame Chromium capture | open, close before frame, reopen opposite side, resize while open | Render-time/repeated reads, observer/pointer tracking, uncancelled frame, or first-frame jump occurs. | `audit:Tokenizer/performance` |
|
|
464
475
|
| AR1–AR5 | Existing interaction suites plus focused pointer/keyboard/browser checks; native-disabled assertions pending conformance (`spec:AST-085`) | add/edit, focus/refocus, overflow, disabled, dismissal, retained bootstrap selection | Modality changes placement, semantics/focus regress, active descendant becomes invalid, or visible placement is wrong before fallback. | `audit:Tokenizer/accessibility` |
|
|
476
|
+
| AR7 | Focused Tokenizer tests, inline and sheet, and the PowerSearch test | inline Clear all; a localized catalog; a Clear all confirmed in the sheet counted across every live region; PowerSearch's own words | The inline Clear all is silent, speaks words other than its catalog message, a sheet Clear all is announced twice, or PowerSearch speaks Tokenizer's words. | `audit:Tokenizer/accessibility` |
|
|
465
477
|
| FR16–FR24, AR6, PR6 | Focused Tokenizer sheet tests with the compact-touch query stubbed both ways, a server-render-then-hydrate test, plus real-engine touch evidence | default popover, forced sheet, adaptive both ways, `adaptive` server-rendered as the combobox and hydrated without a recoverable error, then the trigger on a coarse-pointer client with the body still active, with `hasAutoFocus` the autofocused input's focus on the trigger after the swap and no `onBlur`/`onFocus` reported, the trigger's box against the hydrating field's at 390 and 414px, base props and ref on the trigger, the trigger's description in the inline input's order — a disabled trigger with `disabledMessage`, `description` and a status exposing the caller's ids, the description, the status message and the reason before the count and the tokens —, inert-prop warning, pending search, in-sheet announcements of toggles, busy start, result count and the rendered empty outcome, the confirmed Clear all announced once the confirmation has closed, a below-threshold query, a rejecting source, focus on the panel at open, the query cleared by a swipe, scrim, or Escape close, focus after a presentation change with the sheet closed, focus after the create row, focus after a confirmed Clear all, a spent Clear all focusable with `aria-disabled` and no action, focus after a settled search removes the focused row, no busy feedback or busy-start announcement on open with an instant source, only the search field — no list, no message, no announcement — for an empty query with nothing to list (a search-only source, nothing selected), and the list once a query finds items, element counts under a fine and a coarse pointer at rest and open (Chromium, Experimental stories: adaptive 154/120 at rest — the closed trigger in place of the inline field, a different element that stays under the fine count — and 157/165 open at the query "e" with no footer, the coarse total reconciled as rest − the re-hosted surfaces host + the dialog of 47 by part, forced sheet 114/114 and 165/165; the two owner exceptions DEC-3 records beyond the FR60 allowance, with before and after counts), no confirmation dialog in the DOM while none is pending, the query kept across a disabled-forced close, `aria-setsize`/`aria-posinset` on each row's `listitem` and the next batch on focus reaching the last rendered row, open/toggle/reopen order, create, batches, limit, Clear all, disabled while open, focus return | A field without `experimental` changes, a toggle moves a row, Clear all skips confirmation, the limit disables checked rows, or the popover bundle carries the sheet. | `audit:Tokenizer/sheet` |
|
|
466
478
|
| Record structure | `scripts/check-knowledge.mjs` | Template-v3 current record and current-only frontmatter links | Invalid metadata, authority, or relationship references fail repository validation. | `audit:Tokenizer/knowledge` |
|
|
467
479
|
|
|
@@ -1403,6 +1403,33 @@ describe('Tokenizer', () => {
|
|
|
1403
1403
|
});
|
|
1404
1404
|
});
|
|
1405
1405
|
describe('announcements', () => {
|
|
1406
|
+
it("announces Clear all politely, in the sheet's words", async () => {
|
|
1407
|
+
const onChange = vi.fn();
|
|
1408
|
+
render(
|
|
1409
|
+
<InternationalizationProvider
|
|
1410
|
+
locale="fr"
|
|
1411
|
+
overrides={{
|
|
1412
|
+
fr: {'@astryx.tokenizer.allRemoved': 'Tout a été retiré'},
|
|
1413
|
+
}}>
|
|
1414
|
+
<Tokenizer
|
|
1415
|
+
label="Members"
|
|
1416
|
+
searchSource={userSource}
|
|
1417
|
+
value={[users[0], users[1]]}
|
|
1418
|
+
onChange={onChange}
|
|
1419
|
+
hasClear
|
|
1420
|
+
/>
|
|
1421
|
+
</InternationalizationProvider>,
|
|
1422
|
+
);
|
|
1423
|
+
fireEvent.click(screen.getByRole('button', {name: /clear/i}));
|
|
1424
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
1425
|
+
[],
|
|
1426
|
+
expect.objectContaining({type: 'remove'}),
|
|
1427
|
+
);
|
|
1428
|
+
await waitFor(() => {
|
|
1429
|
+
expect(politeRegion()?.textContent).toBe('Tout a été retiré');
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1406
1433
|
it('announces removal politely on Backspace with an empty input', async () => {
|
|
1407
1434
|
const onChange = vi.fn();
|
|
1408
1435
|
render(
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import React, {
|
|
28
28
|
lazy,
|
|
29
29
|
Suspense,
|
|
30
|
+
use,
|
|
30
31
|
useCallback,
|
|
31
32
|
useEffect,
|
|
32
33
|
useId,
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
useFieldInputStyling,
|
|
52
53
|
} from '../Field/fieldParts';
|
|
53
54
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
55
|
+
import {TokenizerClearAllMessageContext} from './TokenizerClearAllMessageContext';
|
|
54
56
|
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
55
57
|
import {useAdaptivePresentation} from '../hooks/useAdaptivePresentation';
|
|
56
58
|
import {useIsHydrating} from '../hooks/useIsHydrating';
|
|
@@ -1223,15 +1225,27 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
1223
1225
|
);
|
|
1224
1226
|
|
|
1225
1227
|
// Handle clearing all items
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1228
|
+
// A composing component that words its own Clear all (PowerSearch) provides
|
|
1229
|
+
// its words; otherwise the field's own (component:Tokenizer AR7).
|
|
1230
|
+
const clearAllMessage =
|
|
1231
|
+
use(TokenizerClearAllMessageContext) ?? t('@astryx.tokenizer.allRemoved');
|
|
1232
|
+
const handleClearAll = useCallback(
|
|
1233
|
+
(options?: {silent?: boolean}) => {
|
|
1234
|
+
if (isDisabled || value.length === 0) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
// Report the last item as removed (convention)
|
|
1238
|
+
const lastItem = value[value.length - 1];
|
|
1239
|
+
onChange([], {item: lastItem, type: 'remove'});
|
|
1240
|
+
// Focus moves to the input, so the emptied selection is announced, in
|
|
1241
|
+
// the sheet's words (WCAG 4.1.3). The sheet announces its own Clear all.
|
|
1242
|
+
if (!options?.silent) {
|
|
1243
|
+
announce(clearAllMessage);
|
|
1244
|
+
}
|
|
1245
|
+
inputRef.current?.focus();
|
|
1246
|
+
},
|
|
1247
|
+
[value, onChange, isDisabled, announce, clearAllMessage],
|
|
1248
|
+
);
|
|
1235
1249
|
|
|
1236
1250
|
// Handle backspace on empty input — remove last token
|
|
1237
1251
|
const handleKeyDown = useCallback(
|
|
@@ -1636,7 +1650,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
1636
1650
|
emptySearchText={emptySearchText}
|
|
1637
1651
|
onAdd={handleAdd}
|
|
1638
1652
|
onRemove={handleRemove}
|
|
1639
|
-
onClearAll={handleClearAll}
|
|
1653
|
+
onClearAll={() => handleClearAll({silent: true})}
|
|
1640
1654
|
onChangeQuery={handleSheetQueryChange}
|
|
1641
1655
|
/>
|
|
1642
1656
|
)}
|
|
@@ -808,6 +808,14 @@ describe('Tokenizer sheet presentation (single-sheet touch flow)', () => {
|
|
|
808
808
|
),
|
|
809
809
|
).toHaveTextContent('All items removed'),
|
|
810
810
|
);
|
|
811
|
+
// Exactly one live region says it: the field's own region stays silent
|
|
812
|
+
// for a Clear all the sheet already announced.
|
|
813
|
+
const saying = Array.from(
|
|
814
|
+
document.querySelectorAll(
|
|
815
|
+
'[aria-live], [role="status"], [role="alert"]',
|
|
816
|
+
),
|
|
817
|
+
).filter(region => region.textContent?.includes('All items removed'));
|
|
818
|
+
expect(saying).toHaveLength(1);
|
|
811
819
|
} finally {
|
|
812
820
|
modalSpy.mockRestore();
|
|
813
821
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file TokenizerClearAllMessageContext.ts
|
|
7
|
+
* @input None
|
|
8
|
+
* @output A context carrying the words a composing component announces for
|
|
9
|
+
* the field's Clear all
|
|
10
|
+
* @position Internal to Core: not exported from the Tokenizer barrel. A
|
|
11
|
+
* component that composes Tokenizer and words its own Clear all (PowerSearch:
|
|
12
|
+
* "All filters removed") provides it; Tokenizer otherwise announces its own
|
|
13
|
+
* words (component:Tokenizer AR7).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {createContext} from 'react';
|
|
17
|
+
|
|
18
|
+
export const TokenizerClearAllMessageContext = createContext<string | null>(
|
|
19
|
+
null,
|
|
20
|
+
);
|
|
21
|
+
TokenizerClearAllMessageContext.displayName = 'TokenizerClearAllMessageContext';
|
|
@@ -314,6 +314,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
314
314
|
'@astryx.outline.label': 'Table of contents',
|
|
315
315
|
'@astryx.powersearch.label': 'Search',
|
|
316
316
|
'@astryx.powersearch.placeholder': 'Search…',
|
|
317
|
+
'@astryx.powersearch.allFiltersRemoved': 'All filters removed',
|
|
317
318
|
'@astryx.resizable.collapsed': 'Collapsed',
|
|
318
319
|
'@astryx.resizable.handle.label': 'Resize handle',
|
|
319
320
|
'@astryx.selector.placeholder': 'Select…',
|
|
@@ -321,6 +321,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
321
321
|
'@astryx.outline.label': '⟦Ţàƀłé óƒ çóñţéñţš⟧',
|
|
322
322
|
'@astryx.powersearch.label': '⟦Šéàřçĥ⟧',
|
|
323
323
|
'@astryx.powersearch.placeholder': '⟦Šéàřçĥ…⟧',
|
|
324
|
+
'@astryx.powersearch.allFiltersRemoved': '⟦Àłł ƒíłţéřš řéɱóṽéð⟧',
|
|
324
325
|
'@astryx.resizable.collapsed': '⟦Çółłàþšéð⟧',
|
|
325
326
|
'@astryx.resizable.handle.label': '⟦Řéšížé ĥàñðłé⟧',
|
|
326
327
|
'@astryx.selector.placeholder': '⟦Šéłéçţ…⟧',
|
package/src/motion/README.md
CHANGED
|
@@ -19,7 +19,8 @@ so each keeps its measured value; no shipped component reads the roles yet.
|
|
|
19
19
|
| `uikit/timing.ts` | Non-spring clocks and curves: press highlight, Core Animation's named curves, keyboard, Reduced Motion cross-fade. |
|
|
20
20
|
| `uikit/navigation.ts` | UINavigationController: push velocity, parallax, dimming, transition clock, interactive pop finish rule. |
|
|
21
21
|
| `uikit/gestures.ts` | Gesture recognizer thresholds, the velocity filter and haptic stand-ins, as `{value, unit}` entries (sources in JSDoc). |
|
|
22
|
-
| `roles.ts` |
|
|
22
|
+
| `roles.ts` | The internal motion roles: the springs, durations and curves `spec:AST-094` FR4 names, registered by the shared tables, and a role's CSS form read back. |
|
|
23
|
+
| `roleValue.ts` | A role's CSS form, written: a spring's duration and bounce, a duration in ms, a curve's `cubic-bezier()`. |
|
|
23
24
|
| `resolveMotion.ts` | A role's value: its table value, as the per-role tables stay internal (`spec:AST-094` DEC-15). |
|
|
24
25
|
| `MEASUREMENTS.md` | The UIKit reference captures each unconfirmed value still needs. |
|
|
25
26
|
| `spring.ts` | Closed-form damped spring, every Apple parameterization, settling duration, a spring chasing a moving target. |
|
package/src/motion/roles.test.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file roles.test.ts
|
|
5
|
-
* @input roles.ts and the shared UIKit tables (registered
|
|
6
|
-
* @output Proof that
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* @input roles.ts and the shared UIKit tables (registered by this test)
|
|
6
|
+
* @output Proof that the registered roles are exactly the ones spec:AST-094
|
|
7
|
+
* FR4 names, each by its table path; that every other table value, a
|
|
8
|
+
* component's measured spring included, is no role; and that a role's CSS
|
|
9
|
+
* value reads back as the same motion
|
|
9
10
|
* @position Testing for /packages/core/src/motion/roles.ts
|
|
10
11
|
*/
|
|
11
12
|
|