abledom 0.0.0 → 0.0.1

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,1184 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
+
22
+ // src/index.ts
23
+ var index_exports = {};
24
+ __export(index_exports, {
25
+ AbleDOM: () => AbleDOM,
26
+ AtomicRule: () => AtomicRule,
27
+ BadFocusRule: () => BadFocusRule,
28
+ ExistingIdRule: () => ExistingIdRule,
29
+ FocusLostRule: () => FocusLostRule,
30
+ FocusableElementLabelRule: () => FocusableElementLabelRule,
31
+ ValidationRule: () => ValidationRule
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // inline-file:/Users/marata/Documents/Work/abledom/src/ui/ui.css
36
+ var ui_default = "#abledom-report {\n bottom: 20px;\n display: flex;\n flex-direction: column;\n left: 10px;\n max-height: 80%;\n max-width: 60%;\n padding: 4px 8px;\n position: absolute;\n z-index: 100500;\n}\n\n.abledom-notification-container {\n backdrop-filter: blur(3px);\n border-radius: 8px;\n box-shadow: 0px 0px 4px rgba(127, 127, 127, 0.5);\n display: inline-flex;\n margin: 2px 0;\n}\n\n.abledom-notification {\n background-color: rgba(164, 2, 2, 0.7);\n border-radius: 8px;\n color: white;\n display: inline flex;\n font-family: Arial, Helvetica, sans-serif;\n font-size: 16px;\n line-height: 26px;\n padding: 4px;\n}\n\n.abledom-notification .button {\n all: unset;\n color: #000;\n cursor: pointer;\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, 1) 0%,\n rgba(200, 200, 200, 1) 100%\n );\n border-radius: 6px;\n border: 1px solid rgba(255, 255, 255, 0.4);\n box-sizing: border-box;\n line-height: 0px;\n margin-right: 4px;\n max-height: 26px;\n padding: 2px;\n text-decoration: none;\n}\n\n.abledom-notification .button:hover {\n opacity: 0.7;\n}\n\n.abledom-notification .button.close {\n background: none;\n border-color: transparent;\n color: #fff;\n margin: 0;\n}\n\n.abledom-highlight {\n background-color: yellow;\n border: 1px solid red;\n box-sizing: border-box;\n display: none;\n opacity: 0.6;\n position: absolute;\n z-index: 100499;\n}\n";
37
+
38
+ // inline-file:/Users/marata/Documents/Work/abledom/src/ui/close.svg
39
+ var close_default = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"\n stroke-width="2" stroke-linecap="round" stroke-linejoin="round"\n xmlns="http://www.w3.org/2000/svg">\n <circle cx="12" cy="12" r="10"/>\n <line x1="15" y1="9" x2="9" y2="15"/>\n <line x1="9" y1="9" x2="15" y2="15"/>\n</svg>\n';
40
+
41
+ // inline-file:/Users/marata/Documents/Work/abledom/src/ui/help.svg
42
+ var help_default = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none"\n stroke="currentColor" stroke-width="2" stroke-linecap="round"\n stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">\n <circle cx="12" cy="12" r="10"/>\n <path d="M9.09 9a3 3 0 1 1 5.83 1c-.28 1.02-1.22 1.5-2.01 2.1-.76.58-1 1.1-1 2"/>\n <circle cx="12" cy="17" r="0.5"/>\n</svg>';
43
+
44
+ // inline-file:/Users/marata/Documents/Work/abledom/src/ui/log.svg
45
+ var log_default = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"\n stroke-width="2" stroke-linecap="round" stroke-linejoin="round"\n xmlns="http://www.w3.org/2000/svg">\n <polyline points="4 7 9 12 4 17"/>\n <line x1="11" y1="19" x2="20" y2="19"/>\n</svg>';
46
+
47
+ // inline-file:/Users/marata/Documents/Work/abledom/src/ui/reveal.svg
48
+ var reveal_default = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"\n stroke-width="2" stroke-linecap="round" stroke-linejoin="round"\n xmlns="http://www.w3.org/2000/svg">\n <circle cx="12" cy="12" r="10"/>\n <line x1="12" y1="2" x2="12" y2="6"/>\n <line x1="12" y1="18" x2="12" y2="22"/>\n <line x1="2" y1="12" x2="6" y2="12"/>\n <line x1="18" y1="12" x2="22" y2="12"/>\n</svg>';
49
+
50
+ // src/ui/ui.ts
51
+ var _NotificationUI = class _NotificationUI {
52
+ constructor(element, error) {
53
+ __publicField(this, "_wrapper");
54
+ var _a;
55
+ const wrapper = this._wrapper = document.createElement(
56
+ "div"
57
+ );
58
+ const win = element.ownerDocument.defaultView;
59
+ if (!win) {
60
+ return;
61
+ }
62
+ if (!_NotificationUI._highlight) {
63
+ _NotificationUI._highlight = new ElementHighlighter(win);
64
+ }
65
+ wrapper.__abledomui = true;
66
+ wrapper.innerHTML = `
67
+ <div class="abledom-notification-container"><div class="abledom-notification">
68
+ <button class="button" title="Log to Console">${log_default}</button>
69
+ <button class="button" title="Reveal in Elements panel">${reveal_default}</button>
70
+ ${error.message}
71
+ <a href class="button close" href="/" title="Open help" target="_blank">${help_default}</a>
72
+ <button class="button close" class="close" title="Hide">${close_default}</button>
73
+ </div></div>`;
74
+ const container = wrapper.firstElementChild;
75
+ const buttons = wrapper.querySelectorAll("button");
76
+ const logButton = buttons[0];
77
+ const revealButton = buttons[1];
78
+ const closeButton = buttons[2];
79
+ logButton.onclick = () => {
80
+ console.error(
81
+ "AbleDOM violation: ",
82
+ "\nerror:",
83
+ error.message,
84
+ "\nelement:",
85
+ element,
86
+ ...error.rel ? ["\nrelative:", error.rel] : []
87
+ );
88
+ };
89
+ const hasDevTools = !!((_a = win.__ableDOMDevtools) == null ? void 0 : _a.revealElement);
90
+ if (hasDevTools && win.document.body.contains(element)) {
91
+ revealButton.onclick = () => {
92
+ var _a2;
93
+ const revealElement = (_a2 = win.__ableDOMDevtools) == null ? void 0 : _a2.revealElement;
94
+ if (revealElement && win.document.body.contains(element)) {
95
+ revealElement(element).then((revealed) => {
96
+ if (!revealed) {
97
+ }
98
+ });
99
+ }
100
+ };
101
+ } else {
102
+ revealButton.style.display = "none";
103
+ }
104
+ closeButton.onclick = () => {
105
+ var _a2;
106
+ wrapper.style.display = "none";
107
+ (_a2 = _NotificationUI._highlight) == null ? void 0 : _a2.hide();
108
+ };
109
+ container.onmouseover = () => {
110
+ var _a2;
111
+ (_a2 = _NotificationUI._highlight) == null ? void 0 : _a2.highlight(element);
112
+ };
113
+ container.onmouseout = () => {
114
+ var _a2;
115
+ (_a2 = _NotificationUI._highlight) == null ? void 0 : _a2.hide();
116
+ };
117
+ }
118
+ static getElement(instance) {
119
+ return instance._wrapper;
120
+ }
121
+ dispose() {
122
+ this._wrapper.remove();
123
+ }
124
+ };
125
+ __publicField(_NotificationUI, "_highlight");
126
+ var NotificationUI = _NotificationUI;
127
+ var NotificationsUI = class {
128
+ constructor(win) {
129
+ // private _window: Window;
130
+ __publicField(this, "_container");
131
+ __publicField(this, "_notifications", /* @__PURE__ */ new Set());
132
+ const container = this._container = document.createElement("div");
133
+ container.__abledomui = true;
134
+ container.id = "abledom-report";
135
+ container.innerHTML = `<style>${ui_default}</style>`;
136
+ win.document.body.appendChild(container);
137
+ }
138
+ addNotification(notification) {
139
+ if (this._notifications.has(notification)) {
140
+ return;
141
+ }
142
+ this._notifications.add(notification);
143
+ this._container.appendChild(NotificationUI.getElement(notification));
144
+ }
145
+ removeNotification(notification) {
146
+ if (!this._notifications.has(notification)) {
147
+ return;
148
+ }
149
+ this._notifications.delete(notification);
150
+ this._container.removeChild(NotificationUI.getElement(notification));
151
+ }
152
+ };
153
+ var ElementHighlighter = class {
154
+ constructor(win) {
155
+ __publicField(this, "_window");
156
+ __publicField(this, "_container");
157
+ this._window = win;
158
+ const container = this._container = win.document.createElement("div");
159
+ container.__abledomui = true;
160
+ container.className = "abledom-highlight";
161
+ }
162
+ highlight(element) {
163
+ const rect = element.getBoundingClientRect();
164
+ if (rect.width === 0 || rect.height === 0) {
165
+ return;
166
+ }
167
+ const win = this._window;
168
+ const container = this._container;
169
+ const style = container.style;
170
+ if (container.parentElement !== win.document.body) {
171
+ win.document.body.appendChild(container);
172
+ }
173
+ style.width = `${rect.width}px`;
174
+ style.height = `${rect.height}px`;
175
+ style.top = `${rect.top}px`;
176
+ style.left = `${rect.left}px`;
177
+ container.style.display = "block";
178
+ }
179
+ hide() {
180
+ this._container.style.display = "none";
181
+ }
182
+ };
183
+ function isAbleDOMUIElement(element) {
184
+ for (let el = element; el; el = el.parentElement) {
185
+ if (el.__abledomui) {
186
+ return true;
187
+ }
188
+ }
189
+ return false;
190
+ }
191
+
192
+ // src/utils.ts
193
+ var focusableElementSelector = [
194
+ "a[href]",
195
+ "button:not([disabled])",
196
+ "input:not([disabled])",
197
+ "select:not([disabled])",
198
+ "textarea:not([disabled])",
199
+ "*[tabindex]",
200
+ "*[contenteditable]",
201
+ "details > summary",
202
+ "audio[controls]",
203
+ "video[controls]"
204
+ ].join(", ");
205
+ var AccessibilityAffectingElements = {
206
+ a: true,
207
+ area: true,
208
+ article: true,
209
+ aside: true,
210
+ body: true,
211
+ button: true,
212
+ datalist: true,
213
+ details: true,
214
+ dialog: true,
215
+ dl: true,
216
+ form: true,
217
+ h1: true,
218
+ h2: true,
219
+ h3: true,
220
+ h4: true,
221
+ h5: true,
222
+ h6: true,
223
+ hr: true,
224
+ iframe: true,
225
+ img: true,
226
+ input: true,
227
+ li: true,
228
+ link: true,
229
+ main: true,
230
+ menu: true,
231
+ menuitem: true,
232
+ meter: true,
233
+ nav: true,
234
+ object: true,
235
+ ol: true,
236
+ option: true,
237
+ progress: true,
238
+ section: true,
239
+ select: true,
240
+ tbody: true,
241
+ textarea: true,
242
+ tfoot: true,
243
+ th: true,
244
+ thead: true,
245
+ ul: true
246
+ };
247
+ var AccessibilityAttributes = {
248
+ role: true,
249
+ tabindex: true,
250
+ disabled: true,
251
+ required: true,
252
+ readonly: true,
253
+ hidden: true,
254
+ "aria-activedescendant": true,
255
+ "aria-atomic": true,
256
+ "aria-autocomplete": true,
257
+ "aria-busy": true,
258
+ "aria-checked": true,
259
+ "aria-colcount": true,
260
+ "aria-colindex": true,
261
+ "aria-colspan": true,
262
+ "aria-controls": true,
263
+ "aria-current": true,
264
+ "aria-describedby": true,
265
+ "aria-details": true,
266
+ "aria-disabled": true,
267
+ "aria-dropeffect": true,
268
+ "aria-errormessage": true,
269
+ "aria-expanded": true,
270
+ "aria-flowto": true,
271
+ "aria-grabbed": true,
272
+ "aria-haspopup": true,
273
+ "aria-hidden": true,
274
+ "aria-invalid": true,
275
+ "aria-keyshortcuts": true,
276
+ "aria-label": true,
277
+ "aria-labelledby": true,
278
+ "aria-level": true,
279
+ "aria-live": true,
280
+ "aria-modal": true,
281
+ "aria-multiline": true,
282
+ "aria-multiselectable": true,
283
+ "aria-orientation": true,
284
+ "aria-owns": true,
285
+ "aria-placeholder": true,
286
+ "aria-posinset": true,
287
+ "aria-pressed": true,
288
+ "aria-readonly": true,
289
+ "aria-relevant": true,
290
+ "aria-required": true,
291
+ "aria-roledescription": true,
292
+ "aria-rowcount": true,
293
+ "aria-rowindex": true,
294
+ "aria-rowspan": true,
295
+ "aria-selected": true,
296
+ "aria-setsize": true,
297
+ "aria-sort": true,
298
+ "aria-valuemax": true,
299
+ "aria-valuemin": true,
300
+ "aria-valuenow": true,
301
+ "aria-valuetext": true
302
+ };
303
+ function isAccessibilityAffectingElement(element) {
304
+ const tagName = element.tagName.toLowerCase();
305
+ const attributes = getAttributes(element);
306
+ return tagName in AccessibilityAffectingElements || hasAccessibilityAttribute(attributes);
307
+ }
308
+ function hasAccessibilityAttribute(attributes) {
309
+ for (let attrName of Object.keys(attributes)) {
310
+ if (attrName in AccessibilityAttributes && !(attrName === "role" && (attributes[attrName] === "none" || attributes[attrName] === "presentation"))) {
311
+ return true;
312
+ }
313
+ }
314
+ return false;
315
+ }
316
+ function getAttributes(element) {
317
+ const names = element.getAttributeNames();
318
+ const attributes = {};
319
+ for (let name of names) {
320
+ attributes[name] = element.getAttribute(name) || "";
321
+ }
322
+ return attributes;
323
+ }
324
+ function matchesSelector(element, selector) {
325
+ const matches = element.matches || element.matchesSelector || element.msMatchesSelector || element.webkitMatchesSelector;
326
+ return matches && matches.call(element, selector);
327
+ }
328
+ function isDisplayNone(element) {
329
+ var _a, _b;
330
+ const elementDocument = element.ownerDocument;
331
+ const computedStyle = (_a = elementDocument.defaultView) == null ? void 0 : _a.getComputedStyle(element);
332
+ if (element.offsetParent === null && elementDocument.body !== element && (computedStyle == null ? void 0 : computedStyle.position) !== "fixed") {
333
+ return true;
334
+ }
335
+ if ((computedStyle == null ? void 0 : computedStyle.visibility) === "hidden") {
336
+ return true;
337
+ }
338
+ if ((computedStyle == null ? void 0 : computedStyle.position) === "fixed") {
339
+ if (computedStyle.display === "none") {
340
+ return true;
341
+ }
342
+ if (((_b = element.parentElement) == null ? void 0 : _b.offsetParent) === null && elementDocument.body !== element.parentElement) {
343
+ return true;
344
+ }
345
+ }
346
+ return false;
347
+ }
348
+ function isElementVisible(element) {
349
+ if (!element.ownerDocument || element.nodeType !== Node.ELEMENT_NODE) {
350
+ return false;
351
+ }
352
+ if (isDisplayNone(element)) {
353
+ return false;
354
+ }
355
+ const rect = element.ownerDocument.body.getBoundingClientRect();
356
+ if (rect.width === 0 && rect.height === 0) {
357
+ return false;
358
+ }
359
+ return true;
360
+ }
361
+ function getStackTrace() {
362
+ var _a;
363
+ const oldStackTraceLimit = Error.stackTraceLimit;
364
+ try {
365
+ Error.stackTraceLimit = 1e3;
366
+ throw new Error();
367
+ } catch (e) {
368
+ Error.stackTraceLimit = oldStackTraceLimit;
369
+ return ((_a = e.stack) == null ? void 0 : _a.split("\n").slice(1).map((line) => line.trim()).filter((line) => line.startsWith("at "))) || [];
370
+ }
371
+ }
372
+
373
+ // src/rules/base.ts
374
+ var ValidationRule = class {
375
+ constructor() {
376
+ __publicField(this, "_window");
377
+ __publicField(this, "_exceptions", []);
378
+ }
379
+ static setWindow(instance, window2) {
380
+ instance._window = window2;
381
+ }
382
+ static checkExceptions(instance, element) {
383
+ for (const exception of instance._exceptions) {
384
+ if (exception(element)) {
385
+ return true;
386
+ }
387
+ }
388
+ return false;
389
+ }
390
+ addException(checkException) {
391
+ var _a;
392
+ (_a = this._exceptions) == null ? void 0 : _a.push(checkException);
393
+ }
394
+ removeException(checkException) {
395
+ const index = this._exceptions.indexOf(checkException);
396
+ if (index >= 0) {
397
+ this._exceptions.splice(index, 1);
398
+ }
399
+ }
400
+ /**
401
+ * Window is set when the rule is added to the AbleDOM instance.
402
+ */
403
+ get window() {
404
+ return this._window;
405
+ }
406
+ };
407
+
408
+ // src/core.ts
409
+ var _ValidationErrorReport = class _ValidationErrorReport {
410
+ constructor(element, error, onHide) {
411
+ this.element = element;
412
+ this.error = error;
413
+ __publicField(this, "_notification");
414
+ __publicField(this, "_onHide");
415
+ this._onHide = onHide;
416
+ this.report();
417
+ }
418
+ update(error) {
419
+ this.error = error;
420
+ this.report();
421
+ }
422
+ report() {
423
+ if (!_ValidationErrorReport._notificationsUI) {
424
+ _ValidationErrorReport._notificationsUI = new NotificationsUI(window);
425
+ }
426
+ let notification = this._notification;
427
+ if (notification) {
428
+ } else {
429
+ notification = this._notification = new NotificationUI(
430
+ this.element,
431
+ this.error
432
+ );
433
+ }
434
+ _ValidationErrorReport._notificationsUI.addNotification(notification);
435
+ }
436
+ hide() {
437
+ var _a;
438
+ (_a = this._onHide) == null ? void 0 : _a.call(this);
439
+ }
440
+ remove() {
441
+ var _a;
442
+ if (this._notification) {
443
+ (_a = _ValidationErrorReport._notificationsUI) == null ? void 0 : _a.removeNotification(
444
+ this._notification
445
+ );
446
+ delete this._notification;
447
+ }
448
+ }
449
+ };
450
+ __publicField(_ValidationErrorReport, "_notificationsUI");
451
+ var ValidationErrorReport = _ValidationErrorReport;
452
+ var AbleDOM = class {
453
+ constructor(win) {
454
+ __publicField(this, "_window");
455
+ __publicField(this, "_observer");
456
+ __publicField(this, "_clearValidationTimeout");
457
+ __publicField(this, "_elementsWithErrors", /* @__PURE__ */ new Set());
458
+ __publicField(this, "_changedElementIds", /* @__PURE__ */ new Set());
459
+ __publicField(this, "_elementsDependingOnId", /* @__PURE__ */ new Map());
460
+ __publicField(this, "_dependantIdsByElement", /* @__PURE__ */ new Map());
461
+ __publicField(this, "_idByElement", /* @__PURE__ */ new Map());
462
+ __publicField(this, "_rules", []);
463
+ __publicField(this, "_startFunc");
464
+ __publicField(this, "_isStarted", false);
465
+ __publicField(this, "_onFocusIn", (event) => {
466
+ var _a;
467
+ const target = event.target;
468
+ if (target && isAbleDOMUIElement(target)) {
469
+ return;
470
+ }
471
+ for (const rule of this._rules) {
472
+ (_a = rule.focused) == null ? void 0 : _a.call(rule, event).then(
473
+ (focusError) => {
474
+ if (focusError) {
475
+ this._addValidationError(
476
+ focusError.element,
477
+ rule,
478
+ focusError.error
479
+ );
480
+ }
481
+ },
482
+ () => {
483
+ }
484
+ );
485
+ }
486
+ });
487
+ __publicField(this, "_onFocusOut", (event) => {
488
+ var _a;
489
+ const target = event.target;
490
+ if (target && isAbleDOMUIElement(target)) {
491
+ return;
492
+ }
493
+ for (const rule of this._rules) {
494
+ (_a = rule.blurred) == null ? void 0 : _a.call(rule, event).then(
495
+ (focusError) => {
496
+ if (focusError) {
497
+ this._addValidationError(
498
+ focusError.element,
499
+ rule,
500
+ focusError.error
501
+ );
502
+ }
503
+ },
504
+ () => {
505
+ }
506
+ );
507
+ }
508
+ });
509
+ this._window = win;
510
+ const _elementsToValidate = /* @__PURE__ */ new Set();
511
+ const _elementsToRemove = /* @__PURE__ */ new Set();
512
+ win.document.addEventListener("focusin", this._onFocusIn, true);
513
+ win.document.addEventListener("focusout", this._onFocusOut, true);
514
+ this._observer = new MutationObserver((mutations) => {
515
+ var _a;
516
+ for (let mutation of mutations) {
517
+ if (mutation.target.__abledomui) {
518
+ continue;
519
+ }
520
+ const added = mutation.addedNodes;
521
+ const removed = mutation.removedNodes;
522
+ const attributeName = mutation.attributeName;
523
+ if (attributeName === "id") {
524
+ this._onElementId(mutation.target, false);
525
+ continue;
526
+ }
527
+ lookUp(mutation.target);
528
+ for (let i = 0; i < added.length; i++) {
529
+ findTargets(added[i], false);
530
+ }
531
+ for (let i = 0; i < removed.length; i++) {
532
+ findTargets(removed[i], true);
533
+ }
534
+ }
535
+ (_a = this._clearValidationTimeout) == null ? void 0 : _a.call(this);
536
+ const _validationTimeout = win.setTimeout(() => {
537
+ delete this._clearValidationTimeout;
538
+ this._remove(_elementsToRemove);
539
+ this._validate(_elementsToValidate);
540
+ _elementsToRemove.clear();
541
+ _elementsToValidate.clear();
542
+ this._changedElementIds.clear();
543
+ }, 200);
544
+ this._clearValidationTimeout = () => {
545
+ win.clearTimeout(_validationTimeout);
546
+ delete this._clearValidationTimeout;
547
+ };
548
+ });
549
+ this._startFunc = () => {
550
+ delete this._startFunc;
551
+ this._observer.observe(win.document, {
552
+ childList: true,
553
+ subtree: true,
554
+ attributes: true
555
+ });
556
+ findTargets(win.document.body, false);
557
+ this._validate(_elementsToValidate);
558
+ _elementsToValidate.clear();
559
+ };
560
+ const addTarget = (node, removed) => {
561
+ if (node.nodeType !== Node.ELEMENT_NODE) {
562
+ return;
563
+ }
564
+ const id = node.id;
565
+ if (id) {
566
+ this._onElementId(node, removed);
567
+ }
568
+ if (removed) {
569
+ _elementsToRemove.add(node);
570
+ _elementsToValidate.delete(node);
571
+ } else {
572
+ _elementsToValidate.add(node);
573
+ _elementsToRemove.delete(node);
574
+ }
575
+ };
576
+ function lookUp(node) {
577
+ for (let n = node; n; n = n.parentNode) {
578
+ addTarget(n, false);
579
+ }
580
+ }
581
+ function findTargets(node, removed) {
582
+ if (node.nodeType !== Node.ELEMENT_NODE) {
583
+ return;
584
+ }
585
+ addTarget(node, removed);
586
+ const walker = win.document.createTreeWalker(
587
+ node,
588
+ NodeFilter.SHOW_ELEMENT,
589
+ (node2) => {
590
+ addTarget(node2, removed);
591
+ return NodeFilter.FILTER_SKIP;
592
+ }
593
+ );
594
+ if (walker) {
595
+ while (walker.nextNode()) {
596
+ }
597
+ }
598
+ }
599
+ }
600
+ _onElementId(element, removed) {
601
+ const elementId = element.getAttribute("id");
602
+ const oldElementId = this._idByElement.get(element);
603
+ if (oldElementId) {
604
+ this._changedElementIds.add(oldElementId);
605
+ const elements = this._elementsDependingOnId.get(oldElementId);
606
+ if (elements) {
607
+ elements.delete(element);
608
+ if (elements.size === 0) {
609
+ this._elementsDependingOnId.delete(oldElementId);
610
+ }
611
+ }
612
+ }
613
+ if (elementId) {
614
+ this._changedElementIds.add(elementId);
615
+ let elements = this._elementsDependingOnId.get(elementId);
616
+ if (removed) {
617
+ this._idByElement.delete(element);
618
+ if (elements) {
619
+ elements.delete(element);
620
+ if (elements.size === 0) {
621
+ this._elementsDependingOnId.delete(elementId);
622
+ }
623
+ }
624
+ } else {
625
+ this._idByElement.set(element, elementId);
626
+ if (!elements) {
627
+ elements = /* @__PURE__ */ new Set();
628
+ this._elementsDependingOnId.set(elementId, elements);
629
+ }
630
+ elements.add(element);
631
+ }
632
+ } else {
633
+ this._idByElement.delete(element);
634
+ }
635
+ }
636
+ _addValidationError(element, rule, error) {
637
+ if (!error) {
638
+ this._removeElementError(element, rule);
639
+ return;
640
+ }
641
+ if (rule.anchored) {
642
+ let abledomOnElement = element.__abledom;
643
+ if (!abledomOnElement) {
644
+ abledomOnElement = element.__abledom = {};
645
+ }
646
+ let errors = abledomOnElement.errors;
647
+ if (!errors) {
648
+ errors = abledomOnElement.errors = /* @__PURE__ */ new Map();
649
+ }
650
+ const report = errors.get(rule);
651
+ if (report) {
652
+ report.update(error);
653
+ } else {
654
+ errors.set(rule, new ValidationErrorReport(element, error));
655
+ }
656
+ this._elementsWithErrors.add(element);
657
+ } else {
658
+ const report = new ValidationErrorReport(element, error, () => {
659
+ report.remove();
660
+ });
661
+ }
662
+ }
663
+ _removeElementError(element, rule) {
664
+ var _a;
665
+ if (!rule.anchored) {
666
+ return;
667
+ }
668
+ const errors = (_a = element.__abledom) == null ? void 0 : _a.errors;
669
+ if (!errors) {
670
+ return;
671
+ }
672
+ const report = errors.get(rule);
673
+ if (report) {
674
+ report.remove();
675
+ errors.delete(rule);
676
+ }
677
+ if (errors.size === 0) {
678
+ this._elementsWithErrors.delete(element);
679
+ delete element.__abledom;
680
+ }
681
+ }
682
+ _validate(elements) {
683
+ for (const id of this._changedElementIds) {
684
+ const dependingOnId = this._elementsDependingOnId.get(id);
685
+ if (dependingOnId) {
686
+ for (const element of dependingOnId) {
687
+ elements.add(element);
688
+ }
689
+ }
690
+ }
691
+ elements.forEach((element) => {
692
+ var _a, _b, _c, _d, _e;
693
+ if (isAccessibilityAffectingElement(element) || ((_a = element.__abledom) == null ? void 0 : _a.errors)) {
694
+ const dependsOnIds = /* @__PURE__ */ new Set();
695
+ for (const rule of this._rules) {
696
+ if (((_b = rule.accept) == null ? void 0 : _b.call(rule, element)) === false) {
697
+ continue;
698
+ }
699
+ if (ValidationRule.checkExceptions(rule, element)) {
700
+ continue;
701
+ }
702
+ const validationResult = (_c = rule.validate) == null ? void 0 : _c.call(rule, element);
703
+ if (validationResult) {
704
+ this._addValidationError(element, rule, validationResult.error);
705
+ const ids = validationResult.dependsOnIds;
706
+ if (ids) {
707
+ for (const id of ids) {
708
+ dependsOnIds.add(id);
709
+ }
710
+ }
711
+ } else if ((_e = (_d = element.__abledom) == null ? void 0 : _d.errors) == null ? void 0 : _e.has(rule)) {
712
+ this._removeElementError(element, rule);
713
+ }
714
+ }
715
+ this._processElementDependingOnIds(
716
+ element,
717
+ dependsOnIds.size === 0 ? null : dependsOnIds
718
+ );
719
+ }
720
+ });
721
+ }
722
+ _processElementDependingOnIds(element, ids) {
723
+ let dependsOnIds = this._dependantIdsByElement.get(element);
724
+ if (!ids && !dependsOnIds) {
725
+ return;
726
+ }
727
+ if (!dependsOnIds) {
728
+ dependsOnIds = /* @__PURE__ */ new Set();
729
+ }
730
+ if (!ids) {
731
+ ids = /* @__PURE__ */ new Set();
732
+ }
733
+ for (const id of dependsOnIds) {
734
+ if (!ids.has(id)) {
735
+ const elements = this._elementsDependingOnId.get(id);
736
+ if (elements) {
737
+ elements.delete(element);
738
+ if (elements.size === 0) {
739
+ this._elementsDependingOnId.delete(id);
740
+ }
741
+ }
742
+ }
743
+ }
744
+ for (const id of ids) {
745
+ if (!dependsOnIds.has(id)) {
746
+ dependsOnIds.add(id);
747
+ let elements = this._elementsDependingOnId.get(id);
748
+ if (!elements) {
749
+ elements = /* @__PURE__ */ new Set();
750
+ this._elementsDependingOnId.set(id, elements);
751
+ }
752
+ elements.add(element);
753
+ }
754
+ }
755
+ if (dependsOnIds.size === 0) {
756
+ this._dependantIdsByElement.delete(element);
757
+ } else {
758
+ this._dependantIdsByElement.set(element, dependsOnIds);
759
+ }
760
+ }
761
+ _remove(elements) {
762
+ elements.forEach((element) => {
763
+ var _a, _b;
764
+ const rules = [...((_b = (_a = element.__abledom) == null ? void 0 : _a.errors) == null ? void 0 : _b.keys()) || []];
765
+ rules.forEach((rule) => this._removeElementError(element, rule));
766
+ });
767
+ }
768
+ addRule(rule) {
769
+ this._rules.push(rule);
770
+ }
771
+ removeRule(rule) {
772
+ var _a;
773
+ const index = this._rules.indexOf(rule);
774
+ if (index >= 0) {
775
+ const rule2 = this._rules[index];
776
+ this._rules.splice(index, 1);
777
+ (_a = rule2.stop) == null ? void 0 : _a.call(rule2);
778
+ }
779
+ }
780
+ start() {
781
+ var _a, _b;
782
+ if (this._isStarted) {
783
+ return;
784
+ }
785
+ this._isStarted = true;
786
+ for (const rule of this._rules) {
787
+ ValidationRule.setWindow(rule, this._window);
788
+ (_a = rule.start) == null ? void 0 : _a.call(rule);
789
+ }
790
+ (_b = this._startFunc) == null ? void 0 : _b.call(this);
791
+ }
792
+ dispose() {
793
+ var _a, _b;
794
+ this._window.document.addEventListener("focusin", this._onFocusIn, true);
795
+ this._window.document.addEventListener("focusout", this._onFocusOut, true);
796
+ this._remove(this._elementsWithErrors);
797
+ this._elementsWithErrors.clear();
798
+ this._dependantIdsByElement.clear();
799
+ this._elementsDependingOnId.clear();
800
+ this._idByElement.clear();
801
+ (_a = this._clearValidationTimeout) == null ? void 0 : _a.call(this);
802
+ for (const rule of this._rules) {
803
+ (_b = rule.stop) == null ? void 0 : _b.call(rule);
804
+ }
805
+ this._rules = [];
806
+ if (this._startFunc) {
807
+ delete this._startFunc;
808
+ } else {
809
+ this._observer.disconnect();
810
+ }
811
+ }
812
+ };
813
+
814
+ // src/rules/atomic.ts
815
+ var AtomicRule = class extends ValidationRule {
816
+ constructor() {
817
+ super(...arguments);
818
+ __publicField(this, "name", "atomic");
819
+ __publicField(this, "anchored", true);
820
+ }
821
+ accept(element) {
822
+ return matchesSelector(element, focusableElementSelector);
823
+ }
824
+ validate(element) {
825
+ const parentAtomic = document.evaluate(
826
+ `ancestor::*[
827
+ @role = 'button' or
828
+ @role = 'checkbox' or
829
+ @role = 'link' or
830
+ @role = 'menuitem' or
831
+ @role = 'menuitemcheckbox' or
832
+ @role = 'menuitemradio' or
833
+ @role = 'option' or
834
+ @role = 'radio' or
835
+ @role = 'switch' or
836
+ @role = 'tab' or
837
+ @role = 'treeitem' or
838
+ self::a or
839
+ self::button or
840
+ self::input or
841
+ self::option or
842
+ self::textarea
843
+ ][1]`,
844
+ element,
845
+ null,
846
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
847
+ null
848
+ ).snapshotItem(0);
849
+ if (parentAtomic) {
850
+ return {
851
+ error: {
852
+ id: "focusable-in-atomic",
853
+ message: "Focusable element inside atomic focusable.",
854
+ rel: parentAtomic
855
+ }
856
+ };
857
+ }
858
+ return null;
859
+ }
860
+ };
861
+
862
+ // src/rules/label.ts
863
+ var _keyboardEditableInputTypes = /* @__PURE__ */ new Set([
864
+ "text",
865
+ "password",
866
+ "email",
867
+ "search",
868
+ "tel",
869
+ "url",
870
+ "number",
871
+ "date",
872
+ "month",
873
+ "week",
874
+ "time",
875
+ "datetime-local"
876
+ ]);
877
+ var FocusableElementLabelRule = class extends ValidationRule {
878
+ constructor() {
879
+ super(...arguments);
880
+ __publicField(this, "name", "FocusableElementLabelRule");
881
+ __publicField(this, "anchored", true);
882
+ }
883
+ _isAriaHidden(element) {
884
+ return document.evaluate(
885
+ `ancestor-or-self::*[@aria-hidden = 'true' or @hidden]`,
886
+ element,
887
+ null,
888
+ XPathResult.BOOLEAN_TYPE,
889
+ null
890
+ ).booleanValue;
891
+ }
892
+ _hasLabel(element) {
893
+ var _a, _b;
894
+ const labels = element.labels;
895
+ if (labels && labels.length > 0) {
896
+ for (let i = 0; i < labels.length; i++) {
897
+ const label = labels[i];
898
+ if (label.innerText.trim()) {
899
+ return true;
900
+ }
901
+ }
902
+ }
903
+ if (element.tagName === "IMG") {
904
+ if (element.alt.trim()) {
905
+ return true;
906
+ }
907
+ }
908
+ const labelNodes = document.evaluate(
909
+ `(
910
+ .//@aria-label |
911
+ .//text() |
912
+ .//@title |
913
+ .//img/@alt |
914
+ .//input[@type = 'image']/@alt |
915
+ .//input[@type != 'hidden'][@type = 'submit' or @type = 'reset' or @type = 'button']/@value
916
+ )[not(ancestor-or-self::*[@aria-hidden = 'true' or @hidden])]`,
917
+ element,
918
+ null,
919
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
920
+ null
921
+ );
922
+ for (let i = 0; i < labelNodes.snapshotLength; i++) {
923
+ const val = (_b = (_a = labelNodes.snapshotItem(i)) == null ? void 0 : _a.nodeValue) == null ? void 0 : _b.trim();
924
+ if (val) {
925
+ return true;
926
+ }
927
+ }
928
+ return false;
929
+ }
930
+ accept(element) {
931
+ return matchesSelector(element, focusableElementSelector);
932
+ }
933
+ validate(element) {
934
+ var _a, _b;
935
+ if (element.tagName === "INPUT") {
936
+ const type = element.type;
937
+ if (type === "hidden") {
938
+ return null;
939
+ }
940
+ if (_keyboardEditableInputTypes.has(type)) {
941
+ return null;
942
+ }
943
+ if (type === "image") {
944
+ if (element.alt.trim()) {
945
+ return null;
946
+ }
947
+ }
948
+ if (type === "submit" || type === "reset" || type === "button") {
949
+ if (element.value.trim()) {
950
+ return null;
951
+ }
952
+ }
953
+ }
954
+ if (this._isAriaHidden(element)) {
955
+ return null;
956
+ }
957
+ if (this._hasLabel(element)) {
958
+ return null;
959
+ }
960
+ const labelledByNodes = document.evaluate(
961
+ `.//@aria-labelledby[not(ancestor-or-self::*[@aria-hidden = 'true' or @hidden])]`,
962
+ element,
963
+ null,
964
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
965
+ null
966
+ );
967
+ const labelledByValues = [];
968
+ for (let i = 0; i < labelledByNodes.snapshotLength; i++) {
969
+ const val = (_b = (_a = labelledByNodes.snapshotItem(i)) == null ? void 0 : _a.value) == null ? void 0 : _b.trim().split(" ");
970
+ if (val == null ? void 0 : val.length) {
971
+ labelledByValues.push(...val);
972
+ }
973
+ }
974
+ for (const id of labelledByValues) {
975
+ const labelElement = document.getElementById(id);
976
+ if (labelElement && this._hasLabel(labelElement)) {
977
+ return {
978
+ dependsOnIds: new Set(labelledByValues)
979
+ };
980
+ }
981
+ }
982
+ return {
983
+ error: isElementVisible(element) ? {
984
+ id: "focusable-element-label",
985
+ message: "Focusable element must have a non-empty text label."
986
+ } : void 0,
987
+ dependsOnIds: new Set(labelledByValues)
988
+ };
989
+ }
990
+ };
991
+
992
+ // src/rules/existingid.ts
993
+ var ExistingIdRule = class extends ValidationRule {
994
+ constructor() {
995
+ super(...arguments);
996
+ __publicField(this, "name", "existing-id");
997
+ __publicField(this, "anchored", true);
998
+ }
999
+ accept(element) {
1000
+ return element.hasAttribute("aria-labelledby") || element.hasAttribute("aria-describedby") || element.tagName === "LABEL" && !!element.htmlFor;
1001
+ }
1002
+ validate(element) {
1003
+ var _a, _b;
1004
+ const ids = [
1005
+ ...((_a = element.getAttribute("aria-labelledby")) == null ? void 0 : _a.split(" ")) || [],
1006
+ ...((_b = element.getAttribute("aria-describedby")) == null ? void 0 : _b.split(" ")) || [],
1007
+ ...element.tagName === "LABEL" ? [element.htmlFor] : []
1008
+ ].filter((id) => !!id);
1009
+ if (ids.length === 0) {
1010
+ return null;
1011
+ }
1012
+ for (const id of ids) {
1013
+ if (document.getElementById(id)) {
1014
+ return {
1015
+ dependsOnIds: new Set(ids)
1016
+ };
1017
+ }
1018
+ }
1019
+ return {
1020
+ error: {
1021
+ id: "missing-id",
1022
+ message: `Elements with referenced ids do not extist.`
1023
+ },
1024
+ dependsOnIds: new Set(ids)
1025
+ };
1026
+ }
1027
+ };
1028
+
1029
+ // src/rules/focuslost.ts
1030
+ var FocusLostRule = class extends ValidationRule {
1031
+ constructor() {
1032
+ super(...arguments);
1033
+ __publicField(this, "name", "focus-lost");
1034
+ __publicField(this, "anchored", false);
1035
+ __publicField(this, "_focusLostTimeout", 2e3);
1036
+ // For now reporting lost focus after 2 seconds of it being lost.
1037
+ __publicField(this, "_clearScheduledFocusLost");
1038
+ __publicField(this, "_focusedElement");
1039
+ __publicField(this, "_focusedElementPosition");
1040
+ __publicField(this, "_lastFocusStack");
1041
+ __publicField(this, "_lastBlurStack");
1042
+ }
1043
+ _serializeElementPosition(element) {
1044
+ const position = [];
1045
+ const parentElement = element.parentElement;
1046
+ if (!parentElement) {
1047
+ return position;
1048
+ }
1049
+ for (let el = parentElement; el; el = el.parentElement) {
1050
+ const tagName = el.tagName.toLowerCase();
1051
+ position.push(tagName);
1052
+ }
1053
+ return position;
1054
+ }
1055
+ async focused(event) {
1056
+ var _a;
1057
+ const target = event.target;
1058
+ (_a = this._clearScheduledFocusLost) == null ? void 0 : _a.call(this);
1059
+ if (target) {
1060
+ this._lastFocusStack = getStackTrace();
1061
+ this._focusedElement = target;
1062
+ this._focusedElementPosition = this._serializeElementPosition(target);
1063
+ }
1064
+ return null;
1065
+ }
1066
+ async blurred(event) {
1067
+ var _a;
1068
+ const target = event.target;
1069
+ const win = this.window;
1070
+ (_a = this._clearScheduledFocusLost) == null ? void 0 : _a.call(this);
1071
+ if (!target || !win || event.relatedTarget) {
1072
+ return null;
1073
+ }
1074
+ let focusLostTimer;
1075
+ let rejectPromise;
1076
+ const targetPosition = this._focusedElement === target ? this._focusedElementPosition : void 0;
1077
+ this._lastBlurStack = getStackTrace();
1078
+ this._focusedElement = void 0;
1079
+ this._focusedElementPosition = void 0;
1080
+ this._clearScheduledFocusLost = () => {
1081
+ delete this._clearScheduledFocusLost;
1082
+ rejectPromise == null ? void 0 : rejectPromise();
1083
+ if (focusLostTimer) {
1084
+ win.clearTimeout(focusLostTimer);
1085
+ focusLostTimer = void 0;
1086
+ }
1087
+ };
1088
+ return new Promise((resolve, reject) => {
1089
+ rejectPromise = () => {
1090
+ rejectPromise = void 0;
1091
+ reject();
1092
+ };
1093
+ focusLostTimer = win.setTimeout(() => {
1094
+ focusLostTimer = void 0;
1095
+ rejectPromise = void 0;
1096
+ delete this._clearScheduledFocusLost;
1097
+ if (win.document.body && (!win.document.activeElement || win.document.activeElement === win.document.body) && (!win.document.body.contains(target) || !isElementVisible(target))) {
1098
+ resolve({
1099
+ element: target,
1100
+ position: targetPosition || [],
1101
+ error: {
1102
+ id: "focus-lost",
1103
+ message: "Focus lost.",
1104
+ stack: this._lastBlurStack,
1105
+ relStack: this._lastFocusStack
1106
+ }
1107
+ });
1108
+ } else {
1109
+ resolve(null);
1110
+ }
1111
+ }, this._focusLostTimeout);
1112
+ });
1113
+ }
1114
+ };
1115
+
1116
+ // src/rules/badfocus.ts
1117
+ var BadFocusRule = class extends ValidationRule {
1118
+ constructor() {
1119
+ super(...arguments);
1120
+ __publicField(this, "name", "bad-focus");
1121
+ __publicField(this, "anchored", false);
1122
+ __publicField(this, "_lastFocusStack");
1123
+ __publicField(this, "_lastBlurStack");
1124
+ __publicField(this, "_reject");
1125
+ }
1126
+ async focused() {
1127
+ var _a;
1128
+ this._lastFocusStack = getStackTrace();
1129
+ (_a = this._reject) == null ? void 0 : _a.call(this);
1130
+ return null;
1131
+ }
1132
+ async blurred() {
1133
+ var _a;
1134
+ (_a = this._reject) == null ? void 0 : _a.call(this);
1135
+ const win = this.window;
1136
+ if (!win) {
1137
+ return null;
1138
+ }
1139
+ this._lastBlurStack = getStackTrace();
1140
+ return new Promise((resolve, reject) => {
1141
+ let checkTimer;
1142
+ this._reject = () => {
1143
+ if (checkTimer) {
1144
+ win.clearTimeout(checkTimer);
1145
+ checkTimer = void 0;
1146
+ }
1147
+ this._reject = void 0;
1148
+ reject();
1149
+ };
1150
+ checkTimer = win.setTimeout(() => {
1151
+ checkTimer = void 0;
1152
+ this._reject = void 0;
1153
+ if (document.activeElement && !isElementVisible(document.activeElement)) {
1154
+ resolve({
1155
+ element: document.activeElement,
1156
+ error: {
1157
+ id: "bad-focus",
1158
+ message: "Focused stolen by invisible element.",
1159
+ stack: this._lastBlurStack,
1160
+ relStack: this._lastFocusStack
1161
+ }
1162
+ });
1163
+ } else {
1164
+ resolve(null);
1165
+ }
1166
+ }, 100);
1167
+ });
1168
+ }
1169
+ };
1170
+ // Annotate the CommonJS export names for ESM import in node:
1171
+ 0 && (module.exports = {
1172
+ AbleDOM,
1173
+ AtomicRule,
1174
+ BadFocusRule,
1175
+ ExistingIdRule,
1176
+ FocusLostRule,
1177
+ FocusableElementLabelRule,
1178
+ ValidationRule
1179
+ });
1180
+ /*!
1181
+ * Copyright (c) Microsoft Corporation. All rights reserved.
1182
+ * Licensed under the MIT License.
1183
+ */
1184
+ //# sourceMappingURL=index.js.map