@reticular/speakable 1.6.0 → 1.7.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 CHANGED
@@ -258,7 +258,47 @@ Severity levels:
258
258
 
259
259
  ### Storybook Integration
260
260
 
261
- Connect to a running Storybook instance to analyze accessibility behavior across your component library:
261
+ There are two ways to use Speakable with Storybook: the **in-editor addon** (see timelines while you develop) and the **CLI adapter** (analyze the whole library headlessly in CI).
262
+
263
+ **In-editor addon** — adds a "Screen Readers" panel with per-reader tabs, an Audit tab, plus **Timeline** and **Diff** tabs for interaction testing:
264
+
265
+ ```typescript
266
+ // .storybook/main.ts
267
+ const config = {
268
+ addons: ['@reticular/storybook-addon-speakable'],
269
+ };
270
+ export default config;
271
+ ```
272
+
273
+ Static tabs work with zero config. To capture an interaction timeline for a story, add `parameters.speakable` with either a named built-in `pattern` or a full `sequence`:
274
+
275
+ ```typescript
276
+ export const Dialog = {
277
+ render: () => renderDialog(),
278
+ parameters: {
279
+ speakable: {
280
+ pattern: 'modal-dialog', // 'modal-dialog' | 'combobox' | 'tabs' | 'accordion'
281
+ selectors: { trigger: 'button.open', container: '[role="dialog"]' },
282
+ },
283
+ },
284
+ };
285
+
286
+ export const Disclosure = {
287
+ render: () => renderDisclosure(),
288
+ parameters: {
289
+ speakable: {
290
+ sequence: {
291
+ description: 'Tab to the toggle and open it',
292
+ actions: [{ type: 'tab' }, { type: 'enter' }],
293
+ },
294
+ },
295
+ },
296
+ };
297
+ ```
298
+
299
+ The sequence runs automatically after the story renders; the Timeline tab shows the resulting focus moves, ARIA state changes, live-region announcements, and dialog events. Click **Set baseline** on the Timeline tab, and the Diff tab will flag behavioral regressions (severity-classified) on subsequent renders. See the [addon README](./addon-speakable/README.md) for the full `parameters.speakable` reference.
300
+
301
+ **CLI adapter** — connect to a running Storybook instance to analyze behavior across your library headlessly:
262
302
 
263
303
  ```typescript
264
304
  import { createStorybookAdapter, createStoryLoader } from '@reticular/speakable/storybook';
@@ -277,6 +317,50 @@ The Storybook pipeline discovers stories, loads them in isolation, runs interact
277
317
 
278
318
  > See the [Runtime Analysis docs](https://getspeakable.dev/docs/runtime-analysis) for interactive demos and full API reference.
279
319
 
320
+ ### Browser Bundle and Iframe Harness
321
+
322
+ For runtime testing outside Storybook — including web components (Lit, shadow DOM) that need a real browser — Speakable ships two transport-agnostic entry points.
323
+
324
+ **`@reticular/speakable/browser`** runs analysis and capture directly against live DOM in the current page:
325
+
326
+ ```typescript
327
+ import { analyzeElement, captureTimeline } from '@reticular/speakable/browser';
328
+
329
+ // Static analysis of a live element (per-reader output + audit + stats + warnings)
330
+ const result = analyzeElement(document.querySelector('#widget'));
331
+ console.log(result.nvda);
332
+
333
+ // Capture an interaction timeline against the live document
334
+ const timeline = await captureTimeline(document, {
335
+ componentName: 'Menu',
336
+ sequence: {
337
+ description: 'open and arrow down',
338
+ actions: [{ type: 'click', selector: '#menu-btn' }, { type: 'arrowDown' }],
339
+ },
340
+ });
341
+ ```
342
+
343
+ **`@reticular/speakable/harness`** mounts a component into an iframe (via a URL or an HTML string), injects the bundle, and drives analysis over `postMessage` — so you can test any component in isolation in a real browser. Custom elements are awaited (`whenDefined` / Lit `updateComplete`) before analysis, and open shadow roots with `<slot>` projection are handled:
344
+
345
+ ```typescript
346
+ import { createHarness } from '@reticular/speakable/harness';
347
+
348
+ const harness = createHarness({
349
+ target: { container: document.body }, // harness creates + owns the iframe
350
+ bundleUrl: '/speakable-browser.global.js', // the injectable IIFE bundle
351
+ });
352
+
353
+ await harness.load({ html: '<my-widget>Content</my-widget>' });
354
+ const result = await harness.analyze('my-widget');
355
+ const timeline = await harness.captureTimeline({
356
+ componentName: 'MyWidget',
357
+ sequence: { description: 'toggle', actions: [{ type: 'click', selector: 'my-widget' }] },
358
+ });
359
+ harness.destroy();
360
+ ```
361
+
362
+ The harness supports same-origin content only (`srcdoc` HTML or a same-origin URL); cross-origin URLs are rejected with a descriptive error, since a script can't be injected across origins. See [`examples/harness-demo.html`](./examples/harness-demo.html) for a runnable simple + complex (Lit-style shadow DOM) example.
363
+
280
364
  ## MCP Integration (AI Assistants)
281
365
 
282
366
  Speakable includes a Model Context Protocol (MCP) server that lets AI coding assistants analyze accessibility in real-time. Ask your assistant "check if this component is accessible" and it calls Speakable automatically.
@@ -390,7 +474,7 @@ Speakable processes HTML through a four-stage pipeline:
390
474
  - Replace manual screen reader testing with real assistive technology
391
475
  - Guarantee WCAG compliance
392
476
  - Perfectly replicate screen reader behavior (output is heuristic)
393
- - Execute JavaScript in static analysis mode (use runtime mode for dynamic content)
477
+ - Execute JavaScript in static analysis mode (use the CLI `runtime` command, the Storybook addon's Timeline, or the browser/harness entry points for dynamic content)
394
478
 
395
479
  Use Speakable to catch issues early and reduce the manual testing burden. Validate critical flows with real screen readers.
396
480
 
@@ -0,0 +1,58 @@
1
+ import { I as InteractionSequence, A as AccessibilityTimeline } from '../types-Blh0EnZF.js';
2
+ export { a as AnalysisResult, b as AuditFinding, c as analyzeElement, d as analyzeElementWithUpgrade } from '../types-Blh0EnZF.js';
3
+
4
+ /**
5
+ * Transport-agnostic interaction timeline capture.
6
+ *
7
+ * Wraps the core runtime engine (`src/runtime`) so any real-browser surface
8
+ * (Storybook addon, iframe harness, extension) can run an interaction
9
+ * sequence against a live document and receive a serializable timeline.
10
+ */
11
+
12
+ interface CaptureOptions {
13
+ /** Component name for timeline metadata. */
14
+ componentName: string;
15
+ /** Story or variant name for timeline metadata. */
16
+ storyName?: string;
17
+ /** Ordered interaction sequence to execute during capture. */
18
+ sequence: InteractionSequence;
19
+ /** Settle period after the last action (ms). */
20
+ settlePeriod?: number;
21
+ /** Await custom-element upgrade before capture (default: true). */
22
+ awaitUpgrade?: boolean;
23
+ /** Maximum time to wait for custom elements to upgrade (ms). */
24
+ upgradeTimeoutMs?: number;
25
+ }
26
+ /**
27
+ * Attach the runtime engine to `document`, run the interaction sequence,
28
+ * and return a serializable accessibility timeline.
29
+ *
30
+ * When `awaitUpgrade` is enabled (default), waits for custom elements to
31
+ * upgrade first so web components (e.g. Lit) are analyzed post-hydration.
32
+ * Any upgrade warnings are appended to the timeline's `warnings` array.
33
+ */
34
+ declare function captureTimeline(document: Document, options: CaptureOptions): Promise<AccessibilityTimeline>;
35
+
36
+ /**
37
+ * Custom-element upgrade awaiting.
38
+ *
39
+ * Runs in a real browser (needs `customElements`). Waits for custom elements
40
+ * under a root to be defined and, where supported, to finish rendering
41
+ * (Lit's `updateComplete`) before analysis/capture proceeds. Bounded by a
42
+ * timeout so it never blocks indefinitely; unresolved tags become warnings.
43
+ */
44
+ interface UpgradeWarning {
45
+ message: string;
46
+ tag: string;
47
+ }
48
+ /**
49
+ * Wait for custom elements under `root` to upgrade and render.
50
+ *
51
+ * - Awaits `customElements.whenDefined(tag)` for each custom tag (bounded).
52
+ * - Awaits any `updateComplete` promises found (bounded).
53
+ * - On timeout, resolves anyway and returns a warning per unresolved tag.
54
+ * - Never blocks indefinitely. No-op when `customElements` is unavailable.
55
+ */
56
+ declare function awaitCustomElementsReady(root: Document | Element, timeoutMs?: number): Promise<UpgradeWarning[]>;
57
+
58
+ export { type CaptureOptions, type UpgradeWarning, awaitCustomElementsReady, captureTimeline };