@astryxdesign/core 0.7.4-canary.faa934f → 0.7.4
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/CHANGELOG.md +40 -0
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +6 -11
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +7 -1
- package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -0
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +5 -1
- package/dist/DateInput/DateInput.d.ts +6 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +5 -1
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +5 -1
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +5 -1
- package/dist/DateInput/tokens.stylex.d.ts.map +1 -1
- package/dist/DateInput/tokens.stylex.js +3 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/package.json +4 -4
- package/src/BottomSheet/BottomSheet.spec.md +1 -0
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +25 -2
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +7 -6
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +221 -1
- package/src/BottomSheet/__tests__/BottomSheetNativeModal.a11y.browser.spec.ts +461 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +4 -1
- package/src/CommandPalette/CommandPalette.test.tsx +4 -2
- package/src/CommandPalette/CommandPalette.tsx +6 -11
- package/src/CommandPalette/CommandPaletteInput.test.tsx +3 -1
- package/src/CommandPalette/CommandPaletteInput.tsx +10 -1
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +17 -5
- package/src/CommandPalette/CommandPaletteItem.test.tsx +65 -12
- package/src/CommandPalette/CommandPaletteItem.tsx +6 -1
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +250 -20
- package/src/DateInput/DateInput.doc.mjs +23 -5
- package/src/DateInput/DateInput.spec.md +52 -45
- package/src/DateInput/DateInput.tsx +13 -7
- package/src/DateInput/NativeDateField.tsx +6 -2
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateInput/__tests__/DateInputAudit.a11y.chromium.spec.ts +576 -0
- package/src/DateInput/tokens.stylex.ts +3 -1
- package/src/FileInput/FileInput.tsx +18 -2
- package/src/Overlay/Overlay.spec.md +304 -0
- package/src/__tests__/BusyOwnerAnnouncement.test.tsx +217 -1
|
@@ -195,17 +195,6 @@ describe('CommandPaletteItem', () => {
|
|
|
195
195
|
expect(item).toHaveAttribute('aria-selected', 'true');
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
-
it('paints context-derived highlight when the item is not selected', () => {
|
|
199
|
-
renderWithContext(
|
|
200
|
-
<CommandPaletteItem value="test">Item</CommandPaletteItem>,
|
|
201
|
-
{highlightedIndex: 0},
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
expect(getComputedStyle(screen.getByRole('option')).backgroundColor).toBe(
|
|
205
|
-
'var(--color-overlay-hover)',
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
198
|
it('lets explicit visual state override context-derived state', () => {
|
|
210
199
|
renderWithContext(
|
|
211
200
|
<CommandPaletteItem value="test" isHighlighted={false} isSelected={false}>
|
|
@@ -216,7 +205,8 @@ describe('CommandPaletteItem', () => {
|
|
|
216
205
|
|
|
217
206
|
const item = screen.getByRole('option');
|
|
218
207
|
expect(item).toHaveAttribute('aria-selected', 'false');
|
|
219
|
-
expect(
|
|
208
|
+
expect(item).not.toHaveAttribute('data-selected');
|
|
209
|
+
expect(item).not.toHaveAttribute('data-highlighted');
|
|
220
210
|
});
|
|
221
211
|
|
|
222
212
|
it('protects owned option semantics from native passthrough props', () => {
|
|
@@ -354,6 +344,69 @@ describe('CommandPaletteItem', () => {
|
|
|
354
344
|
expect(item).toHaveStyle({marginBlockStart: '8px'});
|
|
355
345
|
});
|
|
356
346
|
|
|
347
|
+
it('reflects selected and disabled state on its theme target', () => {
|
|
348
|
+
render(
|
|
349
|
+
<>
|
|
350
|
+
<CommandPaletteItem value="rest">Rest</CommandPaletteItem>
|
|
351
|
+
<CommandPaletteItem value="selected" isSelected>
|
|
352
|
+
Selected
|
|
353
|
+
</CommandPaletteItem>
|
|
354
|
+
<CommandPaletteItem value="disabled" isDisabled>
|
|
355
|
+
Disabled
|
|
356
|
+
</CommandPaletteItem>
|
|
357
|
+
</>,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const rest = screen.getByRole('option', {name: 'Rest'});
|
|
361
|
+
expect(rest).not.toHaveAttribute('data-selected');
|
|
362
|
+
expect(rest).not.toHaveAttribute('data-disabled');
|
|
363
|
+
expect(screen.getByRole('option', {name: 'Selected'})).toHaveAttribute(
|
|
364
|
+
'data-selected',
|
|
365
|
+
'selected',
|
|
366
|
+
);
|
|
367
|
+
expect(screen.getByRole('option', {name: 'Disabled'})).toHaveAttribute(
|
|
368
|
+
'data-disabled',
|
|
369
|
+
'disabled',
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('reflects context-derived selection on its theme target', () => {
|
|
374
|
+
renderWithContext(
|
|
375
|
+
<CommandPaletteItem value="test">Item</CommandPaletteItem>,
|
|
376
|
+
{value: 'test'},
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
expect(screen.getByRole('option')).toHaveAttribute(
|
|
380
|
+
'data-selected',
|
|
381
|
+
'selected',
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('reflects explicit and context-derived highlight on its theme target', () => {
|
|
386
|
+
renderWithContext(
|
|
387
|
+
<>
|
|
388
|
+
<CommandPaletteItem value="test">
|
|
389
|
+
Context highlighted
|
|
390
|
+
</CommandPaletteItem>
|
|
391
|
+
<CommandPaletteItem value="other" isHighlighted>
|
|
392
|
+
Explicitly highlighted
|
|
393
|
+
</CommandPaletteItem>
|
|
394
|
+
<CommandPaletteItem value="rest">Rest</CommandPaletteItem>
|
|
395
|
+
</>,
|
|
396
|
+
{highlightedIndex: 0},
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(
|
|
400
|
+
screen.getByRole('option', {name: 'Context highlighted'}),
|
|
401
|
+
).toHaveAttribute('data-highlighted', 'highlighted');
|
|
402
|
+
expect(
|
|
403
|
+
screen.getByRole('option', {name: 'Explicitly highlighted'}),
|
|
404
|
+
).toHaveAttribute('data-highlighted', 'highlighted');
|
|
405
|
+
expect(screen.getByRole('option', {name: 'Rest'})).not.toHaveAttribute(
|
|
406
|
+
'data-highlighted',
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
357
410
|
it('sets data-value attribute', () => {
|
|
358
411
|
render(<CommandPaletteItem value="my-value">Item</CommandPaletteItem>);
|
|
359
412
|
expect(screen.getByRole('option')).toHaveAttribute(
|
|
@@ -88,6 +88,7 @@ export interface CommandPaletteItemProps extends Omit<
|
|
|
88
88
|
isHighlighted?: boolean;
|
|
89
89
|
/**
|
|
90
90
|
* Whether this item is currently selected (picker mode).
|
|
91
|
+
* When omitted inside CommandPalette, derived from the palette value.
|
|
91
92
|
* @default false
|
|
92
93
|
*/
|
|
93
94
|
isSelected?: boolean;
|
|
@@ -202,7 +203,11 @@ export function CommandPaletteItem({
|
|
|
202
203
|
}
|
|
203
204
|
onMouseEnter={onMouseEnterProp}
|
|
204
205
|
{...mergeProps(
|
|
205
|
-
themeProps('command-palette-item'
|
|
206
|
+
themeProps('command-palette-item', {
|
|
207
|
+
highlighted: isHighlighted ? 'highlighted' : null,
|
|
208
|
+
selected: isSelected ? 'selected' : null,
|
|
209
|
+
disabled: isDisabled ? 'disabled' : null,
|
|
210
|
+
}),
|
|
206
211
|
stylex.props(
|
|
207
212
|
styles.item,
|
|
208
213
|
!isDisabled && interactionOverlayStyles.backgroundColor,
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file CommandPaletteItemAudit.a11y.chromium.spec.ts
|
|
5
|
-
* @input Exact-head CommandPaletteItem stories
|
|
5
|
+
* @input Exact-head CommandPaletteItem stories, plus the inline grouped palette in
|
|
6
|
+
* the CommandPalette audit matrix, across states, themes, direction, pointers,
|
|
7
|
+
* and viewport sizes
|
|
6
8
|
* @output Screenshots and fail-closed sensor receipts for the CommandPaletteItem whole-component audit
|
|
7
9
|
* @position Component-owned Night Watch browser evidence; CI runs it only for a CommandPaletteItem audit pull request
|
|
8
10
|
*/
|
|
@@ -11,7 +13,13 @@ import {createHash} from 'node:crypto';
|
|
|
11
13
|
import {execFileSync} from 'node:child_process';
|
|
12
14
|
import * as fs from 'node:fs';
|
|
13
15
|
import * as path from 'node:path';
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
expect,
|
|
18
|
+
test,
|
|
19
|
+
type CDPSession,
|
|
20
|
+
type Locator,
|
|
21
|
+
type Page,
|
|
22
|
+
} from '@playwright/test';
|
|
15
23
|
import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
|
|
16
24
|
import {
|
|
17
25
|
DEFAULT_STORYBOOK_DIR,
|
|
@@ -26,8 +34,17 @@ const NARROW = {width: 320, height: 720};
|
|
|
26
34
|
type Mode = 'light' | 'dark';
|
|
27
35
|
type Direction = 'ltr' | 'rtl';
|
|
28
36
|
type Theme = 'neutral' | 'butter';
|
|
29
|
-
type
|
|
30
|
-
type
|
|
37
|
+
type Pointer = 'fine' | 'coarse';
|
|
38
|
+
type Story = 'states' | 'combined-and-long' | 'palette';
|
|
39
|
+
type State =
|
|
40
|
+
| 'rest'
|
|
41
|
+
| 'hover'
|
|
42
|
+
| 'pressed'
|
|
43
|
+
| 'pressed-highlighted'
|
|
44
|
+
| 'disabled-hover'
|
|
45
|
+
| 'touch-press'
|
|
46
|
+
| 'keyboard-highlight'
|
|
47
|
+
| 'keyboard-selected-highlight';
|
|
31
48
|
|
|
32
49
|
type AuditCase = {
|
|
33
50
|
key: string;
|
|
@@ -36,6 +53,8 @@ type AuditCase = {
|
|
|
36
53
|
direction: Direction;
|
|
37
54
|
theme: Theme;
|
|
38
55
|
state: State;
|
|
56
|
+
pointer: Pointer;
|
|
57
|
+
forcedColors: boolean;
|
|
39
58
|
viewport: {width: number; height: number};
|
|
40
59
|
};
|
|
41
60
|
|
|
@@ -57,6 +76,8 @@ function addPair(
|
|
|
57
76
|
direction: options.direction ?? 'ltr',
|
|
58
77
|
theme: options.theme ?? 'neutral',
|
|
59
78
|
state: options.state ?? 'rest',
|
|
79
|
+
pointer: options.pointer ?? 'fine',
|
|
80
|
+
forcedColors: options.forcedColors ?? false,
|
|
60
81
|
viewport: options.viewport ?? WIDE,
|
|
61
82
|
});
|
|
62
83
|
}
|
|
@@ -64,9 +85,19 @@ function addPair(
|
|
|
64
85
|
|
|
65
86
|
addPair('states-rest');
|
|
66
87
|
addPair('states-hover', {state: 'hover'});
|
|
88
|
+
addPair('states-pressed', {state: 'pressed'});
|
|
89
|
+
addPair('states-pressed-highlighted', {state: 'pressed-highlighted'});
|
|
90
|
+
addPair('states-disabled-hover', {state: 'disabled-hover'});
|
|
67
91
|
addPair('states-custom-theme', {theme: 'butter'});
|
|
68
92
|
addPair('states-rtl', {direction: 'rtl'});
|
|
69
93
|
addPair('states-narrow', {viewport: NARROW});
|
|
94
|
+
addPair('states-coarse', {viewport: NARROW, pointer: 'coarse'});
|
|
95
|
+
addPair('states-touch-press', {
|
|
96
|
+
viewport: NARROW,
|
|
97
|
+
pointer: 'coarse',
|
|
98
|
+
state: 'touch-press',
|
|
99
|
+
});
|
|
100
|
+
addPair('states-forced-colors', {forcedColors: true});
|
|
70
101
|
addPair('combined-rest', {story: 'combined-and-long'});
|
|
71
102
|
addPair('combined-custom-theme', {
|
|
72
103
|
story: 'combined-and-long',
|
|
@@ -76,6 +107,45 @@ addPair('combined-narrow', {
|
|
|
76
107
|
story: 'combined-and-long',
|
|
77
108
|
viewport: NARROW,
|
|
78
109
|
});
|
|
110
|
+
addPair('palette-keyboard-highlight', {
|
|
111
|
+
story: 'palette',
|
|
112
|
+
state: 'keyboard-highlight',
|
|
113
|
+
});
|
|
114
|
+
addPair('palette-keyboard-selected-highlight', {
|
|
115
|
+
story: 'palette',
|
|
116
|
+
state: 'keyboard-selected-highlight',
|
|
117
|
+
});
|
|
118
|
+
addPair('palette-keyboard-selected-highlight-custom-theme', {
|
|
119
|
+
story: 'palette',
|
|
120
|
+
state: 'keyboard-selected-highlight',
|
|
121
|
+
theme: 'butter',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
addPair('palette-keyboard-highlight-forced-colors', {
|
|
125
|
+
story: 'palette',
|
|
126
|
+
state: 'keyboard-highlight',
|
|
127
|
+
forcedColors: true,
|
|
128
|
+
});
|
|
129
|
+
addPair('palette-keyboard-selected-highlight-forced-colors', {
|
|
130
|
+
story: 'palette',
|
|
131
|
+
state: 'keyboard-selected-highlight',
|
|
132
|
+
forcedColors: true,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/** Option each pointer state acts on, by its accessible text. */
|
|
136
|
+
const POINTER_TARGET: Partial<Record<State, string>> = {
|
|
137
|
+
hover: 'Open file',
|
|
138
|
+
pressed: 'Open file',
|
|
139
|
+
'pressed-highlighted': 'Search files',
|
|
140
|
+
'disabled-hover': 'Archived command',
|
|
141
|
+
'touch-press': 'Open file',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** Option the palette keyboard states move the active descendant to. */
|
|
145
|
+
const KEYBOARD_TARGET: Partial<Record<State, string>> = {
|
|
146
|
+
'keyboard-highlight': 'Home',
|
|
147
|
+
'keyboard-selected-highlight': 'Settings',
|
|
148
|
+
};
|
|
79
149
|
|
|
80
150
|
test.describe.configure({mode: 'serial', retries: 0});
|
|
81
151
|
let server: StaticServer;
|
|
@@ -83,7 +153,9 @@ let checkoutSha: string;
|
|
|
83
153
|
let storybookSha: string;
|
|
84
154
|
|
|
85
155
|
function storyId(item: AuditCase) {
|
|
86
|
-
return
|
|
156
|
+
return item.story === 'palette'
|
|
157
|
+
? 'core-commandpalette--audit-matrix'
|
|
158
|
+
: `core-commandpaletteitem--${item.story}`;
|
|
87
159
|
}
|
|
88
160
|
|
|
89
161
|
function storyUrl(item: AuditCase) {
|
|
@@ -96,6 +168,38 @@ async function settle(page: Page) {
|
|
|
96
168
|
await page.waitForTimeout(40);
|
|
97
169
|
}
|
|
98
170
|
|
|
171
|
+
async function centerOf(locator: Locator) {
|
|
172
|
+
const box = await locator.boundingBox();
|
|
173
|
+
if (box == null) {
|
|
174
|
+
throw new Error('target option has no rendered bounds');
|
|
175
|
+
}
|
|
176
|
+
return {x: box.x + box.width / 2, y: box.y + box.height / 2};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function highlightedName(input: Locator) {
|
|
180
|
+
return input.evaluate(element => {
|
|
181
|
+
const id = element.getAttribute('aria-activedescendant');
|
|
182
|
+
const option = id ? document.getElementById(id) : null;
|
|
183
|
+
return option?.textContent?.replace(/\s+/g, ' ').trim() ?? null;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** PageUp moves the palette to its first result; ArrowDown walks forward. */
|
|
188
|
+
async function highlightFromKeyboard(
|
|
189
|
+
page: Page,
|
|
190
|
+
input: Locator,
|
|
191
|
+
target: string,
|
|
192
|
+
) {
|
|
193
|
+
await input.focus();
|
|
194
|
+
await page.keyboard.press('PageUp');
|
|
195
|
+
for (let step = 0; step < 4; step++) {
|
|
196
|
+
if ((await highlightedName(input)) === target) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await page.keyboard.press('ArrowDown');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
99
203
|
test.beforeAll(async () => {
|
|
100
204
|
fs.rmSync(OUTPUT, {recursive: true, force: true});
|
|
101
205
|
fs.mkdirSync(OUTPUT, {recursive: true});
|
|
@@ -133,6 +237,8 @@ for (const item of cases) {
|
|
|
133
237
|
viewport: item.viewport,
|
|
134
238
|
colorScheme: item.mode,
|
|
135
239
|
reducedMotion: 'reduce',
|
|
240
|
+
hasTouch: item.pointer === 'coarse',
|
|
241
|
+
forcedColors: item.forcedColors ? 'active' : 'none',
|
|
136
242
|
});
|
|
137
243
|
const page = await context.newPage();
|
|
138
244
|
const pageErrors: string[] = [];
|
|
@@ -140,13 +246,20 @@ for (const item of cases) {
|
|
|
140
246
|
await page.goto(storyUrl(item), {waitUntil: 'load'});
|
|
141
247
|
await page.locator('#storybook-root').waitFor();
|
|
142
248
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const list =
|
|
249
|
+
const isPalette = item.story === 'palette';
|
|
250
|
+
const subject = isPalette
|
|
251
|
+
? page.getByRole('region', {name: 'Grouped command palette'})
|
|
252
|
+
: page.locator('#storybook-root');
|
|
253
|
+
const list = isPalette
|
|
254
|
+
? subject.getByRole('listbox')
|
|
255
|
+
: subject.getByRole('listbox', {
|
|
256
|
+
name:
|
|
257
|
+
item.story === 'states'
|
|
258
|
+
? 'Command item states'
|
|
259
|
+
: 'Combined and long command item states',
|
|
260
|
+
});
|
|
148
261
|
const options = list.getByRole('option');
|
|
149
|
-
const expectedCount = item.story === '
|
|
262
|
+
const expectedCount = item.story === 'combined-and-long' ? 2 : 4;
|
|
150
263
|
const expectedDisabled = item.story === 'states' ? 1 : 0;
|
|
151
264
|
await expect(list).toBeVisible();
|
|
152
265
|
await expect(options).toHaveCount(expectedCount);
|
|
@@ -156,12 +269,51 @@ for (const item of cases) {
|
|
|
156
269
|
await expect(
|
|
157
270
|
list.locator('[role="option"][aria-disabled="true"]'),
|
|
158
271
|
).toHaveCount(expectedDisabled);
|
|
159
|
-
await expect(
|
|
272
|
+
await expect(
|
|
273
|
+
list.locator('[role="option"] :is(button, a, [tabindex])'),
|
|
274
|
+
).toHaveCount(0);
|
|
275
|
+
|
|
276
|
+
const pointerTarget = POINTER_TARGET[item.state];
|
|
277
|
+
const keyboardTarget = KEYBOARD_TARGET[item.state];
|
|
278
|
+
const target = pointerTarget ?? keyboardTarget ?? null;
|
|
279
|
+
const targetOption =
|
|
280
|
+
target == null ? null : options.filter({hasText: target}).first();
|
|
281
|
+
let cdp: CDPSession | null = null;
|
|
160
282
|
|
|
161
|
-
if (item.state === 'hover') {
|
|
162
|
-
await options.first().hover();
|
|
163
|
-
}
|
|
164
283
|
await settle(page);
|
|
284
|
+
if (item.state === 'hover' || item.state === 'disabled-hover') {
|
|
285
|
+
await targetOption?.hover();
|
|
286
|
+
} else if (
|
|
287
|
+
item.state === 'pressed' ||
|
|
288
|
+
item.state === 'pressed-highlighted'
|
|
289
|
+
) {
|
|
290
|
+
if (targetOption == null) {
|
|
291
|
+
throw new Error(`${item.key}: no press target`);
|
|
292
|
+
}
|
|
293
|
+
await targetOption.hover();
|
|
294
|
+
await page.mouse.down();
|
|
295
|
+
} else if (item.state === 'touch-press') {
|
|
296
|
+
if (targetOption == null) {
|
|
297
|
+
throw new Error(`${item.key}: no touch target`);
|
|
298
|
+
}
|
|
299
|
+
const point = await centerOf(targetOption);
|
|
300
|
+
cdp = await context.newCDPSession(page);
|
|
301
|
+
await cdp.send('Input.dispatchTouchEvent', {
|
|
302
|
+
type: 'touchStart',
|
|
303
|
+
touchPoints: [{x: point.x, y: point.y, id: 1}],
|
|
304
|
+
});
|
|
305
|
+
// The touch press controller believes a stationary press after its
|
|
306
|
+
// onset delay; hold past it before reading the painted state.
|
|
307
|
+
await page.waitForTimeout(320);
|
|
308
|
+
} else if (keyboardTarget != null) {
|
|
309
|
+
await highlightFromKeyboard(
|
|
310
|
+
page,
|
|
311
|
+
subject.getByRole('combobox'),
|
|
312
|
+
keyboardTarget,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
await page.evaluate(async () => document.fonts.ready);
|
|
316
|
+
await page.waitForTimeout(40);
|
|
165
317
|
|
|
166
318
|
const observed = await page.evaluate(() => ({
|
|
167
319
|
theme: document
|
|
@@ -173,20 +325,41 @@ for (const item of cases) {
|
|
|
173
325
|
).direction,
|
|
174
326
|
viewport: {width: innerWidth, height: innerHeight},
|
|
175
327
|
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
328
|
+
coarsePointer: matchMedia('(pointer: coarse)').matches,
|
|
329
|
+
forcedColors: matchMedia('(forced-colors: active)').matches,
|
|
176
330
|
horizontalOverflow: document.documentElement.scrollWidth > innerWidth + 1,
|
|
177
331
|
fonts: document.fonts.status,
|
|
178
332
|
}));
|
|
333
|
+
const listBackdrop = await list.evaluate(element => {
|
|
334
|
+
let node: Element | null = element;
|
|
335
|
+
while (node) {
|
|
336
|
+
const color = getComputedStyle(node).backgroundColor;
|
|
337
|
+
if (color !== 'rgba(0, 0, 0, 0)' && color !== 'transparent') {
|
|
338
|
+
return color;
|
|
339
|
+
}
|
|
340
|
+
node = node.parentElement;
|
|
341
|
+
}
|
|
342
|
+
return getComputedStyle(document.body).backgroundColor;
|
|
343
|
+
});
|
|
179
344
|
const optionStates = await options.evaluateAll(nodes =>
|
|
180
345
|
nodes.map(node => {
|
|
181
346
|
const style = getComputedStyle(node);
|
|
182
347
|
const box = node.getBoundingClientRect();
|
|
183
348
|
return {
|
|
184
349
|
name: node.textContent?.replace(/\s+/g, ' ').trim(),
|
|
350
|
+
id: node.id || null,
|
|
185
351
|
selected: node.getAttribute('aria-selected'),
|
|
186
352
|
disabled: node.getAttribute('aria-disabled'),
|
|
353
|
+
themeSelected: node.getAttribute('data-selected'),
|
|
354
|
+
themeDisabled: node.getAttribute('data-disabled'),
|
|
355
|
+
active: node.matches(':active'),
|
|
356
|
+
hovered: node.matches(':hover'),
|
|
357
|
+
press: node.getAttribute('data-astryx-press'),
|
|
187
358
|
backgroundColor: style.backgroundColor,
|
|
359
|
+
backgroundImage: style.backgroundImage,
|
|
188
360
|
color: style.color,
|
|
189
361
|
opacity: style.opacity,
|
|
362
|
+
cursor: style.cursor,
|
|
190
363
|
geometry: {
|
|
191
364
|
x: box.x,
|
|
192
365
|
y: box.y,
|
|
@@ -196,6 +369,18 @@ for (const item of cases) {
|
|
|
196
369
|
};
|
|
197
370
|
}),
|
|
198
371
|
);
|
|
372
|
+
const combobox = isPalette
|
|
373
|
+
? await subject.getByRole('combobox').evaluate(element => ({
|
|
374
|
+
focused: element === document.activeElement,
|
|
375
|
+
activeDescendant: element.getAttribute('aria-activedescendant'),
|
|
376
|
+
}))
|
|
377
|
+
: null;
|
|
378
|
+
|
|
379
|
+
const targetState =
|
|
380
|
+
target == null
|
|
381
|
+
? null
|
|
382
|
+
: (optionStates.find(option => option.name?.startsWith(target)) ??
|
|
383
|
+
null);
|
|
199
384
|
const semanticsPass =
|
|
200
385
|
optionStates.length === expectedCount &&
|
|
201
386
|
optionStates.filter(option => option.selected === 'true').length === 1 &&
|
|
@@ -204,11 +389,48 @@ for (const item of cases) {
|
|
|
204
389
|
optionStates.every(
|
|
205
390
|
option => option.geometry.width > 0 && option.geometry.height > 0,
|
|
206
391
|
);
|
|
392
|
+
let statePass = true;
|
|
393
|
+
if (item.state === 'hover') {
|
|
394
|
+
statePass = targetState?.hovered === true;
|
|
395
|
+
} else if (item.state === 'disabled-hover') {
|
|
396
|
+
statePass =
|
|
397
|
+
targetState?.hovered === true &&
|
|
398
|
+
targetState.disabled === 'true' &&
|
|
399
|
+
targetState.backgroundColor === 'rgba(0, 0, 0, 0)' &&
|
|
400
|
+
targetState.cursor === 'default';
|
|
401
|
+
} else if (
|
|
402
|
+
item.state === 'pressed' ||
|
|
403
|
+
item.state === 'pressed-highlighted'
|
|
404
|
+
) {
|
|
405
|
+
statePass = targetState?.active === true;
|
|
406
|
+
} else if (item.state === 'touch-press') {
|
|
407
|
+
statePass = targetState?.press === 'on';
|
|
408
|
+
} else if (keyboardTarget != null) {
|
|
409
|
+
statePass =
|
|
410
|
+
combobox?.focused === true &&
|
|
411
|
+
targetState?.id != null &&
|
|
412
|
+
combobox.activeDescendant === targetState.id &&
|
|
413
|
+
(item.state === 'keyboard-selected-highlight'
|
|
414
|
+
? targetState.selected === 'true'
|
|
415
|
+
: targetState.selected === 'false');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const png = isPalette
|
|
419
|
+
? await subject.screenshot({animations: 'disabled'})
|
|
420
|
+
: await page.screenshot({animations: 'disabled', fullPage: true});
|
|
421
|
+
if (item.state === 'pressed' || item.state === 'pressed-highlighted') {
|
|
422
|
+
await page.mouse.up();
|
|
423
|
+
}
|
|
424
|
+
if (cdp != null) {
|
|
425
|
+
await cdp.send('Input.dispatchTouchEvent', {
|
|
426
|
+
type: 'touchEnd',
|
|
427
|
+
touchPoints: [],
|
|
428
|
+
});
|
|
429
|
+
}
|
|
207
430
|
|
|
208
|
-
const png = await page.screenshot({animations: 'disabled', fullPage: true});
|
|
209
431
|
const image = `${item.key}.png`;
|
|
210
432
|
const receipt = {
|
|
211
|
-
schemaVersion:
|
|
433
|
+
schemaVersion: 2,
|
|
212
434
|
expected: {
|
|
213
435
|
build: checkoutSha,
|
|
214
436
|
storyId: storyId(item),
|
|
@@ -216,6 +438,9 @@ for (const item of cases) {
|
|
|
216
438
|
direction: item.direction,
|
|
217
439
|
theme: item.theme,
|
|
218
440
|
state: item.state,
|
|
441
|
+
target,
|
|
442
|
+
pointer: item.pointer,
|
|
443
|
+
forcedColors: item.forcedColors,
|
|
219
444
|
viewport: item.viewport,
|
|
220
445
|
optionCount: expectedCount,
|
|
221
446
|
disabledCount: expectedDisabled,
|
|
@@ -226,6 +451,8 @@ for (const item of cases) {
|
|
|
226
451
|
...observed,
|
|
227
452
|
listRole: await list.getAttribute('role'),
|
|
228
453
|
listName: await list.getAttribute('aria-label'),
|
|
454
|
+
listBackdrop,
|
|
455
|
+
combobox,
|
|
229
456
|
optionStates,
|
|
230
457
|
pageErrors,
|
|
231
458
|
},
|
|
@@ -235,7 +462,7 @@ for (const item of cases) {
|
|
|
235
462
|
width: png.readUInt32BE(16),
|
|
236
463
|
height: png.readUInt32BE(20),
|
|
237
464
|
},
|
|
238
|
-
checks: {semanticsPass},
|
|
465
|
+
checks: {semanticsPass, statePass},
|
|
239
466
|
passed:
|
|
240
467
|
storybookSha === checkoutSha &&
|
|
241
468
|
pageErrors.length === 0 &&
|
|
@@ -245,9 +472,12 @@ for (const item of cases) {
|
|
|
245
472
|
observed.viewport.width === item.viewport.width &&
|
|
246
473
|
observed.viewport.height === item.viewport.height &&
|
|
247
474
|
observed.reducedMotion &&
|
|
475
|
+
observed.coarsePointer === (item.pointer === 'coarse') &&
|
|
476
|
+
observed.forcedColors === item.forcedColors &&
|
|
248
477
|
!observed.horizontalOverflow &&
|
|
249
478
|
observed.fonts === 'loaded' &&
|
|
250
|
-
semanticsPass
|
|
479
|
+
semanticsPass &&
|
|
480
|
+
statePass,
|
|
251
481
|
};
|
|
252
482
|
fs.writeFileSync(path.join(OUTPUT, image), png);
|
|
253
483
|
fs.writeFileSync(
|
|
@@ -270,7 +500,7 @@ test('manifest is complete', async () => {
|
|
|
270
500
|
) as AuditReceipt,
|
|
271
501
|
);
|
|
272
502
|
const manifest = {
|
|
273
|
-
schemaVersion:
|
|
503
|
+
schemaVersion: 2,
|
|
274
504
|
component: 'core/CommandPaletteItem',
|
|
275
505
|
build: checkoutSha,
|
|
276
506
|
frames: cases.map(item => ({
|
|
@@ -79,7 +79,7 @@ export const docs = {
|
|
|
79
79
|
name: 'isLoading',
|
|
80
80
|
type: 'boolean',
|
|
81
81
|
description:
|
|
82
|
-
'Whether the input is in a loading state.
|
|
82
|
+
'Whether the input is in a loading state. The field stays focusable and exposes `aria-busy` with a spinner; typing, picking, and clearing are blocked until loading ends.',
|
|
83
83
|
default: 'false',
|
|
84
84
|
},
|
|
85
85
|
{
|
|
@@ -253,6 +253,11 @@ export const docs = {
|
|
|
253
253
|
description:
|
|
254
254
|
'Show a loading state with changeAction when the date triggers a server-side save.',
|
|
255
255
|
},
|
|
256
|
+
{
|
|
257
|
+
guidance: true,
|
|
258
|
+
description:
|
|
259
|
+
'On a fine pointer the field follows the WAI-ARIA combobox pattern with a dialog popup, as in the APG date picker combobox: ArrowDown or Alt+ArrowDown opens the calendar, and Escape closes it and returns focus to the field.',
|
|
260
|
+
},
|
|
256
261
|
{
|
|
257
262
|
guidance: true,
|
|
258
263
|
description:
|
|
@@ -301,7 +306,7 @@ export const docs = {
|
|
|
301
306
|
name: 'Calendar popover',
|
|
302
307
|
required: false,
|
|
303
308
|
description:
|
|
304
|
-
'A month grid that appears when the icon is clicked or
|
|
309
|
+
'A month grid that appears when the icon or the input is clicked, or when ArrowDown is pressed in the input.',
|
|
305
310
|
},
|
|
306
311
|
{
|
|
307
312
|
name: 'Clear button',
|
|
@@ -351,6 +356,11 @@ export const docsZh = {
|
|
|
351
356
|
description:
|
|
352
357
|
'Show a loading state with changeAction when the date triggers a server-side save.',
|
|
353
358
|
},
|
|
359
|
+
{
|
|
360
|
+
guidance: true,
|
|
361
|
+
description:
|
|
362
|
+
'On a fine pointer the field follows the WAI-ARIA combobox pattern with a dialog popup, as in the APG date picker combobox: ArrowDown or Alt+ArrowDown opens the calendar, and Escape closes it and returns focus to the field.',
|
|
363
|
+
},
|
|
354
364
|
{
|
|
355
365
|
guidance: false,
|
|
356
366
|
description:
|
|
@@ -401,7 +411,8 @@ export const docsZh = {
|
|
|
401
411
|
{
|
|
402
412
|
name: 'isDisabled',
|
|
403
413
|
type: 'boolean',
|
|
404
|
-
description:
|
|
414
|
+
description:
|
|
415
|
+
'禁用输入框和日历。输入框仍可聚焦并暴露 `aria-disabled`,但不能输入或打开日历。',
|
|
405
416
|
default: 'false',
|
|
406
417
|
},
|
|
407
418
|
{
|
|
@@ -429,7 +440,8 @@ export const docsZh = {
|
|
|
429
440
|
{
|
|
430
441
|
name: 'isLoading',
|
|
431
442
|
type: 'boolean',
|
|
432
|
-
description:
|
|
443
|
+
description:
|
|
444
|
+
'输入框是否处于加载状态。字段仍可聚焦,并通过 `aria-busy` 和加载指示器表示忙碌;加载结束前不能输入、选择或清除日期。',
|
|
433
445
|
default: 'false',
|
|
434
446
|
},
|
|
435
447
|
{
|
|
@@ -599,6 +611,11 @@ export const docsDense = {
|
|
|
599
611
|
description:
|
|
600
612
|
'Show a loading state with changeAction when the date triggers a server-side save.',
|
|
601
613
|
},
|
|
614
|
+
{
|
|
615
|
+
guidance: true,
|
|
616
|
+
description:
|
|
617
|
+
'On a fine pointer the field follows the WAI-ARIA combobox pattern with a dialog popup, as in the APG date picker combobox: ArrowDown or Alt+ArrowDown opens the calendar, and Escape closes it and returns focus to the field.',
|
|
618
|
+
},
|
|
602
619
|
{
|
|
603
620
|
guidance: true,
|
|
604
621
|
description:
|
|
@@ -638,7 +655,8 @@ export const docsDense = {
|
|
|
638
655
|
value: 'selected date YYYY-MM-DD',
|
|
639
656
|
onChange: 'callback on date change',
|
|
640
657
|
changeAction: 'async action after onChange; drives optimistic UI',
|
|
641
|
-
isLoading:
|
|
658
|
+
isLoading:
|
|
659
|
+
'loading state; stays focusable w/ aria-busy + spinner; blocks typing, picking, clearing',
|
|
642
660
|
min: 'min selectable date (YYYY-MM-DD)',
|
|
643
661
|
max: 'max selectable date (YYYY-MM-DD)',
|
|
644
662
|
dateConstraints: 'custom constraint fns to disable specific dates',
|