@cronn/element-snapshot 0.10.0 → 0.12.0

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