@cronn/element-snapshot 0.10.0 → 0.11.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/browser-lib.iife.js +707 -0
- package/dist/{index.d.ts → index.d.mts} +105 -100
- package/dist/index.mjs +166 -0
- package/package.json +16 -8
- package/dist/browser-lib.d.ts +0 -131
- package/dist/browser-lib.global.js +0 -784
- package/dist/index.js +0 -169
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//#region src/browser/attribute.ts
|
|
5
|
+
function stringAttribute(value) {
|
|
6
|
+
return value !== null && value !== "" ? value : void 0;
|
|
7
|
+
}
|
|
8
|
+
function booleanAttribute(value) {
|
|
9
|
+
return value === true || value === "true" ? true : void 0;
|
|
10
|
+
}
|
|
11
|
+
function numericAttribute(value) {
|
|
12
|
+
if (value === null) return;
|
|
13
|
+
const parsedValue = parseInt(value);
|
|
14
|
+
return Number.isNaN(parsedValue) ? void 0 : parsedValue;
|
|
15
|
+
}
|
|
16
|
+
function enumAttribute(value, supportedValues) {
|
|
17
|
+
if (value === null || !supportedValues.has(value)) return;
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function resolveElementReference(element, attributeName) {
|
|
21
|
+
const referenceId = element.getAttribute(attributeName);
|
|
22
|
+
if (referenceId === null) return null;
|
|
23
|
+
return element.ownerDocument.getElementById(referenceId);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/browser/children.ts
|
|
28
|
+
function snapshotChildren(node, excludedNodes = []) {
|
|
29
|
+
const children = Array.from(node.childNodes).map((node) => snapshotNodeRecursive(node, excludedNodes)).filter((childSnapshot) => childSnapshot !== null).flat();
|
|
30
|
+
return children.length > 0 ? children : void 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/utils/snapshot.ts
|
|
35
|
+
function filterSnapshots(snapshots, filter) {
|
|
36
|
+
return snapshots.flatMap((snapshot) => {
|
|
37
|
+
const filteredChildren = hasChildren(snapshot) ? filterSnapshots(snapshot.children ?? [], filter) : [];
|
|
38
|
+
if (filter(snapshot)) return {
|
|
39
|
+
...snapshot,
|
|
40
|
+
children: filteredChildren.length > 0 ? filteredChildren : void 0
|
|
41
|
+
};
|
|
42
|
+
return filteredChildren;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function hasChildren(snapshot) {
|
|
46
|
+
return "children" in snapshot && snapshot.children !== void 0 && snapshot.children.length > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/browser/text.ts
|
|
51
|
+
function snapshotTextNode(textNode) {
|
|
52
|
+
if (textNode.textContent === null) return null;
|
|
53
|
+
const normalizedText = normalizeText(textNode.textContent);
|
|
54
|
+
if (normalizedText.length === 0) return null;
|
|
55
|
+
return {
|
|
56
|
+
role: "text",
|
|
57
|
+
name: normalizedText
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function snapshotPresentationalChildren(node) {
|
|
61
|
+
const accessibleTextContent = resolveAccessibleTextContent(node);
|
|
62
|
+
if (accessibleTextContent === void 0 || accessibleTextContent.length === 0) return;
|
|
63
|
+
return [{
|
|
64
|
+
role: "text",
|
|
65
|
+
name: accessibleTextContent
|
|
66
|
+
}];
|
|
67
|
+
}
|
|
68
|
+
function resolveAccessibleTextContent(node, excludedNodes = []) {
|
|
69
|
+
const children = snapshotChildren(node, excludedNodes);
|
|
70
|
+
if (children === void 0) return;
|
|
71
|
+
const normalizedText = normalizeText(filterSnapshots(children, (element) => element.role === "text").map((textNode) => textNode.name).join(" "));
|
|
72
|
+
return normalizedText.length > 0 ? normalizedText : void 0;
|
|
73
|
+
}
|
|
74
|
+
function normalizeText(text) {
|
|
75
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/browser/name.ts
|
|
80
|
+
function resolveAccessibleName(element, includeTextContent = true) {
|
|
81
|
+
const labelledByElement = resolveElementReference(element, "aria-labelledby");
|
|
82
|
+
if (labelledByElement !== null) return resolveAccessibleTextContent(labelledByElement);
|
|
83
|
+
if (element.ariaLabel !== null) return element.ariaLabel;
|
|
84
|
+
if (includeTextContent) return resolveAccessibleTextContent(element);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/browser/state.ts
|
|
89
|
+
function disableableAttributes(element) {
|
|
90
|
+
return { disabled: booleanAttribute(isNativeInputElement(element) || element instanceof HTMLButtonElement ? element.hasAttribute("disabled") : false) ?? booleanAttribute(element.ariaDisabled) };
|
|
91
|
+
}
|
|
92
|
+
function selectableAttributes(element) {
|
|
93
|
+
return { selected: booleanAttribute(element.ariaSelected) };
|
|
94
|
+
}
|
|
95
|
+
function inputStateAttributes(element) {
|
|
96
|
+
const supportsHtmlAttributes = isNativeInputElement(element);
|
|
97
|
+
const readonly = booleanAttribute(supportsHtmlAttributes ? element.hasAttribute("readonly") : false);
|
|
98
|
+
const required = booleanAttribute(supportsHtmlAttributes ? element.hasAttribute("required") : false);
|
|
99
|
+
return {
|
|
100
|
+
readonly: readonly ?? booleanAttribute(element.ariaReadOnly),
|
|
101
|
+
...disableableAttributes(element),
|
|
102
|
+
required: required ?? booleanAttribute(element.ariaRequired),
|
|
103
|
+
invalid: booleanAttribute(element.ariaInvalid)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function isNativeInputElement(element) {
|
|
107
|
+
return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/browser/button.ts
|
|
112
|
+
const PRESSED_VALUES = new Set([
|
|
113
|
+
"true",
|
|
114
|
+
"mixed",
|
|
115
|
+
"false"
|
|
116
|
+
]);
|
|
117
|
+
function snapshotButton(element) {
|
|
118
|
+
return {
|
|
119
|
+
role: "button",
|
|
120
|
+
name: resolveAccessibleName(element),
|
|
121
|
+
attributes: {
|
|
122
|
+
...disableableAttributes(element),
|
|
123
|
+
expanded: booleanAttribute(element.ariaExpanded),
|
|
124
|
+
pressed: resolvePressedAttribute(element)
|
|
125
|
+
},
|
|
126
|
+
children: snapshotChildren(element)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function resolvePressedAttribute(element) {
|
|
130
|
+
const pressed = enumAttribute(element.ariaPressed, PRESSED_VALUES);
|
|
131
|
+
if (pressed === "mixed") return pressed;
|
|
132
|
+
return booleanAttribute(pressed === "true");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/browser/description.ts
|
|
137
|
+
function discribableAttributes(element) {
|
|
138
|
+
return { description: resolveDescription(element) };
|
|
139
|
+
}
|
|
140
|
+
function resolveDescription(element) {
|
|
141
|
+
const describedByElement = resolveElementReference(element, "aria-describedby");
|
|
142
|
+
if (describedByElement !== null) return resolveAccessibleTextContent(describedByElement);
|
|
143
|
+
if (element.ariaDescription !== null) return element.ariaDescription;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/browser/container.ts
|
|
148
|
+
const CONTAINER_ROLES = new Set([
|
|
149
|
+
"paragraph",
|
|
150
|
+
"list",
|
|
151
|
+
"listitem",
|
|
152
|
+
"listbox",
|
|
153
|
+
"article",
|
|
154
|
+
"banner",
|
|
155
|
+
"complementary",
|
|
156
|
+
"contentinfo",
|
|
157
|
+
"form",
|
|
158
|
+
"main",
|
|
159
|
+
"navigation",
|
|
160
|
+
"region",
|
|
161
|
+
"search",
|
|
162
|
+
"term",
|
|
163
|
+
"definition",
|
|
164
|
+
"table",
|
|
165
|
+
"grid",
|
|
166
|
+
"rowgroup",
|
|
167
|
+
"row",
|
|
168
|
+
"rowheader",
|
|
169
|
+
"cell",
|
|
170
|
+
"gridcell",
|
|
171
|
+
"alert",
|
|
172
|
+
"menu",
|
|
173
|
+
"tablist",
|
|
174
|
+
"tabpanel"
|
|
175
|
+
]);
|
|
176
|
+
function snapshotContainer(role, element) {
|
|
177
|
+
const children = snapshotChildren(element);
|
|
178
|
+
return {
|
|
179
|
+
role,
|
|
180
|
+
name: resolveAccessibleName(element, false),
|
|
181
|
+
children
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function isContainerRole(role) {
|
|
185
|
+
return CONTAINER_ROLES.has(role);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/browser/selector.ts
|
|
190
|
+
function selectorList(...selectors) {
|
|
191
|
+
return selectors.join(",");
|
|
192
|
+
}
|
|
193
|
+
function roleSelector(roleName) {
|
|
194
|
+
return attributeSelector("role", roleName);
|
|
195
|
+
}
|
|
196
|
+
function attributeSelector(name, value) {
|
|
197
|
+
return `[${name}='${value}']`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/browser/utils.ts
|
|
202
|
+
function getElementTagName(element) {
|
|
203
|
+
return element.tagName.toLowerCase();
|
|
204
|
+
}
|
|
205
|
+
function isWithinElement(element, containerSelector) {
|
|
206
|
+
return element.closest(containerSelector) !== null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/browser/role.ts
|
|
211
|
+
const ELEMENT_ROLES = {
|
|
212
|
+
main: "main",
|
|
213
|
+
nav: "navigation",
|
|
214
|
+
form: "form",
|
|
215
|
+
header: "banner",
|
|
216
|
+
section: "region",
|
|
217
|
+
article: "article",
|
|
218
|
+
aside: "complementary",
|
|
219
|
+
search: "search",
|
|
220
|
+
footer: "contentinfo",
|
|
221
|
+
p: "paragraph",
|
|
222
|
+
h1: "heading",
|
|
223
|
+
h2: "heading",
|
|
224
|
+
h3: "heading",
|
|
225
|
+
h4: "heading",
|
|
226
|
+
h5: "heading",
|
|
227
|
+
h6: "heading",
|
|
228
|
+
ul: "list",
|
|
229
|
+
ol: "list",
|
|
230
|
+
li: "listitem",
|
|
231
|
+
a: "link",
|
|
232
|
+
button: "button",
|
|
233
|
+
input: resolveInputRole,
|
|
234
|
+
textarea: "textbox",
|
|
235
|
+
select: "combobox",
|
|
236
|
+
option: "option",
|
|
237
|
+
dt: "term",
|
|
238
|
+
dd: "definition",
|
|
239
|
+
table: "table",
|
|
240
|
+
thead: "rowgroup",
|
|
241
|
+
tbody: "rowgroup",
|
|
242
|
+
tfoot: "rowgroup",
|
|
243
|
+
tr: "row",
|
|
244
|
+
th: resolveTableHeaderCellRole,
|
|
245
|
+
td: "cell",
|
|
246
|
+
fieldset: "group",
|
|
247
|
+
progress: "progressbar",
|
|
248
|
+
hr: "separator",
|
|
249
|
+
img: "img",
|
|
250
|
+
svg: "img"
|
|
251
|
+
};
|
|
252
|
+
const CONTEXT_DEPENDENT_ROLES = {
|
|
253
|
+
banner: isTopLevelBanner,
|
|
254
|
+
region: hasAccessibleName,
|
|
255
|
+
term: isWithinDescriptionList,
|
|
256
|
+
definition: isWithinDescriptionList,
|
|
257
|
+
listitem: isWithinList,
|
|
258
|
+
menuitem: isWithinMenu,
|
|
259
|
+
rowgroup: isWithinTableOrGrid,
|
|
260
|
+
row: isWithinTableOrGrid,
|
|
261
|
+
columnheader: isWithinTableRowOrGridRow,
|
|
262
|
+
rowheader: isWithinTableRowOrGridRow,
|
|
263
|
+
cell: isWithinTableRow,
|
|
264
|
+
gridcell: isWithinGridRow,
|
|
265
|
+
tab: isWithinTablist
|
|
266
|
+
};
|
|
267
|
+
const INPUT_ROLES = {
|
|
268
|
+
button: "button",
|
|
269
|
+
checkbox: "checkbox",
|
|
270
|
+
email: "textbox",
|
|
271
|
+
image: "button",
|
|
272
|
+
number: "spinbutton",
|
|
273
|
+
radio: "radio",
|
|
274
|
+
range: "slider",
|
|
275
|
+
reset: "button",
|
|
276
|
+
search: "searchbox",
|
|
277
|
+
submit: "button",
|
|
278
|
+
tel: "searchbox",
|
|
279
|
+
text: "textbox",
|
|
280
|
+
url: "textbox"
|
|
281
|
+
};
|
|
282
|
+
function parseElementRole(element) {
|
|
283
|
+
const resolvedElementRole = resolveElementRole(element);
|
|
284
|
+
if (resolvedElementRole === void 0) return;
|
|
285
|
+
const isValidInContext = CONTEXT_DEPENDENT_ROLES[resolvedElementRole];
|
|
286
|
+
if (isValidInContext !== void 0 && !isValidInContext(element)) return;
|
|
287
|
+
return resolvedElementRole;
|
|
288
|
+
}
|
|
289
|
+
function resolveElementRole(element) {
|
|
290
|
+
const explicitRole = element.role;
|
|
291
|
+
if (isIgnoredRole(explicitRole)) return;
|
|
292
|
+
if (explicitRole !== null && (hasRoleSpecificSnapshot(explicitRole) || isContainerRole(explicitRole))) return explicitRole;
|
|
293
|
+
const roleResolver = ELEMENT_ROLES[getElementTagName(element)];
|
|
294
|
+
if (roleResolver === void 0) return;
|
|
295
|
+
if (typeof roleResolver === "string") return roleResolver;
|
|
296
|
+
return roleResolver(element);
|
|
297
|
+
}
|
|
298
|
+
function isIgnoredRole(role) {
|
|
299
|
+
return role === "presentation" || role === "none";
|
|
300
|
+
}
|
|
301
|
+
function resolveInputRole(element) {
|
|
302
|
+
if (!(element instanceof HTMLInputElement)) return;
|
|
303
|
+
const roleResolver = INPUT_ROLES[element.type];
|
|
304
|
+
if (roleResolver === void 0) return "textbox";
|
|
305
|
+
if (typeof roleResolver === "string") return roleResolver;
|
|
306
|
+
return roleResolver(element);
|
|
307
|
+
}
|
|
308
|
+
const DISALLOWED_BANNER_CONTAINER_ELEMENTS = [
|
|
309
|
+
"article",
|
|
310
|
+
"aside",
|
|
311
|
+
"main",
|
|
312
|
+
"nav",
|
|
313
|
+
"section"
|
|
314
|
+
];
|
|
315
|
+
const DISALLOWED_BANNER_CONTAINER_ROLES = [
|
|
316
|
+
"article",
|
|
317
|
+
"complementary",
|
|
318
|
+
"main",
|
|
319
|
+
"navigation",
|
|
320
|
+
"region"
|
|
321
|
+
];
|
|
322
|
+
function isTopLevelBanner(element) {
|
|
323
|
+
return !isWithinElement(element, selectorList(...DISALLOWED_BANNER_CONTAINER_ELEMENTS, ...DISALLOWED_BANNER_CONTAINER_ROLES.map(roleSelector)));
|
|
324
|
+
}
|
|
325
|
+
function hasAccessibleName(element) {
|
|
326
|
+
return resolveAccessibleName(element, false) !== void 0;
|
|
327
|
+
}
|
|
328
|
+
function isWithinDescriptionList(element) {
|
|
329
|
+
return isWithinElement(element, "dl");
|
|
330
|
+
}
|
|
331
|
+
function resolveTableHeaderCellRole(element) {
|
|
332
|
+
if (!(element instanceof HTMLTableCellElement)) return;
|
|
333
|
+
switch (element.scope) {
|
|
334
|
+
case "col": return "columnheader";
|
|
335
|
+
case "row": return "rowheader";
|
|
336
|
+
default: return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function isWithinTableOrGrid(element) {
|
|
340
|
+
return isWithinTable(element) || isWithinGrid(element);
|
|
341
|
+
}
|
|
342
|
+
function isWithinTable(element) {
|
|
343
|
+
return isWithinElement(element, selectorList("table", roleSelector("table")));
|
|
344
|
+
}
|
|
345
|
+
function isWithinGrid(element) {
|
|
346
|
+
return isWithinElement(element, roleSelector("grid"));
|
|
347
|
+
}
|
|
348
|
+
function isWithinTableRowOrGridRow(element) {
|
|
349
|
+
return isWithinTableRow(element) || isWithinGridRow(element);
|
|
350
|
+
}
|
|
351
|
+
function isWithinTableRow(element) {
|
|
352
|
+
const closestRow = element.closest(selectorList("tr", roleSelector("row")));
|
|
353
|
+
return closestRow !== null && isWithinTable(closestRow);
|
|
354
|
+
}
|
|
355
|
+
function isWithinGridRow(element) {
|
|
356
|
+
const closestRow = element.closest(selectorList("tr", roleSelector("row")));
|
|
357
|
+
return closestRow !== null && isWithinGrid(closestRow);
|
|
358
|
+
}
|
|
359
|
+
function isWithinList(element) {
|
|
360
|
+
return isWithinElement(element, selectorList("ul", "ol", roleSelector("list")));
|
|
361
|
+
}
|
|
362
|
+
function isWithinMenu(element) {
|
|
363
|
+
return isWithinElement(element, roleSelector("menu"));
|
|
364
|
+
}
|
|
365
|
+
function isWithinTablist(element) {
|
|
366
|
+
return isWithinElement(element, roleSelector("tablist"));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/browser/input.ts
|
|
371
|
+
function snapshotInput(element) {
|
|
372
|
+
if (element instanceof HTMLInputElement) return snapshotInputElement(element);
|
|
373
|
+
if (element instanceof HTMLTextAreaElement) return snapshotTextareaElement(element);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
function snapshotInputElement(element) {
|
|
377
|
+
const inputRole = resolveInputRole(element);
|
|
378
|
+
if (inputRole === void 0 || inputRole === "button" || inputRole === "combobox") return null;
|
|
379
|
+
const value = resolveInputValue(element);
|
|
380
|
+
return {
|
|
381
|
+
role: inputRole,
|
|
382
|
+
name: resolveInputLabel(element),
|
|
383
|
+
attributes: {
|
|
384
|
+
value,
|
|
385
|
+
checked: resolveChecked(element),
|
|
386
|
+
...snapshotCommonInputAttributes(element, value === void 0)
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function snapshotTextareaElement(element) {
|
|
391
|
+
const value = resolveInputValue(element);
|
|
392
|
+
return {
|
|
393
|
+
role: "textbox",
|
|
394
|
+
name: resolveInputLabel(element),
|
|
395
|
+
attributes: {
|
|
396
|
+
value,
|
|
397
|
+
...snapshotCommonInputAttributes(element, value === void 0)
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function snapshotCommonInputAttributes(element, isEmpty) {
|
|
402
|
+
return {
|
|
403
|
+
...discribableAttributes(element),
|
|
404
|
+
placeholder: isEmpty ? resolvePlaceholder(element) : void 0,
|
|
405
|
+
...inputStateAttributes(element)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const CHECKED_INPUT_TYPES = new Set(["checkbox", "radio"]);
|
|
409
|
+
function resolveInputValue(element) {
|
|
410
|
+
if (element instanceof HTMLInputElement && CHECKED_INPUT_TYPES.has(element.type)) return;
|
|
411
|
+
return stringAttribute(element.value);
|
|
412
|
+
}
|
|
413
|
+
function resolveChecked(element) {
|
|
414
|
+
if (!(element instanceof HTMLInputElement)) return;
|
|
415
|
+
return element.checked ? true : void 0;
|
|
416
|
+
}
|
|
417
|
+
function resolveInputLabel(element) {
|
|
418
|
+
const accessibleName = resolveAccessibleName(element, false);
|
|
419
|
+
if (accessibleName !== void 0) return accessibleName;
|
|
420
|
+
if (element.id !== null) {
|
|
421
|
+
const referencedElement = element.ownerDocument.querySelector(`[for='${element.id}']`);
|
|
422
|
+
if (referencedElement !== null) return resolveAccessibleTextContent(referencedElement);
|
|
423
|
+
}
|
|
424
|
+
const closestLabel = element.closest("label");
|
|
425
|
+
if (closestLabel !== null) return resolveAccessibleTextContent(closestLabel, [element]);
|
|
426
|
+
}
|
|
427
|
+
function resolvePlaceholder(element) {
|
|
428
|
+
if (element instanceof HTMLSelectElement || element instanceof HTMLButtonElement) return;
|
|
429
|
+
return stringAttribute(element.placeholder);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/browser/combobox.ts
|
|
434
|
+
function snapshotCombobox(element) {
|
|
435
|
+
if (!isCombobox(element)) return null;
|
|
436
|
+
const options = snapshotOptions(element);
|
|
437
|
+
const value = resolveValue$1(element);
|
|
438
|
+
return {
|
|
439
|
+
role: "combobox",
|
|
440
|
+
name: resolveInputLabel(element),
|
|
441
|
+
attributes: {
|
|
442
|
+
value,
|
|
443
|
+
...snapshotCommonInputAttributes(element, value === void 0)
|
|
444
|
+
},
|
|
445
|
+
options
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function isCombobox(element) {
|
|
449
|
+
if (element instanceof HTMLSelectElement) return element.role === null || element.role === "combobox";
|
|
450
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLButtonElement) return element.role === "combobox";
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
function resolveValue$1(element) {
|
|
454
|
+
if (element instanceof HTMLButtonElement) return resolveAccessibleTextContent(element);
|
|
455
|
+
if (element instanceof HTMLInputElement) return resolveInputValue(element);
|
|
456
|
+
const selectedLabels = Array.from(element.selectedOptions).map((option) => option.label);
|
|
457
|
+
if (selectedLabels.length === 0) return;
|
|
458
|
+
if (selectedLabels.length === 1) return selectedLabels.at(0);
|
|
459
|
+
return selectedLabels;
|
|
460
|
+
}
|
|
461
|
+
function snapshotOptions(element) {
|
|
462
|
+
return resolveOptions(element).map(snapshotOption).filter((childSnapshot) => childSnapshot !== null);
|
|
463
|
+
}
|
|
464
|
+
function resolveOptions(element) {
|
|
465
|
+
if (element instanceof HTMLSelectElement) return Array.from(element.options);
|
|
466
|
+
const controlledElement = resolveElementReference(element, "aria-controls");
|
|
467
|
+
if (controlledElement === null) return [];
|
|
468
|
+
const optionSelector = selectorList("option", roleSelector("option"));
|
|
469
|
+
return Array.from(controlledElement.querySelectorAll(optionSelector));
|
|
470
|
+
}
|
|
471
|
+
function snapshotOption(element) {
|
|
472
|
+
return {
|
|
473
|
+
role: "option",
|
|
474
|
+
name: resolveAccessibleName(element),
|
|
475
|
+
attributes: { selected: element instanceof HTMLOptionElement ? booleanAttribute(element.selected) : booleanAttribute(element.ariaSelected) },
|
|
476
|
+
children: snapshotChildren(element)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/browser/dialog.ts
|
|
482
|
+
function snapshotDialogWithRole(role) {
|
|
483
|
+
return (element) => snapshotDialog(role, element);
|
|
484
|
+
}
|
|
485
|
+
function snapshotDialog(role, element) {
|
|
486
|
+
return {
|
|
487
|
+
role,
|
|
488
|
+
name: resolveAccessibleName(element, false),
|
|
489
|
+
attributes: {
|
|
490
|
+
...discribableAttributes(element),
|
|
491
|
+
modal: booleanAttribute(element.ariaModal)
|
|
492
|
+
},
|
|
493
|
+
children: snapshotChildren(element)
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/browser/group.ts
|
|
499
|
+
function snapshotGroup(element) {
|
|
500
|
+
return {
|
|
501
|
+
role: "group",
|
|
502
|
+
name: resolveGroupName(element),
|
|
503
|
+
children: snapshotChildren(element)
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function resolveGroupName(element) {
|
|
507
|
+
const legendElement = element instanceof HTMLFieldSetElement ? element.querySelector("legend") : null;
|
|
508
|
+
if (legendElement !== null) return resolveAccessibleTextContent(legendElement);
|
|
509
|
+
return resolveAccessibleName(element, false);
|
|
510
|
+
}
|
|
511
|
+
function snapshotRadiogroup(element) {
|
|
512
|
+
return {
|
|
513
|
+
role: "radiogroup",
|
|
514
|
+
name: resolveAccessibleName(element, false),
|
|
515
|
+
attributes: discribableAttributes(element),
|
|
516
|
+
children: snapshotChildren(element)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/browser/heading.ts
|
|
522
|
+
const LEVEL_REGEXP = /^H([1-6])$/;
|
|
523
|
+
function snapshotHeading(element) {
|
|
524
|
+
if (!(element instanceof HTMLHeadingElement)) return null;
|
|
525
|
+
return {
|
|
526
|
+
role: "heading",
|
|
527
|
+
name: resolveAccessibleName(element),
|
|
528
|
+
attributes: { level: resolveLevel(element) },
|
|
529
|
+
children: snapshotChildren(element)
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function resolveLevel(element) {
|
|
533
|
+
return numericAttribute(LEVEL_REGEXP.exec(element.nodeName)?.at(1) ?? null);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/browser/image.ts
|
|
538
|
+
function snapshotImage(element) {
|
|
539
|
+
return {
|
|
540
|
+
role: "img",
|
|
541
|
+
name: resolveImageName(element)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function resolveImageName(element) {
|
|
545
|
+
if (element instanceof HTMLImageElement) return stringAttribute(element.alt);
|
|
546
|
+
return resolveAccessibleName(element, true);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/browser/link.ts
|
|
551
|
+
function snapshotLink(element) {
|
|
552
|
+
return {
|
|
553
|
+
role: "link",
|
|
554
|
+
name: resolveAccessibleName(element),
|
|
555
|
+
attributes: linkAttributes(element),
|
|
556
|
+
children: snapshotChildren(element)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function linkAttributes(element) {
|
|
560
|
+
return { url: stringAttribute(element.getAttribute("href")) };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/browser/list.ts
|
|
565
|
+
function snapshotMenuitem(element) {
|
|
566
|
+
return {
|
|
567
|
+
role: "menuitem",
|
|
568
|
+
name: resolveAccessibleName(element),
|
|
569
|
+
attributes: {
|
|
570
|
+
...linkAttributes(element),
|
|
571
|
+
...disableableAttributes(element)
|
|
572
|
+
},
|
|
573
|
+
children: snapshotChildren(element)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/browser/progressbar.ts
|
|
579
|
+
function snapshotProgressbar(element) {
|
|
580
|
+
return {
|
|
581
|
+
role: "progressbar",
|
|
582
|
+
name: resolveAccessibleName(element, false),
|
|
583
|
+
attributes: { value: resolveValue(element) },
|
|
584
|
+
children: snapshotPresentationalChildren(element)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function resolveValue(element) {
|
|
588
|
+
if (element instanceof HTMLProgressElement) return element.value;
|
|
589
|
+
return numericAttribute(element.ariaValueNow);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/browser/separator.ts
|
|
594
|
+
function snapshotSeparator(element) {
|
|
595
|
+
return {
|
|
596
|
+
role: "separator",
|
|
597
|
+
children: snapshotPresentationalChildren(element)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/browser/tab.ts
|
|
603
|
+
function snapshotTab(element) {
|
|
604
|
+
return {
|
|
605
|
+
role: "tab",
|
|
606
|
+
name: resolveAccessibleName(element),
|
|
607
|
+
attributes: {
|
|
608
|
+
...disableableAttributes(element),
|
|
609
|
+
...selectableAttributes(element)
|
|
610
|
+
},
|
|
611
|
+
children: snapshotChildren(element)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/browser/table.ts
|
|
617
|
+
const SORT_ATTRIBUTES = new Set([
|
|
618
|
+
"ascending",
|
|
619
|
+
"descending",
|
|
620
|
+
"other"
|
|
621
|
+
]);
|
|
622
|
+
function snapshotColumnheader(element) {
|
|
623
|
+
return {
|
|
624
|
+
role: "columnheader",
|
|
625
|
+
name: resolveAccessibleName(element),
|
|
626
|
+
attributes: { sort: enumAttribute(element.ariaSort, SORT_ATTRIBUTES) },
|
|
627
|
+
children: snapshotChildren(element)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/browser/element.ts
|
|
633
|
+
const ROLE_SNAPSHOTS = {
|
|
634
|
+
button: snapshotButton,
|
|
635
|
+
checkbox: snapshotInput,
|
|
636
|
+
combobox: snapshotCombobox,
|
|
637
|
+
option: snapshotOption,
|
|
638
|
+
heading: snapshotHeading,
|
|
639
|
+
link: snapshotLink,
|
|
640
|
+
radio: snapshotInput,
|
|
641
|
+
searchbox: snapshotInput,
|
|
642
|
+
slider: snapshotInput,
|
|
643
|
+
spinbutton: snapshotInput,
|
|
644
|
+
textbox: snapshotInput,
|
|
645
|
+
dialog: snapshotDialogWithRole("dialog"),
|
|
646
|
+
alertdialog: snapshotDialogWithRole("alertdialog"),
|
|
647
|
+
tab: snapshotTab,
|
|
648
|
+
menuitem: snapshotMenuitem,
|
|
649
|
+
columnheader: snapshotColumnheader,
|
|
650
|
+
group: snapshotGroup,
|
|
651
|
+
radiogroup: snapshotRadiogroup,
|
|
652
|
+
progressbar: snapshotProgressbar,
|
|
653
|
+
img: snapshotImage,
|
|
654
|
+
separator: snapshotSeparator
|
|
655
|
+
};
|
|
656
|
+
function snapshotElement(element) {
|
|
657
|
+
return snapshotNodeRecursive(element);
|
|
658
|
+
}
|
|
659
|
+
function snapshotNodeRecursive(node, excludedNodes = []) {
|
|
660
|
+
const snapshot = snapshotNodeByType(node, excludedNodes);
|
|
661
|
+
if (snapshot === null) return [];
|
|
662
|
+
if (Array.isArray(snapshot)) return snapshot;
|
|
663
|
+
return [snapshot];
|
|
664
|
+
}
|
|
665
|
+
function snapshotNodeByType(node, excludedNodes) {
|
|
666
|
+
if (excludedNodes.includes(node)) return null;
|
|
667
|
+
if (node.nodeType === Node.TEXT_NODE) return snapshotTextNode(node);
|
|
668
|
+
if (!isSupportedElement(node) || isHiddenElement(node)) return null;
|
|
669
|
+
const elementRole = parseElementRole(node);
|
|
670
|
+
if (elementRole === void 0) return snapshotChildren(node, excludedNodes) ?? null;
|
|
671
|
+
if (hasRoleSpecificSnapshot(elementRole)) {
|
|
672
|
+
const snapshotByRole = ROLE_SNAPSHOTS[elementRole];
|
|
673
|
+
return snapshotByRole(node);
|
|
674
|
+
}
|
|
675
|
+
return snapshotContainer(elementRole, node);
|
|
676
|
+
}
|
|
677
|
+
const UNSUPPORTED_ELEMENTS = new Set([
|
|
678
|
+
"style",
|
|
679
|
+
"script",
|
|
680
|
+
"noscript",
|
|
681
|
+
"picture",
|
|
682
|
+
"audio",
|
|
683
|
+
"video",
|
|
684
|
+
"figure",
|
|
685
|
+
"template"
|
|
686
|
+
]);
|
|
687
|
+
function isSupportedElement(node) {
|
|
688
|
+
if (!(node instanceof HTMLElement || node instanceof SVGElement)) return false;
|
|
689
|
+
const tagName = getElementTagName(node);
|
|
690
|
+
return !UNSUPPORTED_ELEMENTS.has(tagName);
|
|
691
|
+
}
|
|
692
|
+
function hasRoleSpecificSnapshot(role) {
|
|
693
|
+
return role in ROLE_SNAPSHOTS;
|
|
694
|
+
}
|
|
695
|
+
function isHiddenElement(element) {
|
|
696
|
+
if (element.hasAttribute("hidden")) return true;
|
|
697
|
+
if (booleanAttribute(element.ariaHidden) === true) return true;
|
|
698
|
+
const cssStyles = window.getComputedStyle(element);
|
|
699
|
+
return cssStyles.display === "none" || cssStyles.visibility === "hidden" || cssStyles.visibility === "collapse";
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region src/browser-lib.ts
|
|
704
|
+
window.snapshotElement = snapshotElement;
|
|
705
|
+
|
|
706
|
+
//#endregion
|
|
707
|
+
})();
|