@reticular/speakable 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.
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Canonical Announcement Model - Version 1
3
+ *
4
+ * Platform-agnostic representation of accessibility semantics.
5
+ * Deterministic, serializable, suitable for snapshot testing.
6
+ */
7
+ /**
8
+ * Model version for forward compatibility.
9
+ * Breaking changes increment major version.
10
+ */
11
+ interface ModelVersion {
12
+ major: number;
13
+ minor: number;
14
+ }
15
+ /**
16
+ * Accessible role following ARIA specification.
17
+ * V1 supports common interactive and structural roles.
18
+ */
19
+ type AccessibleRole = 'button' | 'link' | 'heading' | 'textbox' | 'checkbox' | 'radio' | 'combobox' | 'listbox' | 'option' | 'list' | 'listitem' | 'article' | 'generic' | 'navigation' | 'main' | 'banner' | 'contentinfo' | 'region' | 'img' | 'complementary' | 'form' | 'search' | 'paragraph' | 'blockquote' | 'code' | 'staticText' | 'table' | 'row' | 'cell' | 'columnheader' | 'rowheader' | 'term' | 'definition' | 'figure' | 'caption' | 'group' | 'dialog' | 'meter' | 'progressbar' | 'status' | 'document' | 'application' | 'separator';
20
+ /**
21
+ * Accessible states and properties.
22
+ * Only includes properties relevant to the element's role.
23
+ */
24
+ interface AccessibleState {
25
+ /** Whether the element is expanded (e.g., accordion, dropdown) */
26
+ expanded?: boolean;
27
+ /** Whether the element is checked (checkbox, radio, or mixed state) */
28
+ checked?: boolean | 'mixed';
29
+ /** Whether the element is pressed (toggle button) */
30
+ pressed?: boolean | 'mixed';
31
+ /** Whether the element is selected (option, tab, etc.) */
32
+ selected?: boolean;
33
+ /** Whether the element is disabled */
34
+ disabled?: boolean;
35
+ /** Whether the element has invalid input */
36
+ invalid?: boolean;
37
+ /** Whether the element is required */
38
+ required?: boolean;
39
+ /** Whether the element is read-only */
40
+ readonly?: boolean;
41
+ /** Whether the element is busy loading */
42
+ busy?: boolean;
43
+ /** Current page/step/location indicator */
44
+ current?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | false;
45
+ /** Whether the element is grabbed for drag-and-drop */
46
+ grabbed?: boolean;
47
+ /** Whether the element is hidden from accessibility tree */
48
+ hidden?: boolean;
49
+ /** Heading level (1-6) */
50
+ level?: number;
51
+ /** Position in set (1-indexed) for lists, tabs, etc. */
52
+ posinset?: number;
53
+ /** Total size of set */
54
+ setsize?: number;
55
+ }
56
+ /**
57
+ * Value for form controls and range widgets.
58
+ */
59
+ interface AccessibleValue {
60
+ /** Current value */
61
+ current: string | number;
62
+ /** Minimum value (for range widgets) */
63
+ min?: number;
64
+ /** Maximum value (for range widgets) */
65
+ max?: number;
66
+ /** Textual representation (e.g., "50%") */
67
+ text?: string;
68
+ }
69
+ /**
70
+ * Focusability information.
71
+ */
72
+ interface FocusInfo {
73
+ /** Whether the element can receive focus */
74
+ focusable: boolean;
75
+ /** Explicit tabindex value (only present if explicitly set) */
76
+ tabindex?: number;
77
+ }
78
+ /**
79
+ * Single node in the accessibility tree.
80
+ */
81
+ interface AccessibleNode {
82
+ /** ARIA role of the element */
83
+ role: AccessibleRole;
84
+ /** Accessible name (computed via ARIA name algorithm) */
85
+ name: string;
86
+ /** Accessible description (aria-describedby, title, etc.) */
87
+ description?: string;
88
+ /** Value for form controls */
89
+ value?: AccessibleValue;
90
+ /** State properties */
91
+ state: AccessibleState;
92
+ /** Focus information */
93
+ focus: FocusInfo;
94
+ /** Child nodes in the accessibility tree */
95
+ children: AccessibleNode[];
96
+ }
97
+ /**
98
+ * Root of the Canonical Announcement Model.
99
+ */
100
+ interface AnnouncementModel {
101
+ /** Model version for forward compatibility */
102
+ version: ModelVersion;
103
+ /** Root node of the accessibility tree */
104
+ root: AccessibleNode;
105
+ /** Metadata about the extraction */
106
+ metadata: {
107
+ /** ISO 8601 timestamp of extraction */
108
+ extractedAt: string;
109
+ /** Optional hash of source HTML for change detection */
110
+ sourceHash?: string;
111
+ };
112
+ }
113
+ /**
114
+ * Supported roles as a constant array for validation.
115
+ */
116
+ declare const SUPPORTED_ROLES: readonly AccessibleRole[];
117
+ /**
118
+ * Current model version constant.
119
+ */
120
+ declare const CURRENT_MODEL_VERSION: ModelVersion;
121
+
122
+ /**
123
+ * Validation functions for the Canonical Announcement Model.
124
+ */
125
+
126
+ /**
127
+ * Validation error class.
128
+ */
129
+ declare class ValidationError extends Error {
130
+ constructor(message: string);
131
+ }
132
+ /**
133
+ * Validates that a role is supported in V1.
134
+ *
135
+ * @param role - The role to validate
136
+ * @returns true if valid
137
+ * @throws ValidationError if invalid
138
+ */
139
+ declare function validateRole(role: string): role is AccessibleRole;
140
+ /**
141
+ * Validates accessible state constraints.
142
+ *
143
+ * @param state - The state object to validate
144
+ * @throws ValidationError if invalid
145
+ */
146
+ declare function validateState(state: AccessibleState): void;
147
+ /**
148
+ * Validates tree structure integrity (no cycles).
149
+ *
150
+ * @param node - The root node to validate
151
+ * @param visited - Set of visited nodes (for cycle detection)
152
+ * @throws ValidationError if cycles detected
153
+ */
154
+ declare function validateTreeStructure(node: AccessibleNode, visited?: Set<AccessibleNode>): void;
155
+ /**
156
+ * Validates an entire AnnouncementModel.
157
+ *
158
+ * @param model - The model to validate
159
+ * @throws ValidationError if invalid
160
+ */
161
+ declare function validateModel(model: AnnouncementModel): void;
162
+
163
+ /**
164
+ * JSON serialization and deserialization for the Canonical Announcement Model.
165
+ *
166
+ * Ensures deterministic output with consistent property ordering.
167
+ */
168
+
169
+ /**
170
+ * Serialization options.
171
+ */
172
+ interface SerializationOptions {
173
+ /** Whether to pretty-print the JSON output */
174
+ pretty?: boolean;
175
+ /** Whether to validate the model before serialization */
176
+ validate?: boolean;
177
+ }
178
+ /**
179
+ * Deserialization options.
180
+ */
181
+ interface DeserializationOptions {
182
+ /** Whether to validate the model after deserialization */
183
+ validate?: boolean;
184
+ }
185
+ /**
186
+ * Serializes an AnnouncementModel to JSON string.
187
+ *
188
+ * Produces deterministic output with consistent property ordering.
189
+ *
190
+ * @param model - The model to serialize
191
+ * @param options - Serialization options
192
+ * @returns JSON string representation
193
+ * @throws ValidationError if validation is enabled and model is invalid
194
+ */
195
+ declare function serializeModel(model: AnnouncementModel, options?: SerializationOptions): string;
196
+ /**
197
+ * Deserializes a JSON string to an AnnouncementModel.
198
+ *
199
+ * @param json - JSON string to deserialize
200
+ * @param options - Deserialization options
201
+ * @returns Parsed AnnouncementModel
202
+ * @throws SyntaxError if JSON is invalid
203
+ * @throws ValidationError if validation is enabled and model is invalid
204
+ */
205
+ declare function deserializeModel(json: string, options?: DeserializationOptions): AnnouncementModel;
206
+ /**
207
+ * Creates a new AnnouncementModel with current version and timestamp.
208
+ *
209
+ * @param root - Root accessible node
210
+ * @param sourceHash - Optional hash of source HTML
211
+ * @returns New AnnouncementModel
212
+ */
213
+ declare function createModel(root: AccessibleNode, sourceHash?: string): AnnouncementModel;
214
+ /**
215
+ * Compares two models for equality (deep comparison).
216
+ *
217
+ * @param a - First model
218
+ * @param b - Second model
219
+ * @returns true if models are equivalent
220
+ */
221
+ declare function modelsEqual(a: AnnouncementModel, b: AnnouncementModel): boolean;
222
+ /**
223
+ * Clones a model (deep copy).
224
+ *
225
+ * @param model - Model to clone
226
+ * @returns Deep copy of the model
227
+ */
228
+ declare function cloneModel(model: AnnouncementModel): AnnouncementModel;
229
+
230
+ /**
231
+ * Semantic diff types for comparing accessibility trees.
232
+ */
233
+
234
+ /**
235
+ * Type of change detected in the diff.
236
+ */
237
+ type ChangeType = 'added' | 'removed' | 'changed';
238
+ /**
239
+ * JSON path to a node in the tree (e.g., "root.children[0].children[1]").
240
+ */
241
+ type NodePath = string;
242
+ /**
243
+ * Details about what changed in a node.
244
+ */
245
+ interface PropertyChange {
246
+ /** Property that changed */
247
+ property: 'role' | 'name' | 'description' | 'value' | 'state' | 'focus';
248
+ /** Old value (undefined for added nodes) */
249
+ oldValue?: unknown;
250
+ /** New value (undefined for removed nodes) */
251
+ newValue?: unknown;
252
+ }
253
+ /**
254
+ * A single change in the accessibility tree.
255
+ */
256
+ interface NodeChange {
257
+ /** Type of change */
258
+ type: ChangeType;
259
+ /** JSON path to the node */
260
+ path: NodePath;
261
+ /** The node itself (for added/removed) or property changes (for changed) */
262
+ node?: AccessibleNode;
263
+ /** Specific property changes (only for type='changed') */
264
+ changes?: PropertyChange[];
265
+ }
266
+ /**
267
+ * Complete diff between two accessibility trees.
268
+ */
269
+ interface SemanticDiff {
270
+ /** All detected changes */
271
+ changes: NodeChange[];
272
+ /** Summary statistics */
273
+ summary: {
274
+ added: number;
275
+ removed: number;
276
+ changed: number;
277
+ total: number;
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Semantic diff algorithm for accessibility trees.
283
+ *
284
+ * Compares two accessibility trees and identifies:
285
+ * - Added nodes (present in new tree, not in old)
286
+ * - Removed nodes (present in old tree, not in new)
287
+ * - Changed nodes (same path, different properties)
288
+ */
289
+
290
+ /**
291
+ * Compare two accessibility trees and generate a semantic diff.
292
+ *
293
+ * @param oldTree - The original accessibility tree
294
+ * @param newTree - The updated accessibility tree
295
+ * @returns Semantic diff with all detected changes
296
+ */
297
+ declare function diffAccessibilityTrees(oldTree: AccessibleNode, newTree: AccessibleNode): SemanticDiff;
298
+ /**
299
+ * Generate a human-readable description of a change.
300
+ *
301
+ * @param change - The change to describe
302
+ * @returns Human-readable description
303
+ */
304
+ declare function describeChange(change: NodeChange): string;
305
+
306
+ /**
307
+ * Formatting utilities for semantic diff output.
308
+ *
309
+ * Provides JSON and human-readable text formatting for diffs.
310
+ */
311
+
312
+ /**
313
+ * Format a semantic diff as pretty-printed JSON.
314
+ *
315
+ * @param diff - The semantic diff to format
316
+ * @returns JSON string with 2-space indentation
317
+ */
318
+ declare function formatDiffAsJSON(diff: SemanticDiff): string;
319
+ /**
320
+ * Format a semantic diff as human-readable text.
321
+ *
322
+ * @param diff - The semantic diff to format
323
+ * @returns Multi-line text description of changes
324
+ */
325
+ declare function formatDiffAsText(diff: SemanticDiff): string;
326
+ /**
327
+ * Format a semantic diff for CI/CD tools (GitHub Actions, GitLab CI, etc.).
328
+ *
329
+ * Uses a format that's easy to parse and diff-friendly.
330
+ *
331
+ * @param diff - The semantic diff to format
332
+ * @returns CI-friendly text output
333
+ */
334
+ declare function formatDiffForCI(diff: SemanticDiff): string;
335
+ /**
336
+ * Check if a diff represents a regression (accessibility got worse).
337
+ *
338
+ * Heuristics:
339
+ * - Removed nodes with important roles (button, link, heading, etc.)
340
+ * - Changed nodes that lost accessible names
341
+ * - Changed nodes that became disabled or hidden
342
+ *
343
+ * @param diff - The semantic diff to analyze
344
+ * @returns True if potential regression detected
345
+ */
346
+ declare function hasAccessibilityRegression(diff: SemanticDiff): boolean;
347
+
348
+ export { type AccessibleNode, type AccessibleRole, type AccessibleState, type AccessibleValue, type AnnouncementModel, CURRENT_MODEL_VERSION, type ChangeType, type DeserializationOptions, type FocusInfo, type ModelVersion, type NodeChange, type NodePath, type PropertyChange, SUPPORTED_ROLES, type SemanticDiff, type SerializationOptions, ValidationError, cloneModel, createModel, describeChange, deserializeModel, diffAccessibilityTrees, formatDiffAsJSON, formatDiffAsText, formatDiffForCI, hasAccessibilityRegression, modelsEqual, serializeModel, validateModel, validateRole, validateState, validateTreeStructure };