@generative-a11y/dom 0.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/dist/index.js ADDED
@@ -0,0 +1,976 @@
1
+ // src/composed-tree.ts
2
+ function deepActiveElement(document2) {
3
+ let active = document2.activeElement;
4
+ const visited = /* @__PURE__ */ new Set();
5
+ while (active) {
6
+ if (visited.has(active)) throw new Error("Cyclic active element");
7
+ visited.add(active);
8
+ const shadow = active.shadowRoot;
9
+ const nested = shadow?.activeElement;
10
+ if (!nested) return active;
11
+ active = nested;
12
+ }
13
+ return null;
14
+ }
15
+ function composedContains(ancestor, target) {
16
+ let current = target;
17
+ const visited = /* @__PURE__ */ new Set();
18
+ while (current) {
19
+ if (current === ancestor) return true;
20
+ if (visited.has(current)) throw new Error("Cyclic composed tree");
21
+ visited.add(current);
22
+ current = composedParent(current);
23
+ }
24
+ return false;
25
+ }
26
+ function composedParent(element) {
27
+ const assignedSlot = element.assignedSlot;
28
+ if (assignedSlot) return assignedSlot;
29
+ if (element.parentElement) return element.parentElement;
30
+ const root = element.getRootNode();
31
+ if (root && "host" in root) {
32
+ const host = root.host;
33
+ if (host.ownerDocument === element.ownerDocument) return host;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ // src/attention.ts
39
+ var UNKNOWN_SNAPSHOT = Object.freeze({
40
+ visibility: "unknown",
41
+ windowFocus: "unknown",
42
+ focusArea: "unknown",
43
+ newestResponse: "unknown",
44
+ mode: "unknown"
45
+ });
46
+ function createAttentionStore(options = {}) {
47
+ const selectedDocument = options.document ?? (typeof document === "undefined" ? void 0 : document);
48
+ if (!selectedDocument) {
49
+ return {
50
+ subscribe: () => () => void 0,
51
+ getSnapshot: () => UNKNOWN_SNAPSHOT,
52
+ getServerSnapshot: () => UNKNOWN_SNAPSHOT,
53
+ registerComposer: () => () => void 0,
54
+ registerConversation: () => () => void 0,
55
+ registerNewestResponse: () => () => void 0,
56
+ dispose: () => void 0
57
+ };
58
+ }
59
+ const listeners = /* @__PURE__ */ new Set();
60
+ const composers = /* @__PURE__ */ new Map();
61
+ const conversations = /* @__PURE__ */ new Map();
62
+ const selectedWindow = selectedDocument.defaultView;
63
+ let disposed = false;
64
+ let newestTarget;
65
+ let newestResult = "unobserved";
66
+ let newestRegistration = 0;
67
+ let observer;
68
+ let snapshot = makeSnapshot(
69
+ selectedDocument,
70
+ composers,
71
+ conversations,
72
+ newestResult
73
+ );
74
+ const update = () => {
75
+ if (disposed) return;
76
+ const next = makeSnapshot(
77
+ selectedDocument,
78
+ composers,
79
+ conversations,
80
+ newestResult
81
+ );
82
+ if (sameSnapshot(snapshot, next)) return;
83
+ snapshot = next;
84
+ for (const listener of [...listeners]) {
85
+ try {
86
+ listener();
87
+ } catch {
88
+ }
89
+ }
90
+ };
91
+ const handleFocusOut = (event) => {
92
+ if (event.relatedTarget === null) update();
93
+ };
94
+ const listenerCleanups = [];
95
+ const installListener = (install, cleanup) => {
96
+ listenerCleanups.push(cleanup);
97
+ install();
98
+ };
99
+ const cleanupListeners = () => {
100
+ for (const cleanup of listenerCleanups.splice(0).reverse()) {
101
+ try {
102
+ cleanup();
103
+ } catch {
104
+ }
105
+ }
106
+ };
107
+ try {
108
+ installListener(
109
+ () => selectedDocument.addEventListener("visibilitychange", update),
110
+ () => selectedDocument.removeEventListener("visibilitychange", update)
111
+ );
112
+ if (selectedWindow) {
113
+ installListener(
114
+ () => selectedWindow.addEventListener("focus", update),
115
+ () => selectedWindow.removeEventListener("focus", update)
116
+ );
117
+ installListener(
118
+ () => selectedWindow.addEventListener("blur", update),
119
+ () => selectedWindow.removeEventListener("blur", update)
120
+ );
121
+ }
122
+ installListener(
123
+ () => selectedDocument.addEventListener("focusin", update),
124
+ () => selectedDocument.removeEventListener("focusin", update)
125
+ );
126
+ installListener(
127
+ () => selectedDocument.addEventListener("focusout", handleFocusOut),
128
+ () => selectedDocument.removeEventListener("focusout", handleFocusOut)
129
+ );
130
+ } catch (error) {
131
+ cleanupListeners();
132
+ throw error;
133
+ }
134
+ const register = (elements, element) => {
135
+ if (disposed) throw new Error("AttentionStore is disposed");
136
+ elements.set(element, (elements.get(element) ?? 0) + 1);
137
+ update();
138
+ let registered = true;
139
+ return () => {
140
+ if (!registered) return;
141
+ registered = false;
142
+ const remaining = (elements.get(element) ?? 1) - 1;
143
+ if (remaining === 0) elements.delete(element);
144
+ else elements.set(element, remaining);
145
+ update();
146
+ };
147
+ };
148
+ const createObserver = (registration, target) => {
149
+ const factory = options.createIntersectionObserver ?? defaultIntersectionObserverFactory(selectedWindow);
150
+ if (!factory) return void 0;
151
+ let created;
152
+ try {
153
+ created = factory((entries) => {
154
+ if (disposed || registration !== newestRegistration || newestTarget !== target || observer !== created) {
155
+ return;
156
+ }
157
+ let latest;
158
+ for (const entry of entries) {
159
+ if (entry.target === target) latest = entry;
160
+ }
161
+ if (!latest) return;
162
+ newestResult = latest.isIntersecting ? "visible" : "outside";
163
+ update();
164
+ }, options.intersectionObserverInit);
165
+ created.observe(target);
166
+ return created;
167
+ } catch {
168
+ cleanupObserver(created, target);
169
+ return void 0;
170
+ }
171
+ };
172
+ const stopObserver = (target) => {
173
+ const current = observer;
174
+ observer = void 0;
175
+ cleanupObserver(current, target);
176
+ };
177
+ return {
178
+ subscribe(listener) {
179
+ if (disposed) throw new Error("AttentionStore is disposed");
180
+ listeners.add(listener);
181
+ let subscribed = true;
182
+ return () => {
183
+ if (!subscribed) return;
184
+ subscribed = false;
185
+ listeners.delete(listener);
186
+ };
187
+ },
188
+ getSnapshot: () => snapshot,
189
+ getServerSnapshot: () => UNKNOWN_SNAPSHOT,
190
+ registerComposer: (element) => register(composers, element),
191
+ registerConversation: (element) => register(conversations, element),
192
+ registerNewestResponse(element) {
193
+ if (disposed) throw new Error("AttentionStore is disposed");
194
+ const registration = ++newestRegistration;
195
+ stopObserver(newestTarget);
196
+ newestTarget = element;
197
+ newestResult = "unknown";
198
+ observer = createObserver(registration, element);
199
+ update();
200
+ let registered = true;
201
+ return () => {
202
+ if (!registered) return;
203
+ registered = false;
204
+ if (registration !== newestRegistration || newestTarget !== element)
205
+ return;
206
+ stopObserver(element);
207
+ newestTarget = void 0;
208
+ newestResult = "unobserved";
209
+ update();
210
+ };
211
+ },
212
+ dispose() {
213
+ if (disposed) return;
214
+ disposed = true;
215
+ listeners.clear();
216
+ composers.clear();
217
+ conversations.clear();
218
+ stopObserver(newestTarget);
219
+ newestTarget = void 0;
220
+ cleanupListeners();
221
+ }
222
+ };
223
+ }
224
+ function makeSnapshot(document2, composers, conversations, newestResponse) {
225
+ const visibility = readVisibility(document2);
226
+ const windowFocus = readWindowFocus(document2);
227
+ return Object.freeze({
228
+ visibility,
229
+ windowFocus,
230
+ focusArea: readFocusArea(document2, composers, conversations),
231
+ newestResponse,
232
+ mode: deriveMode(visibility, windowFocus, newestResponse)
233
+ });
234
+ }
235
+ function deriveMode(visibility, windowFocus, newestResponse) {
236
+ if (visibility === "hidden") return "background";
237
+ if (visibility !== "visible") return "unknown";
238
+ if (windowFocus === "blurred") return "away";
239
+ if (windowFocus !== "focused") return "unknown";
240
+ if (newestResponse === "outside") return "reading-history";
241
+ if (newestResponse === "visible") return "foreground";
242
+ return "unknown";
243
+ }
244
+ function defaultIntersectionObserverFactory(window2) {
245
+ const Constructor = window2?.IntersectionObserver;
246
+ if (typeof Constructor !== "function") return void 0;
247
+ return (callback, options) => new Constructor(callback, options);
248
+ }
249
+ function cleanupObserver(observer, target) {
250
+ if (!observer) return;
251
+ if (target) {
252
+ try {
253
+ observer.unobserve(target);
254
+ } catch {
255
+ }
256
+ }
257
+ try {
258
+ observer.disconnect();
259
+ } catch {
260
+ }
261
+ }
262
+ function readFocusArea(document2, composers, conversations) {
263
+ let activeElement;
264
+ try {
265
+ activeElement = deepActiveElement(document2);
266
+ } catch {
267
+ return "unknown";
268
+ }
269
+ if (activeElement === null || activeElement === document2.body || activeElement === document2.documentElement) {
270
+ return "none";
271
+ }
272
+ if ([...composers.keys()].some(
273
+ (element) => composedContains(element, activeElement)
274
+ )) {
275
+ return "composer";
276
+ }
277
+ if ([...conversations.keys()].some(
278
+ (element) => composedContains(element, activeElement)
279
+ )) {
280
+ return "conversation";
281
+ }
282
+ return "elsewhere";
283
+ }
284
+ function readVisibility(document2) {
285
+ try {
286
+ if (document2.visibilityState === "visible") return "visible";
287
+ if (document2.visibilityState === "hidden") return "hidden";
288
+ } catch {
289
+ }
290
+ return "unknown";
291
+ }
292
+ function readWindowFocus(document2) {
293
+ try {
294
+ if (typeof document2.hasFocus !== "function") return "unknown";
295
+ return document2.hasFocus() ? "focused" : "blurred";
296
+ } catch {
297
+ return "unknown";
298
+ }
299
+ }
300
+ function sameSnapshot(left, right) {
301
+ return left.visibility === right.visibility && left.windowFocus === right.windowFocus && left.focusArea === right.focusArea && left.newestResponse === right.newestResponse && left.mode === right.mode;
302
+ }
303
+
304
+ // src/focus.ts
305
+ function captureFocus(selectedDocument) {
306
+ const document2 = selectedDocument ?? (typeof globalThis.document === "undefined" ? void 0 : globalThis.document);
307
+ if (!document2) return Object.freeze({ document: null, target: null });
308
+ let target = null;
309
+ try {
310
+ const active = deepActiveElement(document2);
311
+ if (active !== null && active !== document2.body && active !== document2.documentElement && active.ownerDocument === document2) {
312
+ target = active;
313
+ }
314
+ } catch {
315
+ }
316
+ return Object.freeze({ document: document2, target });
317
+ }
318
+ function focusElement(target, options = {}) {
319
+ const eligibility = focusEligibility(target);
320
+ if (eligibility) {
321
+ return { status: "skipped", reason: eligibility, target };
322
+ }
323
+ let focus;
324
+ let document2;
325
+ let previous;
326
+ try {
327
+ document2 = target.ownerDocument;
328
+ previous = deepActiveElement(document2);
329
+ focus = target.focus;
330
+ } catch {
331
+ return { status: "skipped", reason: "unavailable", target };
332
+ }
333
+ if (typeof focus !== "function") {
334
+ return { status: "skipped", reason: "missing-focus", target };
335
+ }
336
+ let focusThrew = false;
337
+ try {
338
+ focus.call(target, { preventScroll: options.preventScroll ?? true });
339
+ } catch {
340
+ focusThrew = true;
341
+ }
342
+ if (focusThrew) {
343
+ restorePreviousFocus(previous, target, document2);
344
+ return { status: "skipped", reason: "focus-error", target };
345
+ }
346
+ const postFocusEligibility = focusEligibility(target);
347
+ let active;
348
+ try {
349
+ active = deepActiveElement(document2);
350
+ } catch {
351
+ return { status: "skipped", reason: "focus-error", target };
352
+ }
353
+ if (postFocusEligibility) {
354
+ if (active === target) restorePreviousFocus(previous, target, document2);
355
+ return { status: "skipped", reason: postFocusEligibility, target };
356
+ }
357
+ if (active !== target) {
358
+ return { status: "skipped", reason: "focus-not-applied", target };
359
+ }
360
+ return { status: "focused", target };
361
+ }
362
+ function restoreFocus(capture, options = {}) {
363
+ let document2;
364
+ let target;
365
+ try {
366
+ document2 = capture.document;
367
+ target = capture.target;
368
+ } catch {
369
+ return { status: "skipped", reason: "unavailable", target: null };
370
+ }
371
+ if (!document2 || !target) {
372
+ return { status: "skipped", reason: "unavailable", target: null };
373
+ }
374
+ try {
375
+ if (target.ownerDocument !== document2) {
376
+ return { status: "skipped", reason: "cross-document", target };
377
+ }
378
+ const guard = options.onlyIfFocusWithin;
379
+ if (guard !== void 0) {
380
+ const active = deepActiveElement(document2);
381
+ if (guard.ownerDocument !== document2 || active === null || !composedContains(guard, active)) {
382
+ return { status: "skipped", reason: "guard-mismatch", target };
383
+ }
384
+ }
385
+ } catch {
386
+ return { status: "skipped", reason: "guard-mismatch", target };
387
+ }
388
+ return focusElement(target, options);
389
+ }
390
+ function focusEligibility(target) {
391
+ let ownerDocument;
392
+ try {
393
+ ownerDocument = target.ownerDocument;
394
+ const ElementConstructor = ownerDocument.defaultView?.Element;
395
+ if (!ownerDocument || ElementConstructor && !(target instanceof ElementConstructor)) {
396
+ return "unavailable";
397
+ }
398
+ if (target.isConnected !== true) return "disconnected";
399
+ if (target.getAttribute("disabled") !== null || target.disabled === true) {
400
+ return "disabled";
401
+ }
402
+ } catch {
403
+ return "unavailable";
404
+ }
405
+ try {
406
+ const matches = target.matches;
407
+ if (typeof matches === "function" && matches.call(target, ":disabled")) {
408
+ return "disabled";
409
+ }
410
+ } catch {
411
+ }
412
+ try {
413
+ let current = target;
414
+ const visited = /* @__PURE__ */ new Set();
415
+ while (current) {
416
+ if (visited.has(current)) return "unavailable";
417
+ visited.add(current);
418
+ if (current.hasAttribute("hidden")) return "hidden";
419
+ if (current.getAttribute("aria-hidden")?.trim().toLowerCase() === "true")
420
+ return "aria-hidden";
421
+ if (current.hasAttribute("inert") || current.inert === true) {
422
+ return "inert";
423
+ }
424
+ current = composedParent(current);
425
+ }
426
+ } catch {
427
+ return "unavailable";
428
+ }
429
+ try {
430
+ const focus = target.focus;
431
+ if (typeof focus !== "function") return "missing-focus";
432
+ } catch {
433
+ return "unavailable";
434
+ }
435
+ return void 0;
436
+ }
437
+ function restorePreviousFocus(previous, attempted, document2) {
438
+ try {
439
+ if (!previous || deepActiveElement(document2) !== attempted || focusEligibility(previous) !== void 0) {
440
+ return;
441
+ }
442
+ const focus = previous.focus;
443
+ if (typeof focus === "function")
444
+ focus.call(previous, { preventScroll: true });
445
+ } catch {
446
+ }
447
+ }
448
+
449
+ // src/preferences.ts
450
+ var defaultPreferences = Object.freeze({
451
+ version: 1,
452
+ preset: "balanced",
453
+ streaming: "preset",
454
+ tools: "preset"
455
+ });
456
+ function preferencesToCoreConfiguration(value) {
457
+ const preferences = normalizePreferences(value);
458
+ if (preferences.preset === "completion-only") {
459
+ return { preset: "completion-only" };
460
+ }
461
+ const policy = {};
462
+ const text = mapStreaming(preferences.streaming);
463
+ const tools = mapTools(preferences.tools);
464
+ if (text) policy.text = text;
465
+ if (tools) policy.tools = tools;
466
+ return Object.keys(policy).length === 0 ? { preset: preferences.preset } : { preset: preferences.preset, policy };
467
+ }
468
+ function createPreferenceStore(options = {}) {
469
+ const configuredDefault = options.defaultValue ? normalizePreferences(options.defaultValue) : defaultPreferences;
470
+ const persistence = resolvePersistence(options.persistence);
471
+ const report = (source, code, cause) => {
472
+ const diagnostic = {
473
+ source,
474
+ code,
475
+ ...cause === void 0 ? {} : { error: serializeError(cause) }
476
+ };
477
+ try {
478
+ options.onDiagnostic?.(diagnostic);
479
+ } catch {
480
+ }
481
+ };
482
+ let current = configuredDefault;
483
+ if (persistence?.storage) {
484
+ try {
485
+ const stored = persistence.storage.getItem(persistence.key);
486
+ if (stored !== null) {
487
+ current = parsePreferences(stored, "storage-read", report) ?? current;
488
+ }
489
+ } catch (cause) {
490
+ report("storage-read", "operation-failed", cause);
491
+ }
492
+ }
493
+ let disposed = false;
494
+ let eventEpoch = 0;
495
+ const listeners = /* @__PURE__ */ new Set();
496
+ let unsubscribeEvents;
497
+ const notify = () => {
498
+ for (const listener of [...listeners]) {
499
+ try {
500
+ listener();
501
+ } catch {
502
+ }
503
+ }
504
+ };
505
+ const replace = (next) => {
506
+ if (samePreferences(current, next)) return false;
507
+ current = next;
508
+ notify();
509
+ return true;
510
+ };
511
+ const handleStorageEvent = (event) => {
512
+ if (disposed || !persistence) return;
513
+ const epoch = ++eventEpoch;
514
+ let key;
515
+ let newValue;
516
+ let storageArea;
517
+ try {
518
+ key = event.key;
519
+ newValue = event.newValue;
520
+ storageArea = event.storageArea;
521
+ } catch (cause) {
522
+ if (!disposed && epoch === eventEpoch) {
523
+ report("storage-event", "operation-failed", cause);
524
+ }
525
+ return;
526
+ }
527
+ if (disposed || epoch !== eventEpoch) return;
528
+ try {
529
+ if (storageArea != null && storageArea !== persistence.storage) {
530
+ return;
531
+ }
532
+ if (key !== null && key !== persistence.key) return;
533
+ if (key === null || newValue === null) {
534
+ replace(configuredDefault);
535
+ return;
536
+ }
537
+ const next = parsePreferences(newValue, "storage-event", report);
538
+ if (!disposed && epoch === eventEpoch && next) replace(next);
539
+ } catch (cause) {
540
+ if (!disposed && epoch === eventEpoch) {
541
+ report("storage-event", "operation-failed", cause);
542
+ }
543
+ }
544
+ };
545
+ if (persistence?.events) {
546
+ try {
547
+ unsubscribeEvents = persistence.events.subscribe(handleStorageEvent);
548
+ } catch (cause) {
549
+ report("event-subscribe", "operation-failed", cause);
550
+ }
551
+ }
552
+ return {
553
+ subscribe(listener) {
554
+ if (disposed) throw new Error("PreferenceStore is disposed");
555
+ listeners.add(listener);
556
+ let subscribed = true;
557
+ return () => {
558
+ if (!subscribed) return;
559
+ subscribed = false;
560
+ listeners.delete(listener);
561
+ };
562
+ },
563
+ getSnapshot: () => current,
564
+ getServerSnapshot: () => configuredDefault,
565
+ setPreferences(value) {
566
+ if (disposed) throw new Error("PreferenceStore is disposed");
567
+ const next = normalizePreferences(value);
568
+ const changed = replace(next);
569
+ if (!disposed && persistence?.storage && samePreferences(current, next)) {
570
+ const serialized = JSON.stringify(next);
571
+ if (!changed) {
572
+ const epoch = eventEpoch;
573
+ let matchesStoredValue = false;
574
+ try {
575
+ matchesStoredValue = persistence.storage.getItem(persistence.key) === serialized;
576
+ } catch (cause) {
577
+ report("storage-read", "operation-failed", cause);
578
+ }
579
+ if (matchesStoredValue || disposed || epoch !== eventEpoch || !samePreferences(current, next)) {
580
+ return;
581
+ }
582
+ }
583
+ if (disposed || !samePreferences(current, next)) return;
584
+ try {
585
+ persistence.storage.setItem(persistence.key, serialized);
586
+ } catch (cause) {
587
+ report("storage-write", "operation-failed", cause);
588
+ }
589
+ }
590
+ },
591
+ dispose() {
592
+ if (disposed) return;
593
+ disposed = true;
594
+ eventEpoch += 1;
595
+ listeners.clear();
596
+ const unsubscribe = unsubscribeEvents;
597
+ unsubscribeEvents = void 0;
598
+ if (unsubscribe) {
599
+ try {
600
+ unsubscribe();
601
+ } catch (cause) {
602
+ report("event-unsubscribe", "operation-failed", cause);
603
+ }
604
+ }
605
+ }
606
+ };
607
+ }
608
+ function resolvePersistence(persistence) {
609
+ if (!persistence) return void 0;
610
+ if (persistence.storage) return persistence;
611
+ const browser = getBrowserPersistence();
612
+ return {
613
+ key: persistence.key,
614
+ ...browser?.storage ? { storage: browser.storage } : {},
615
+ ...persistence.events ? { events: persistence.events } : browser?.events ? { events: browser.events } : {}
616
+ };
617
+ }
618
+ function getBrowserPersistence() {
619
+ if (typeof window === "undefined") return void 0;
620
+ try {
621
+ const browserWindow = window;
622
+ const storage = browserWindow.localStorage;
623
+ if (!storage) return void 0;
624
+ return {
625
+ storage,
626
+ events: {
627
+ subscribe(listener) {
628
+ const handle = (event) => listener(event);
629
+ browserWindow.addEventListener("storage", handle);
630
+ return () => browserWindow.removeEventListener("storage", handle);
631
+ }
632
+ }
633
+ };
634
+ } catch {
635
+ return void 0;
636
+ }
637
+ }
638
+ function parsePreferences(raw, source, report) {
639
+ let value;
640
+ try {
641
+ value = JSON.parse(raw);
642
+ } catch (cause) {
643
+ report(source, "invalid-json", cause);
644
+ return void 0;
645
+ }
646
+ try {
647
+ return normalizePreferences(value);
648
+ } catch (cause) {
649
+ report(
650
+ source,
651
+ cause instanceof UnsupportedPreferenceVersionError ? "unsupported-version" : "invalid-preference",
652
+ cause
653
+ );
654
+ return void 0;
655
+ }
656
+ }
657
+ var UnsupportedPreferenceVersionError = class extends TypeError {
658
+ };
659
+ function normalizePreferences(value) {
660
+ const fields = snapshotPreferenceFields(value);
661
+ if (fields.version !== 1)
662
+ throw new UnsupportedPreferenceVersionError(
663
+ "Unsupported preference version"
664
+ );
665
+ if (fields.preset === "completion-only") {
666
+ if (!hasExactKeys(fields, ["version", "preset"]))
667
+ throw new TypeError("Invalid completion-only preferences");
668
+ return Object.freeze({ version: 1, preset: "completion-only" });
669
+ }
670
+ if (!["minimal", "balanced", "verbose"].includes(fields.preset))
671
+ throw new TypeError("Invalid preference preset");
672
+ if (!hasExactKeys(fields, ["version", "preset", "streaming", "tools"]))
673
+ throw new TypeError("Invalid preference fields");
674
+ if (!STREAMING_VALUES.includes(fields.streaming))
675
+ throw new TypeError("Invalid streaming preference");
676
+ if (!TOOL_VALUES.includes(fields.tools))
677
+ throw new TypeError("Invalid tool preference");
678
+ return Object.freeze({
679
+ version: 1,
680
+ preset: fields.preset,
681
+ streaming: fields.streaming,
682
+ tools: fields.tools
683
+ });
684
+ }
685
+ var STREAMING_VALUES = [
686
+ "preset",
687
+ "off",
688
+ "completion",
689
+ "paragraph",
690
+ "sentence"
691
+ ];
692
+ var TOOL_VALUES = [
693
+ "preset",
694
+ "off",
695
+ "failures",
696
+ "status",
697
+ "progress"
698
+ ];
699
+ function snapshotPreferenceFields(value) {
700
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
701
+ throw new TypeError("Invalid preferences");
702
+ }
703
+ try {
704
+ const descriptors = Object.getOwnPropertyDescriptors(value);
705
+ const fields = /* @__PURE__ */ Object.create(null);
706
+ for (const key of Reflect.ownKeys(descriptors)) {
707
+ if (typeof key !== "string")
708
+ throw new TypeError("Invalid preference fields");
709
+ const descriptor = descriptors[key];
710
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
711
+ throw new TypeError(
712
+ "Preference fields must be enumerable data properties"
713
+ );
714
+ }
715
+ fields[key] = descriptor.value;
716
+ }
717
+ return fields;
718
+ } catch {
719
+ throw new TypeError("Invalid preferences");
720
+ }
721
+ }
722
+ function hasExactKeys(value, expected) {
723
+ const keys = Object.keys(value);
724
+ return keys.length === expected.length && expected.every((key) => keys.includes(key));
725
+ }
726
+ function samePreferences(left, right) {
727
+ return JSON.stringify(left) === JSON.stringify(right);
728
+ }
729
+ function mapStreaming(value) {
730
+ if (value === "preset") return void 0;
731
+ if (value === "off") return { strategy: "silent" };
732
+ if (value === "completion")
733
+ return { strategy: "completion", minimumCharacters: 0, maximumDelayMs: 0 };
734
+ return { strategy: value };
735
+ }
736
+ function mapTools(value) {
737
+ if (value === "preset") return void 0;
738
+ return {
739
+ announceStart: value === "status" || value === "progress",
740
+ announceProgress: value === "progress",
741
+ announceCompletion: value === "status" || value === "progress",
742
+ announceFailure: value !== "off"
743
+ };
744
+ }
745
+ function serializeError(cause) {
746
+ const fallback = { name: "Error", message: "Unknown error" };
747
+ try {
748
+ if (cause instanceof Error) {
749
+ const name = cause.name;
750
+ const message = cause.message;
751
+ if (typeof name !== "string" || typeof message !== "string") {
752
+ return fallback;
753
+ }
754
+ return { name: name || "Error", message };
755
+ }
756
+ if (typeof cause === "string" || typeof cause === "number" || typeof cause === "boolean" || typeof cause === "bigint" || typeof cause === "symbol") {
757
+ return { name: "Error", message: String(cause) };
758
+ }
759
+ } catch {
760
+ return fallback;
761
+ }
762
+ return fallback;
763
+ }
764
+
765
+ // src/index.ts
766
+ function createDOMAnnouncer(options = {}) {
767
+ validateSuppliedRegions(options);
768
+ const selectedDocument = options.document ?? options.regions?.polite.ownerDocument ?? (typeof document === "undefined" ? void 0 : document);
769
+ const ownsRegions = options.regions === void 0;
770
+ const regions = options.regions ?? createLiveRegions(selectedDocument);
771
+ if (regions) {
772
+ configureRegion(regions.polite, "polite");
773
+ configureRegion(regions.assertive, "assertive");
774
+ }
775
+ let notifierEnabled = true;
776
+ let disposed = false;
777
+ const report = (result) => {
778
+ try {
779
+ options.onDiagnostic?.(result);
780
+ } catch {
781
+ }
782
+ return result;
783
+ };
784
+ return {
785
+ announce(intent) {
786
+ if (disposed) {
787
+ return report({
788
+ status: "disposed",
789
+ method: "none",
790
+ channel: intent.channel
791
+ });
792
+ }
793
+ if (regions) {
794
+ const region = regions[intent.channel];
795
+ applyLocale(region, intent.locale);
796
+ let error;
797
+ if (notifierEnabled && options.mode !== "live-region") {
798
+ let notified = false;
799
+ try {
800
+ const ariaNotify = region.ariaNotify;
801
+ if (typeof ariaNotify === "function") {
802
+ ariaNotify.call(region, intent.text, {
803
+ priority: intent.channel === "assertive" ? "high" : "normal"
804
+ });
805
+ notified = true;
806
+ }
807
+ } catch (cause) {
808
+ notifierEnabled = false;
809
+ error = serializeError2(cause);
810
+ }
811
+ if (notified) {
812
+ return report({
813
+ status: "notified",
814
+ method: "aria-notify",
815
+ channel: intent.channel
816
+ });
817
+ }
818
+ }
819
+ region.textContent = intent.text;
820
+ return report({
821
+ status: "mutated",
822
+ method: "live-region",
823
+ channel: intent.channel,
824
+ ...error === void 0 ? {} : { error }
825
+ });
826
+ }
827
+ return report({
828
+ status: "unavailable",
829
+ method: "none",
830
+ channel: intent.channel
831
+ });
832
+ },
833
+ getRegions() {
834
+ return regions;
835
+ },
836
+ dispose() {
837
+ if (disposed) return;
838
+ disposed = true;
839
+ if (ownsRegions) {
840
+ regions?.polite.remove();
841
+ regions?.assertive.remove();
842
+ }
843
+ }
844
+ };
845
+ }
846
+ function validateSuppliedRegions(options) {
847
+ if (options.regions === void 0) return;
848
+ const { polite, assertive } = options.regions;
849
+ if (polite === assertive) {
850
+ throw new TypeError(
851
+ "Polite and assertive regions must be distinct elements"
852
+ );
853
+ }
854
+ if (!polite.isConnected || !assertive.isConnected) {
855
+ throw new TypeError("Supplied regions must be connected");
856
+ }
857
+ if (polite.contains(assertive) || assertive.contains(polite)) {
858
+ throw new TypeError(
859
+ "Polite and assertive regions must not contain one another"
860
+ );
861
+ }
862
+ if (polite.ownerDocument !== assertive.ownerDocument) {
863
+ throw new TypeError(
864
+ "Polite and assertive regions must belong to the same document"
865
+ );
866
+ }
867
+ if (options.document !== void 0 && (polite.ownerDocument !== options.document || assertive.ownerDocument !== options.document)) {
868
+ throw new TypeError(
869
+ "Supplied regions must belong to the provided document"
870
+ );
871
+ }
872
+ }
873
+ function serializeError2(error) {
874
+ try {
875
+ if (error instanceof Error) {
876
+ return {
877
+ name: readErrorString(error, "name", "Error"),
878
+ message: readErrorString(error, "message", "Unknown error")
879
+ };
880
+ }
881
+ } catch {
882
+ }
883
+ return { name: "Error", message: safeString(error, "Unknown error") };
884
+ }
885
+ function readErrorString(error, property, fallback) {
886
+ try {
887
+ return safeString(error[property], fallback);
888
+ } catch {
889
+ return fallback;
890
+ }
891
+ }
892
+ function safeString(value, fallback) {
893
+ try {
894
+ return String(value);
895
+ } catch {
896
+ return fallback;
897
+ }
898
+ }
899
+ function createLiveRegions(selectedDocument) {
900
+ const parent = selectedDocument?.body ?? selectedDocument?.documentElement;
901
+ if (!selectedDocument || !parent) return void 0;
902
+ const regions = {
903
+ polite: selectedDocument.createElement("div"),
904
+ assertive: selectedDocument.createElement("div")
905
+ };
906
+ parent.append(regions.polite, regions.assertive);
907
+ return regions;
908
+ }
909
+ function configureRegion(region, channel) {
910
+ region.removeAttribute("role");
911
+ region.removeAttribute("aria-busy");
912
+ region.removeAttribute("hidden");
913
+ region.removeAttribute("aria-hidden");
914
+ region.inert = false;
915
+ region.removeAttribute("inert");
916
+ region.style.removeProperty("display");
917
+ region.style.removeProperty("visibility");
918
+ region.style.removeProperty("content-visibility");
919
+ region.setAttribute("aria-live", channel);
920
+ region.setAttribute("aria-atomic", "true");
921
+ region.setAttribute("aria-relevant", "additions text");
922
+ Object.assign(region.style, {
923
+ position: "absolute",
924
+ width: "1px",
925
+ height: "1px",
926
+ padding: "0",
927
+ margin: "-1px",
928
+ overflow: "hidden",
929
+ clip: "rect(0, 0, 0, 0)",
930
+ whiteSpace: "nowrap",
931
+ border: "0"
932
+ });
933
+ }
934
+ function applyLocale(region, locale) {
935
+ if (locale === void 0) region.removeAttribute("lang");
936
+ else region.setAttribute("lang", locale);
937
+ }
938
+ function connectRuntimeToDOM(runtime, options = {}) {
939
+ const announcer = createDOMAnnouncer(options);
940
+ let disposed = false;
941
+ let unsubscribe;
942
+ try {
943
+ unsubscribe = runtime.subscribeAnnouncements((intent) => {
944
+ if (!disposed) announcer.announce(intent);
945
+ });
946
+ } catch (cause) {
947
+ announcer.dispose();
948
+ throw cause;
949
+ }
950
+ return {
951
+ announcer,
952
+ dispose() {
953
+ if (disposed) return;
954
+ disposed = true;
955
+ try {
956
+ unsubscribe();
957
+ } finally {
958
+ announcer.dispose();
959
+ }
960
+ }
961
+ };
962
+ }
963
+ export {
964
+ captureFocus,
965
+ connectRuntimeToDOM,
966
+ createAttentionStore,
967
+ createDOMAnnouncer,
968
+ createPreferenceStore,
969
+ defaultPreferences,
970
+ focusElement,
971
+ normalizePreferences,
972
+ preferencesToCoreConfiguration,
973
+ restoreFocus,
974
+ samePreferences
975
+ };
976
+ //# sourceMappingURL=index.js.map