@a11y-oracle/core-engine 1.0.0

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/README.md ADDED
@@ -0,0 +1,473 @@
1
+ # @a11y-oracle/core-engine
2
+
3
+ Framework-agnostic accessibility engine for A11y-Oracle. Provides two main APIs:
4
+
5
+ 1. **`SpeechEngine`** — Reads the browser's Accessibility Tree via CDP and generates standardized speech output.
6
+ 2. **`A11yOrchestrator`** — Unified orchestrator that combines speech, keyboard dispatch, and focus indicator analysis into a single `pressKey()` call.
7
+
8
+ This package is the foundation that the Playwright and Cypress plugins build on. You can also use it directly with any CDP-compatible client.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @a11y-oracle/core-engine
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ### SpeechEngine (Speech Only)
19
+
20
+ The speech engine operates through a `CDPSessionLike` interface, which any CDP client can satisfy:
21
+
22
+ ```typescript
23
+ import { SpeechEngine } from '@a11y-oracle/core-engine';
24
+
25
+ // Works with Playwright's CDPSession
26
+ const cdpSession = await page.context().newCDPSession(page);
27
+ const engine = new SpeechEngine(cdpSession);
28
+ await engine.enable();
29
+
30
+ // Get speech for the currently focused element
31
+ const result = await engine.getSpeech();
32
+ console.log(result?.speech); // "Products, button, collapsed"
33
+
34
+ // Get speech for every visible element
35
+ const all = await engine.getFullTreeSpeech();
36
+ const nav = all.find(r => r.speech.includes('navigation landmark'));
37
+
38
+ // Clean up
39
+ await engine.disable();
40
+ ```
41
+
42
+ ### A11yOrchestrator (Unified State)
43
+
44
+ The orchestrator coordinates the speech engine, keyboard engine, and focus analyzer:
45
+
46
+ ```typescript
47
+ import { A11yOrchestrator } from '@a11y-oracle/core-engine';
48
+
49
+ const cdpSession = await page.context().newCDPSession(page);
50
+ const orchestrator = new A11yOrchestrator(cdpSession);
51
+ await orchestrator.enable();
52
+
53
+ // Press a key and get unified state
54
+ const state = await orchestrator.pressKey('Tab');
55
+ console.log(state.speech); // "Products, button, collapsed"
56
+ console.log(state.focusedElement?.tag); // "BUTTON"
57
+ console.log(state.focusIndicator.meetsWCAG_AA); // true
58
+
59
+ // Get state without pressing a key
60
+ const current = await orchestrator.getState();
61
+
62
+ // Tab order extraction
63
+ const report = await orchestrator.traverseTabOrder();
64
+ console.log(report.totalCount); // 12
65
+
66
+ // Keyboard trap detection (WCAG 2.1.2)
67
+ const result = await orchestrator.traverseSubTree('#modal', 20);
68
+ console.log(result.isTrapped); // false
69
+
70
+ await orchestrator.disable();
71
+ ```
72
+
73
+ ### Configuration Options
74
+
75
+ ```typescript
76
+ const engine = new SpeechEngine(cdpSession, {
77
+ // Include "landmark" suffix on landmark roles (default: true)
78
+ // true: "Main, navigation landmark"
79
+ // false: "Main, navigation"
80
+ includeLandmarks: true,
81
+
82
+ // Include accessible descriptions in output (default: false)
83
+ // true: "Submit, button, Submits the form"
84
+ // false: "Submit, button"
85
+ includeDescription: false,
86
+ });
87
+
88
+ const orchestrator = new A11yOrchestrator(cdpSession, {
89
+ // All SpeechEngine options, plus:
90
+
91
+ // Milliseconds to wait after key press for focus/CSS to settle (default: 50)
92
+ focusSettleMs: 50,
93
+ });
94
+ ```
95
+
96
+ ## API Reference
97
+
98
+ ### `SpeechEngine`
99
+
100
+ The speech engine. All methods are async and operate through the CDP session.
101
+
102
+ #### `constructor(cdp: CDPSessionLike, options?: SpeechEngineOptions)`
103
+
104
+ Create a new engine instance.
105
+
106
+ - `cdp` — Any object implementing the `CDPSessionLike` interface.
107
+ - `options.includeLandmarks` — Append "landmark" to landmark roles. Default `true`.
108
+ - `options.includeDescription` — Include `aria-describedby` text. Default `false`.
109
+
110
+ #### `enable(): Promise<void>`
111
+
112
+ Enable the CDP Accessibility domain. Must be called before any other method.
113
+
114
+ #### `disable(): Promise<void>`
115
+
116
+ Disable the CDP Accessibility domain. Call when done to free browser resources.
117
+
118
+ #### `getSpeech(): Promise<SpeechResult | null>`
119
+
120
+ Get the speech output for the currently focused element.
121
+
122
+ Returns `null` if no element has focus or the focused element is ignored (e.g., `role="presentation"`).
123
+
124
+ ```typescript
125
+ const result = await engine.getSpeech();
126
+ if (result) {
127
+ console.log(result.speech); // "Products, button, collapsed"
128
+ console.log(result.name); // "Products"
129
+ console.log(result.role); // "button"
130
+ console.log(result.states); // ["collapsed"]
131
+ console.log(result.rawNode); // Full CDP AXNode object
132
+ }
133
+ ```
134
+
135
+ #### `getFullTreeSpeech(): Promise<SpeechResult[]>`
136
+
137
+ Get speech for all non-ignored, non-silent nodes in the accessibility tree. Useful for asserting on landmarks, headings, or structural elements that don't have focus.
138
+
139
+ ```typescript
140
+ const all = await engine.getFullTreeSpeech();
141
+ const headings = all.filter(r => r.role.includes('heading'));
142
+ const landmarks = all.filter(r => r.role.includes('landmark'));
143
+ ```
144
+
145
+ #### `computeSpeech(node: AXNode): SpeechResult | null`
146
+
147
+ Compute speech for a single AXNode. Returns `null` for ignored or silent nodes.
148
+
149
+ #### `findFocusedNode(nodes: AXNode[]): AXNode | null`
150
+
151
+ Find the most specific focused node in the flat AXTree array. When multiple nodes report `focused: true` (e.g., `RootWebArea` and a `menuitem`), the deepest node is returned.
152
+
153
+ ### `A11yOrchestrator`
154
+
155
+ Unified orchestrator coordinating three sub-engines:
156
+
157
+ | Engine | Responsibility |
158
+ |--------|---------------|
159
+ | `SpeechEngine` | AXTree to speech string |
160
+ | `KeyboardEngine` | CDP key dispatch + `document.activeElement` |
161
+ | `FocusAnalyzer` | CSS focus indicator + tab order + trap detection |
162
+
163
+ #### `constructor(cdp: CDPSessionLike, options?: A11yOrchestratorOptions)`
164
+
165
+ Create a new orchestrator.
166
+
167
+ - `cdp` — Any CDP-compatible session.
168
+ - `options.includeLandmarks` — Append "landmark" to landmark roles. Default `true`.
169
+ - `options.includeDescription` — Include description text. Default `false`.
170
+ - `options.focusSettleMs` — Delay after key press for focus/CSS to settle. Default `50`.
171
+
172
+ #### `enable(): Promise<void>`
173
+
174
+ Enable the CDP Accessibility domain. Must be called before other methods.
175
+
176
+ #### `disable(): Promise<void>`
177
+
178
+ Disable the CDP Accessibility domain.
179
+
180
+ #### `pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>`
181
+
182
+ Dispatch a key press and return the unified accessibility state.
183
+
184
+ 1. Sends `keyDown` + `keyUp` via CDP `Input.dispatchKeyEvent`
185
+ 2. Waits `focusSettleMs` for CSS transitions and focus events
186
+ 3. Collects speech, focused element, and focus indicator in parallel
187
+
188
+ ```typescript
189
+ const state = await orchestrator.pressKey('Tab');
190
+ // state.speech → "Products, button, collapsed"
191
+ // state.focusedElement → { tag: 'BUTTON', id: 'products-btn', ... }
192
+ // state.focusIndicator → { isVisible: true, contrastRatio: 12.5, meetsWCAG_AA: true }
193
+
194
+ // With modifier keys
195
+ const prev = await orchestrator.pressKey('Tab', { shift: true });
196
+ ```
197
+
198
+ #### `getState(): Promise<A11yState>`
199
+
200
+ Get the current unified state without pressing a key. Useful after programmatic focus changes.
201
+
202
+ ```typescript
203
+ await page.focus('#my-button');
204
+ const state = await orchestrator.getState();
205
+ ```
206
+
207
+ #### `traverseTabOrder(): Promise<TabOrderReport>`
208
+
209
+ Extract all tabbable elements in DOM tab order.
210
+
211
+ ```typescript
212
+ const report = await orchestrator.traverseTabOrder();
213
+ console.log(report.totalCount); // 12
214
+ console.log(report.entries[0].tag); // "A"
215
+ console.log(report.entries[0].id); // "home-link"
216
+ ```
217
+
218
+ #### `traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>`
219
+
220
+ Detect whether a container traps keyboard focus (WCAG 2.1.2).
221
+
222
+ Focuses the first tabbable element in the container, presses Tab up to `maxTabs` times (default 50), and checks whether focus ever escapes.
223
+
224
+ ```typescript
225
+ const result = await orchestrator.traverseSubTree('#modal-container', 20);
226
+ if (result.isTrapped) {
227
+ console.log('Keyboard trap detected!');
228
+ console.log(`Focus visited ${result.visitedElements.length} elements`);
229
+ } else {
230
+ console.log(`Focus escaped to: ${result.escapeElement?.tag}`);
231
+ }
232
+ ```
233
+
234
+ ### Types
235
+
236
+ #### `CDPSessionLike`
237
+
238
+ The abstraction boundary between the engine and test frameworks:
239
+
240
+ ```typescript
241
+ interface CDPSessionLike {
242
+ send(method: 'Accessibility.enable'): Promise<void>;
243
+ send(method: 'Accessibility.disable'): Promise<void>;
244
+ send(
245
+ method: 'Accessibility.getFullAXTree',
246
+ params?: { depth?: number; frameId?: string }
247
+ ): Promise<{ nodes: AXNode[] }>;
248
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
249
+ }
250
+ ```
251
+
252
+ Both Playwright's `CDPSession` and `chrome-remote-interface` clients satisfy this interface without adapters.
253
+
254
+ #### `SpeechResult`
255
+
256
+ Returned by `getSpeech()` and `getFullTreeSpeech()`:
257
+
258
+ ```typescript
259
+ interface SpeechResult {
260
+ speech: string; // "Products, button, collapsed"
261
+ name: string; // "Products"
262
+ role: string; // "button"
263
+ states: string[]; // ["collapsed"]
264
+ rawNode: Protocol.Accessibility.AXNode;
265
+ }
266
+ ```
267
+
268
+ #### `A11yState`
269
+
270
+ Returned by `pressKey()` and `getState()`:
271
+
272
+ ```typescript
273
+ interface A11yState {
274
+ speech: string; // "Products, button, collapsed"
275
+ speechResult: SpeechResult | null; // Full speech result with raw AXNode
276
+ focusedElement: A11yFocusedElement | null; // DOM info
277
+ focusIndicator: A11yFocusIndicator; // CSS analysis
278
+ }
279
+ ```
280
+
281
+ #### `A11yFocusedElement`
282
+
283
+ ```typescript
284
+ interface A11yFocusedElement {
285
+ tag: string; // "BUTTON"
286
+ id: string; // "submit-btn"
287
+ className: string; // "btn primary"
288
+ textContent: string; // "Submit"
289
+ role: string; // "button"
290
+ ariaLabel: string; // "Submit form"
291
+ tabIndex: number; // 0
292
+ rect: { x: number; y: number; width: number; height: number };
293
+ }
294
+ ```
295
+
296
+ #### `A11yFocusIndicator`
297
+
298
+ ```typescript
299
+ interface A11yFocusIndicator {
300
+ isVisible: boolean; // true if outline or box-shadow detected
301
+ contrastRatio: number | null; // null if colors unparseable
302
+ meetsWCAG_AA: boolean; // true if visible and contrast >= 3.0
303
+ }
304
+ ```
305
+
306
+ #### `ModifierKeys`
307
+
308
+ ```typescript
309
+ interface ModifierKeys {
310
+ shift?: boolean;
311
+ ctrl?: boolean;
312
+ alt?: boolean;
313
+ meta?: boolean;
314
+ }
315
+ ```
316
+
317
+ #### `TabOrderReport`
318
+
319
+ ```typescript
320
+ interface TabOrderReport {
321
+ entries: TabOrderEntry[];
322
+ totalCount: number;
323
+ }
324
+ ```
325
+
326
+ #### `TabOrderEntry`
327
+
328
+ ```typescript
329
+ interface TabOrderEntry {
330
+ index: number; // Position in tab order (0-based)
331
+ tag: string; // "BUTTON"
332
+ id: string; // "submit-btn"
333
+ textContent: string;
334
+ tabIndex: number;
335
+ role: string;
336
+ rect: { x: number; y: number; width: number; height: number };
337
+ }
338
+ ```
339
+
340
+ #### `TraversalResult`
341
+
342
+ ```typescript
343
+ interface TraversalResult {
344
+ isTrapped: boolean; // true if focus never escaped
345
+ tabCount: number; // Total Tab presses attempted
346
+ visitedElements: TabOrderEntry[]; // Elements that received focus
347
+ escapeElement: TabOrderEntry | null; // First element outside container
348
+ }
349
+ ```
350
+
351
+ ## Role Mappings
352
+
353
+ The `ROLE_TO_SPEECH` record maps 50+ CDP role values to speech strings. Roles are grouped into four categories:
354
+
355
+ ### Interactive Roles
356
+
357
+ | CDP Role | Speech |
358
+ |----------|--------|
359
+ | `button` | `button` |
360
+ | `link` | `link` |
361
+ | `checkbox` | `checkbox` |
362
+ | `radio` | `radio button` |
363
+ | `textbox` | `edit text` |
364
+ | `combobox` | `combo box` |
365
+ | `slider` | `slider` |
366
+ | `switch` | `switch` |
367
+ | `tab` | `tab` |
368
+ | `menuitem` | `menu item` |
369
+ | `menuitemcheckbox` | `menu item checkbox` |
370
+ | `menuitemradio` | `menu item radio` |
371
+ | `option` | `option` |
372
+ | `searchbox` | `search text` |
373
+ | `spinbutton` | `spin button` |
374
+
375
+ ### Landmark Roles
376
+
377
+ Landmarks automatically append "landmark" when `includeLandmarks` is `true`:
378
+
379
+ | CDP Role | Speech |
380
+ |----------|--------|
381
+ | `navigation` | `navigation landmark` |
382
+ | `main` | `main landmark` |
383
+ | `banner` | `banner landmark` |
384
+ | `contentinfo` | `content info landmark` |
385
+ | `complementary` | `complementary landmark` |
386
+ | `search` | `search landmark` |
387
+ | `region` | `region landmark` |
388
+ | `form` | `form landmark` |
389
+
390
+ ### Structure Roles
391
+
392
+ | CDP Role | Speech |
393
+ |----------|--------|
394
+ | `heading` | `heading` |
395
+ | `list` | `list` |
396
+ | `listitem` | `list item` |
397
+ | `img` | `image` |
398
+ | `table` | `table` |
399
+ | `row` | `row` |
400
+ | `cell` | `cell` |
401
+ | `dialog` | `dialog` |
402
+ | `alert` | `alert` |
403
+ | `menu` | `menu` |
404
+ | `menubar` | `menu bar` |
405
+ | `toolbar` | `toolbar` |
406
+ | `tree` | `tree` |
407
+ | `treeitem` | `tree item` |
408
+ | `tablist` | `tab list` |
409
+ | `tabpanel` | `tab panel` |
410
+ | `progressbar` | `progress bar` |
411
+ | `tooltip` | `tooltip` |
412
+
413
+ ### Silent Roles
414
+
415
+ These roles produce no speech output:
416
+
417
+ `generic`, `none`, `presentation`, `StaticText`, `InlineTextBox`, `LineBreak`, `RootWebArea`, `WebArea`, `paragraph`, `DescriptionListDetail`, `DescriptionListTerm`, `DescriptionList`
418
+
419
+ Unknown roles pass through as-is for forward compatibility with new ARIA roles.
420
+
421
+ ## State Mappings
422
+
423
+ The `STATE_MAPPINGS` array defines how boolean ARIA properties translate to spoken strings. States appear in the output in the order listed below:
424
+
425
+ | CDP Property | `true` | `false` |
426
+ |-------------|--------|---------|
427
+ | `expanded` | `expanded` | `collapsed` |
428
+ | `checked` | `checked` | `not checked` |
429
+ | `selected` | `selected` | *(silent)* |
430
+ | `pressed` | `pressed` | `not pressed` |
431
+ | `disabled` | `dimmed` | *(silent)* |
432
+ | `required` | `required` | *(silent)* |
433
+ | `invalid` | `invalid` | *(silent)* |
434
+ | `readonly` | `read only` | *(silent)* |
435
+ | `multiselectable` | `multi selectable` | *(silent)* |
436
+
437
+ Heading levels are a special case: `level` property produces `level N` (e.g., `level 2`).
438
+
439
+ When multiple states are present, they are joined in the fixed order above:
440
+
441
+ ```
442
+ "Submit, button, expanded, required"
443
+ "Email, edit text, invalid, required"
444
+ ```
445
+
446
+ ## Exports
447
+
448
+ ```typescript
449
+ // Main classes
450
+ export { SpeechEngine } from '@a11y-oracle/core-engine';
451
+ export { A11yOrchestrator } from '@a11y-oracle/core-engine';
452
+
453
+ // Core types
454
+ export type {
455
+ CDPSessionLike,
456
+ SpeechResult,
457
+ SpeechEngineOptions,
458
+ A11yState,
459
+ A11yFocusedElement,
460
+ A11yFocusIndicator,
461
+ A11yOrchestratorOptions,
462
+ } from '@a11y-oracle/core-engine';
463
+
464
+ // Data (for advanced customization)
465
+ export { ROLE_TO_SPEECH, LANDMARK_ROLES } from '@a11y-oracle/core-engine';
466
+ export { STATE_MAPPINGS, extractStates } from '@a11y-oracle/core-engine';
467
+ export type { StateMapping, AXNodeProperty } from '@a11y-oracle/core-engine';
468
+
469
+ // Re-exports from sub-engines
470
+ export type { ModifierKeys, FocusedElementInfo, KeyDefinition } from '@a11y-oracle/core-engine';
471
+ export { KEY_DEFINITIONS } from '@a11y-oracle/core-engine';
472
+ export type { FocusIndicator, TabOrderEntry, TabOrderReport, TraversalResult } from '@a11y-oracle/core-engine';
473
+ ```
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @module @a11y-oracle/core-engine
3
+ *
4
+ * Core speech engine for A11y-Oracle. Connects to Chrome DevTools Protocol,
5
+ * fetches the Accessibility Tree, and generates standardized speech output
6
+ * based on W3C specifications.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { SpeechEngine } from '@a11y-oracle/core-engine';
11
+ * import type { CDPSessionLike } from '@a11y-oracle/core-engine';
12
+ *
13
+ * // Create engine with any CDP-compatible session
14
+ * const engine = new SpeechEngine(cdpSession);
15
+ * await engine.enable();
16
+ *
17
+ * // Get speech for the focused element
18
+ * const result = await engine.getSpeech();
19
+ * console.log(result?.speech); // "Products, button, collapsed"
20
+ * ```
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+ export { SpeechEngine } from './lib/speech-engine.js';
25
+ export { A11yOrchestrator } from './lib/a11y-orchestrator.js';
26
+ export type { CDPSessionLike, SpeechResult, SpeechEngineOptions, A11yState, A11yFocusedElement, A11yFocusIndicator, A11yOrchestratorOptions, } from './lib/types.js';
27
+ export { ROLE_TO_SPEECH, LANDMARK_ROLES } from './lib/role-map.js';
28
+ export { STATE_MAPPINGS, extractStates } from './lib/state-map.js';
29
+ export type { StateMapping, AXNodeProperty } from './lib/state-map.js';
30
+ export type { ModifierKeys, FocusedElementInfo, KeyDefinition, } from '@a11y-oracle/keyboard-engine';
31
+ export { KEY_DEFINITIONS } from '@a11y-oracle/keyboard-engine';
32
+ export type { FocusIndicator, TabOrderEntry, TabOrderReport, TraversalResult, } from '@a11y-oracle/focus-analyzer';
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGvE,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,aAAa,GACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,YAAY,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @module @a11y-oracle/core-engine
3
+ *
4
+ * Core speech engine for A11y-Oracle. Connects to Chrome DevTools Protocol,
5
+ * fetches the Accessibility Tree, and generates standardized speech output
6
+ * based on W3C specifications.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { SpeechEngine } from '@a11y-oracle/core-engine';
11
+ * import type { CDPSessionLike } from '@a11y-oracle/core-engine';
12
+ *
13
+ * // Create engine with any CDP-compatible session
14
+ * const engine = new SpeechEngine(cdpSession);
15
+ * await engine.enable();
16
+ *
17
+ * // Get speech for the focused element
18
+ * const result = await engine.getSpeech();
19
+ * console.log(result?.speech); // "Products, button, collapsed"
20
+ * ```
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+ export { SpeechEngine } from './lib/speech-engine.js';
25
+ export { A11yOrchestrator } from './lib/a11y-orchestrator.js';
26
+ export { ROLE_TO_SPEECH, LANDMARK_ROLES } from './lib/role-map.js';
27
+ export { STATE_MAPPINGS, extractStates } from './lib/state-map.js';
28
+ export { KEY_DEFINITIONS } from '@a11y-oracle/keyboard-engine';
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @module a11y-orchestrator
3
+ *
4
+ * Coordinates the three sub-engines (Speech, Keyboard, Focus) into a
5
+ * single `pressKey()` → unified-state workflow.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { A11yOrchestrator } from '@a11y-oracle/core-engine';
10
+ *
11
+ * const oracle = new A11yOrchestrator(cdpSession);
12
+ * await oracle.enable();
13
+ *
14
+ * const state = await oracle.pressKey('Tab');
15
+ * console.log(state.speech); // "Products, button, collapsed"
16
+ * console.log(state.focusedElement?.tag); // "BUTTON"
17
+ * console.log(state.focusIndicator.meetsWCAG_AA); // true
18
+ * ```
19
+ */
20
+ import type { CDPSessionLike, A11yState, A11yOrchestratorOptions } from './types.js';
21
+ import type { ModifierKeys } from '@a11y-oracle/keyboard-engine';
22
+ import type { TabOrderReport, TraversalResult } from '@a11y-oracle/focus-analyzer';
23
+ /**
24
+ * Orchestrates speech, keyboard dispatch, and focus analysis into a
25
+ * unified accessibility testing API.
26
+ *
27
+ * A single `pressKey()` call dispatches a keystroke, waits for focus
28
+ * to settle, then collects speech output, focused element info, and
29
+ * focus indicator analysis in parallel.
30
+ *
31
+ * ## Sub-engines
32
+ *
33
+ * | Engine | Responsibility |
34
+ * |--------|---------------|
35
+ * | {@link SpeechEngine} | AXTree → speech string |
36
+ * | {@link KeyboardEngine} | CDP key dispatch + `document.activeElement` |
37
+ * | {@link FocusAnalyzer} | CSS focus indicator + tab order + trap detection |
38
+ */
39
+ export declare class A11yOrchestrator {
40
+ private speech;
41
+ private keyboard;
42
+ private focusAnalyzer;
43
+ private options;
44
+ /**
45
+ * @param cdp - CDP session for sending protocol commands.
46
+ * @param options - Optional configuration for speech output and focus settling.
47
+ */
48
+ constructor(cdp: CDPSessionLike, options?: A11yOrchestratorOptions);
49
+ /**
50
+ * Enable the CDP Accessibility domain.
51
+ *
52
+ * Must be called before any other method.
53
+ */
54
+ enable(): Promise<void>;
55
+ /**
56
+ * Disable the CDP Accessibility domain.
57
+ *
58
+ * Call this when done to free browser resources.
59
+ */
60
+ disable(): Promise<void>;
61
+ /**
62
+ * Dispatch a key press and return the unified accessibility state.
63
+ *
64
+ * 1. Dispatches `keyDown` + `keyUp` via CDP `Input.dispatchKeyEvent`.
65
+ * 2. Waits {@link A11yOrchestratorOptions.focusSettleMs} for transitions.
66
+ * 3. Collects speech, focused element, and focus indicator **in parallel**.
67
+ *
68
+ * @param key - Key name (e.g. `'Tab'`, `'Enter'`, `'ArrowDown'`).
69
+ * @param modifiers - Optional modifier keys.
70
+ * @returns Unified accessibility state snapshot.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const state = await orchestrator.pressKey('Tab');
75
+ * expect(state.speech).toBe('Products, button, collapsed');
76
+ * expect(state.focusIndicator.meetsWCAG_AA).toBe(true);
77
+ * ```
78
+ */
79
+ pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>;
80
+ /**
81
+ * Get the current unified accessibility state without pressing a key.
82
+ *
83
+ * Collects speech, focused element, and focus indicator in parallel.
84
+ *
85
+ * @returns Unified accessibility state snapshot.
86
+ */
87
+ getState(): Promise<A11yState>;
88
+ /**
89
+ * Extract all tabbable elements in DOM tab order.
90
+ *
91
+ * @returns Report with sorted tab order entries and total count.
92
+ */
93
+ traverseTabOrder(): Promise<TabOrderReport>;
94
+ /**
95
+ * Detect whether a container traps keyboard focus.
96
+ *
97
+ * Focuses the first tabbable element in the container, then
98
+ * presses Tab repeatedly. If focus never escapes after `maxTabs`
99
+ * presses, the container is a keyboard trap (WCAG 2.1.2 failure).
100
+ *
101
+ * @param selector - CSS selector for the container to test.
102
+ * @param maxTabs - Maximum Tab presses before declaring a trap. Default 50.
103
+ * @returns Traversal result indicating whether focus is trapped.
104
+ */
105
+ traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>;
106
+ /**
107
+ * Map a raw `FocusedElementInfo` from keyboard-engine to the
108
+ * orchestrator's `A11yFocusedElement` shape.
109
+ */
110
+ private mapFocusedElement;
111
+ }
112
+ //# sourceMappingURL=a11y-orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y-orchestrator.d.ts","sourceRoot":"","sources":["../../src/lib/a11y-orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EAGT,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,YAAY,EAAsB,MAAM,8BAA8B,CAAC;AAErF,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAoC;IAEnD;;;OAGG;gBACS,GAAG,EAAE,cAAc,EAAE,OAAO,GAAE,uBAA4B;IAWtE;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;;;;;;;;;;;;;;;OAiBG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAazE;;;;;;OAMG;IACG,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC;IA0BpC;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,cAAc,CAAC;IAQjD;;;;;;;;;;OAUG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC;IAI3B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CAY1B"}