testaro 27.0.0 → 28.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +3 -1
- package/README.md +14 -3
- package/package.json +2 -1
- package/procs/aslint.js +83 -0
- package/procs/nav.js +56 -14
- package/procs/standardize.js +36 -0
- package/run.js +35 -22
- package/tests/aslint.js +83 -0
- package/tests/axe.js +4 -0
- package/tests/qualWeb.js +6 -0
- package/aslint/LICENSE +0 -362
- package/aslint/README.md +0 -260
- package/aslint/app/rules/abstract-rule.ts +0 -83
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.documentation.md +0 -36
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.test.ts +0 -113
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.ts +0 -103
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.documentation.md +0 -34
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.test.ts +0 -82
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.ts +0 -44
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.documentation.md +0 -40
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.test.ts +0 -48
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts +0 -37
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.test.ts +0 -50
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.ts +0 -70
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.test.ts +0 -55
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.ts +0 -83
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.test.ts +0 -12
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts +0 -73
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.test.ts +0 -12
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.ts +0 -87
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.test.ts +0 -48
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.ts +0 -40
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.ts +0 -66
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.ts +0 -134
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.ts +0 -90
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.test.ts +0 -12
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.ts +0 -48
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.test.ts +0 -12
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.ts +0 -148
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.test.ts +0 -12
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.ts +0 -66
- package/aslint/app/rules/aslint/outline-zero/outline-zero.test.ts +0 -12
- package/aslint/app/rules/aslint/outline-zero/outline-zero.ts +0 -85
- package/aslint/app/rules/aslint/overlay/overlay.test.ts +0 -122
- package/aslint/app/rules/aslint/overlay/overlay.ts +0 -141
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.documentation.md +0 -49
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.test.ts +0 -91
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.ts +0 -62
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.documentation.md +0 -44
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.test.ts +0 -111
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.ts +0 -120
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.documentation.md +0 -45
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.test.ts +0 -63
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.ts +0 -44
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.documentation.md +0 -55
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.test.ts +0 -80
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.ts +0 -58
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.documentation.md +0 -48
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.test.ts +0 -52
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.ts +0 -32
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.documentation.md +0 -44
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.test.ts +0 -12
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.ts +0 -83
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.documentation.md +0 -46
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.test.ts +0 -46
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.ts +0 -36
- package/aslint/app/rules/aslint/role-application/role-application.test.ts +0 -48
- package/aslint/app/rules/aslint/role-application/role-application.ts +0 -38
- package/aslint/app/rules/aslint/rtl-content/rtl-content.test.ts +0 -12
- package/aslint/app/rules/aslint/rtl-content/rtl-content.ts +0 -75
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.test.ts +0 -12
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.ts +0 -48
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.test.ts +0 -73
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.ts +0 -34
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden.documentation.md +0 -32
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.docmentation.md +0 -48
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.test.ts +0 -67
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.ts +0 -63
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts +0 -12
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.ts +0 -63
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.documentation.md +0 -65
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.test.ts +0 -53
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.ts +0 -47
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.documentation.md +0 -57
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.test.ts +0 -52
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.ts +0 -63
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.documentation.md +0 -39
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.test.ts +0 -66
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.ts +0 -37
- package/aslint/utils/aria.test.ts +0 -12
- package/aslint/utils/aria.ts +0 -120
- package/aslint/utils/async.test.ts +0 -25
- package/aslint/utils/async.ts +0 -22
- package/aslint/utils/common.test.ts +0 -239
- package/aslint/utils/common.ts +0 -168
- package/aslint/utils/console.test.ts +0 -85
- package/aslint/utils/console.ts +0 -89
- package/aslint/utils/css.test.ts +0 -153
- package/aslint/utils/css.ts +0 -191
- package/aslint/utils/dom.test.ts +0 -627
- package/aslint/utils/dom.ts +0 -1051
- package/aslint/utils/env.test.ts +0 -14
- package/aslint/utils/env.ts +0 -8
- package/aslint/utils/func.test.ts +0 -160
- package/aslint/utils/func.ts +0 -70
- package/aslint/utils/global.test.ts +0 -12
- package/aslint/utils/global.ts +0 -25
- package/aslint/utils/object.test.ts +0 -524
- package/aslint/utils/object.ts +0 -278
- package/aslint/utils/report.test.ts +0 -56
- package/aslint/utils/report.ts +0 -36
- package/aslint/utils/text.test.ts +0 -270
- package/aslint/utils/text.ts +0 -165
- package/aslint/utils/time.test.ts +0 -43
- package/aslint/utils/time.ts +0 -33
package/aslint/utils/dom.ts
DELETED
|
@@ -1,1051 +0,0 @@
|
|
|
1
|
-
import { TinyColor } from '@ctrl/tinycolor';
|
|
2
|
-
|
|
3
|
-
import { NATIVELY_DISABLEABLE } from '../constants/nativelyDisableable';
|
|
4
|
-
import { NODE_TYPE } from '../constants/nodeType';
|
|
5
|
-
import { Css } from './css';
|
|
6
|
-
import { TextUtility } from './text';
|
|
7
|
-
import { ObjectUtility } from './object';
|
|
8
|
-
import { Context, IContextElement, IHtmlInfo } from '../interfaces/context.interface';
|
|
9
|
-
|
|
10
|
-
type FormElement = HTMLInputElement | HTMLOutputElement | HTMLTextAreaElement;
|
|
11
|
-
|
|
12
|
-
export class DomUtility {
|
|
13
|
-
private static regExpTest: RegExp['test'] = RegExp.prototype.test;
|
|
14
|
-
private static nonSpaceRe: RegExp = /\S/;
|
|
15
|
-
|
|
16
|
-
public static isNativelyDisableable(element: Element | Node & ParentNode): boolean {
|
|
17
|
-
return element.nodeName.toUpperCase() in NATIVELY_DISABLEABLE;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public static getBodyElement(): HTMLElement | HTMLCollectionOf<HTMLBodyElement> {
|
|
21
|
-
return document.body ? document.body : document.getElementsByTagName('body')[0];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public static getSelectedOption(select: HTMLSelectElement): any {
|
|
25
|
-
const options: HTMLOptionsCollection = select.options;
|
|
26
|
-
const len: number = options.length - 1;
|
|
27
|
-
|
|
28
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
29
|
-
if (options[i].selected) {
|
|
30
|
-
return options[i];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Get text from a given node
|
|
38
|
-
*
|
|
39
|
-
* @static
|
|
40
|
-
* @param {Node} node
|
|
41
|
-
* @param {boolean} [filterEmpty=false] Set true to filter out empty text nodes. Default: false
|
|
42
|
-
* @param {boolean} [safeTrim=false] Set true to use more advanced trim. Default: false
|
|
43
|
-
* @returns {string}
|
|
44
|
-
* @memberof DomUtility
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
public static getText(node: Node, filterEmpty: boolean = false, safeTrim: boolean = false): string {
|
|
48
|
-
const element: Node = node;
|
|
49
|
-
const notFound: string = '';
|
|
50
|
-
const nodeList: string[] = [];
|
|
51
|
-
|
|
52
|
-
if (ObjectUtility.isHtmlElement(element) === false) {
|
|
53
|
-
return notFound;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let filterCallback: NodeFilter | null | undefined = null;
|
|
57
|
-
|
|
58
|
-
if (filterEmpty) {
|
|
59
|
-
|
|
60
|
-
if (safeTrim) {
|
|
61
|
-
filterCallback = {
|
|
62
|
-
acceptNode: (_node: Node): number => {
|
|
63
|
-
return (TextUtility.safeTrim(_node.nodeValue!).length > 0) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
} else {
|
|
67
|
-
filterCallback = {
|
|
68
|
-
acceptNode: (_node: Node): number => {
|
|
69
|
-
return (_node.nodeValue!.trim().length > 0) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const treeWalker: TreeWalker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, filterCallback);
|
|
76
|
-
|
|
77
|
-
while (treeWalker.nextNode()) {
|
|
78
|
-
nodeList.push((treeWalker.currentNode as Text).data);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return nodeList.join('');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public static getTextFromNode(node: Node): string {
|
|
85
|
-
let text: string = '';
|
|
86
|
-
let option: HTMLOptionElement;
|
|
87
|
-
|
|
88
|
-
switch (node.nodeName.toLowerCase()) {
|
|
89
|
-
case 'img':
|
|
90
|
-
case 'area':
|
|
91
|
-
if ((node as HTMLImageElement | HTMLAreaElement).alt && (node as HTMLImageElement | HTMLAreaElement).alt.length) {
|
|
92
|
-
text += (node as HTMLImageElement | HTMLAreaElement).alt;
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
|
|
96
|
-
case 'input':
|
|
97
|
-
case 'output':
|
|
98
|
-
case 'textarea':
|
|
99
|
-
if (
|
|
100
|
-
(node as FormElement).type === 'email' ||
|
|
101
|
-
(node as FormElement).type === 'number' ||
|
|
102
|
-
(node as FormElement).type === 'text' ||
|
|
103
|
-
(node as FormElement).type === 'textarea'
|
|
104
|
-
) {
|
|
105
|
-
text += (node as FormElement).value;
|
|
106
|
-
} else if (
|
|
107
|
-
(node as FormElement).type === 'image' &&
|
|
108
|
-
typeof (node as HTMLInputElement).alt === 'string' &&
|
|
109
|
-
(node as HTMLInputElement).alt.length
|
|
110
|
-
) {
|
|
111
|
-
text += (node as HTMLInputElement).alt;
|
|
112
|
-
}
|
|
113
|
-
break;
|
|
114
|
-
|
|
115
|
-
case 'select':
|
|
116
|
-
option = DomUtility.getSelectedOption(node as HTMLSelectElement);
|
|
117
|
-
text += DomUtility.getText(option);
|
|
118
|
-
break;
|
|
119
|
-
|
|
120
|
-
default:
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return text;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public static getTextFromNodes(nodes: Node[] | NodeListOf<ChildNode>): string {
|
|
128
|
-
const len: number = nodes ? nodes.length : 0;
|
|
129
|
-
let node: Node;
|
|
130
|
-
let text: string = '';
|
|
131
|
-
|
|
132
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
133
|
-
node = nodes[i];
|
|
134
|
-
|
|
135
|
-
if (node.nodeType === NODE_TYPE.TEXT_NODE) {
|
|
136
|
-
text += (node as Text).data;
|
|
137
|
-
} else if (node.nodeType === NODE_TYPE.ELEMENT_NODE) {
|
|
138
|
-
switch (node.nodeName.toLowerCase()) {
|
|
139
|
-
case 'img':
|
|
140
|
-
case 'area':
|
|
141
|
-
case 'input':
|
|
142
|
-
case 'output':
|
|
143
|
-
case 'textarea':
|
|
144
|
-
case 'select':
|
|
145
|
-
text += DomUtility.getTextFromNode(node);
|
|
146
|
-
break;
|
|
147
|
-
|
|
148
|
-
default:
|
|
149
|
-
if (nodes[i].childNodes) {
|
|
150
|
-
text += DomUtility.getTextFromNodes(nodes[i].childNodes);
|
|
151
|
-
}
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return text;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public static testRegExp(re: RegExp, string: string): boolean {
|
|
161
|
-
return DomUtility.regExpTest.call(re, string);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public static isWhitespace(str: string | null): boolean {
|
|
165
|
-
if (typeof str !== 'string') {
|
|
166
|
-
console.warn(`[DomUtility.isWhitespace] Invalid str type (got: ${typeof str})`);
|
|
167
|
-
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return DomUtility.testRegExp(DomUtility.nonSpaceRe, str) === false;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
public static isWhitespaceText(node: Node): boolean {
|
|
175
|
-
return node.nodeType === NODE_TYPE.TEXT_NODE && DomUtility.isWhitespace(node.nodeValue);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
public static textContainsOnlyWhiteSpaces(str: string): boolean {
|
|
179
|
-
return (/^\s*$/).test(str);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
public static hasNonWhitespacesContent(element: HTMLElement): boolean {
|
|
183
|
-
const text: string | null = element.textContent;
|
|
184
|
-
|
|
185
|
-
return typeof text === 'string' ? this.textContainsOnlyWhiteSpaces(text) : false;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
public static isEmptyElement(element: Node): boolean {
|
|
189
|
-
const excludeCommentNodes = (node: ChildNode): boolean => {
|
|
190
|
-
return node.nodeType !== NODE_TYPE.COMMENT_NODE;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const nonCommentChildNodes: ChildNode[] = Array.from(element.childNodes).filter(excludeCommentNodes);
|
|
194
|
-
|
|
195
|
-
return nonCommentChildNodes.length === 0;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
public static getParentElement(element: HTMLElement, nodeName: string): HTMLElement | null {
|
|
199
|
-
let parent: HTMLElement | null = element;
|
|
200
|
-
|
|
201
|
-
if (typeof nodeName !== 'string') {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
while (parent && parent.nodeName) {
|
|
206
|
-
if (parent.nodeName && parent.nodeName.toLowerCase() === nodeName) {
|
|
207
|
-
return parent;
|
|
208
|
-
}
|
|
209
|
-
parent = parent.parentElement;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
public static getHighestZindex(): number {
|
|
216
|
-
const elms: HTMLCollectionOf<Element> = document.getElementsByTagName('*');
|
|
217
|
-
const len: number = elms.length;
|
|
218
|
-
const zIndexes: number[] = [];
|
|
219
|
-
let zIndex: string | null;
|
|
220
|
-
|
|
221
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
222
|
-
zIndex = Css.getStyle(elms[i] as HTMLElement, 'z-index');
|
|
223
|
-
|
|
224
|
-
if (zIndex !== null && zIndex !== 'auto') {
|
|
225
|
-
zIndexes.push(Number(zIndex));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (zIndexes.length === 0) {
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return Math.max(...zIndexes);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
public static contains(parentNode: Element, childNode: Element): boolean {
|
|
237
|
-
|
|
238
|
-
if (ObjectUtility.isHtmlElement(parentNode) === false) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (ObjectUtility.isHostMethod(parentNode, 'contains')) {
|
|
243
|
-
return parentNode.contains(childNode);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return Boolean(parentNode.compareDocumentPosition(childNode) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
public static querySelectorAll(selector: string, context?: any): HTMLElement[] {
|
|
250
|
-
let result: HTMLElement[] = context ? context.querySelectorAll(selector) : document.querySelectorAll(selector);
|
|
251
|
-
|
|
252
|
-
if (result) {
|
|
253
|
-
result = Array.from(result);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return result;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Note: proxiedNode is used by CloudFlare Rocket Loader
|
|
261
|
-
* See https://support.cloudflare.com/hc/en-us/articles/200168056-What-does-Rocket-Loader-do-
|
|
262
|
-
*/
|
|
263
|
-
|
|
264
|
-
public static querySelectorAllExclude(selector: string, context?: Context | null, excludeContainers?: HTMLElement | null | undefined | (HTMLElement | null)[], excludeElements?: HTMLElement | null | undefined | HTMLElement[]): Element[] | null {
|
|
265
|
-
const queryResults: NodeListOf<Element> = context ? context.querySelectorAll(selector) : document.querySelectorAll(selector);
|
|
266
|
-
|
|
267
|
-
let _excludeContainers: (HTMLElement | null)[] = [];
|
|
268
|
-
|
|
269
|
-
if (Array.isArray(excludeContainers)) {
|
|
270
|
-
_excludeContainers = excludeContainers;
|
|
271
|
-
} else if (excludeContainers) {
|
|
272
|
-
_excludeContainers = [excludeContainers];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let _excludeElements: HTMLElement[] = [];
|
|
276
|
-
|
|
277
|
-
if (Array.isArray(excludeElements)) {
|
|
278
|
-
_excludeElements = excludeElements;
|
|
279
|
-
} else if (excludeElements) {
|
|
280
|
-
_excludeElements = [excludeElements];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let result: NodeListOf<Element> | Element[] = Array.from(queryResults);
|
|
284
|
-
|
|
285
|
-
if (queryResults.length === 0) {
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const excludedElements = (node: Element): boolean => {
|
|
290
|
-
const foundedEl: HTMLElement = (node as any).proxiedNode || node;
|
|
291
|
-
|
|
292
|
-
const findElement = (excludedElement: HTMLElement): boolean => {
|
|
293
|
-
return foundedEl === excludedElement;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const index: number = _excludeElements.findIndex(findElement);
|
|
297
|
-
|
|
298
|
-
return index === -1;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const excludeFromContainers = (node: Element): boolean => {
|
|
302
|
-
const foundedEl: HTMLElement = (node as any).proxiedNode || node;
|
|
303
|
-
|
|
304
|
-
const findElement = (containerFromExclude: HTMLElement | null): boolean => {
|
|
305
|
-
if (containerFromExclude === null) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return DomUtility.contains(containerFromExclude, foundedEl);
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const index: number = _excludeContainers.findIndex(findElement);
|
|
313
|
-
|
|
314
|
-
return index === -1;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const excludeContainersItself = (node: Element): boolean => {
|
|
318
|
-
const foundedEl: HTMLElement = (node as any).proxiedNode || node;
|
|
319
|
-
|
|
320
|
-
const findElement = (containerFromExclude: HTMLElement | null): boolean => {
|
|
321
|
-
return foundedEl === containerFromExclude;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const index: number = _excludeContainers.findIndex(findElement);
|
|
325
|
-
|
|
326
|
-
return index === -1;
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
result = result.filter(excludeFromContainers).filter(excludeContainersItself)
|
|
330
|
-
.filter(excludedElements);
|
|
331
|
-
|
|
332
|
-
return result;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public static getOuterDimensions(el: HTMLElement): { height: number; width: number } {
|
|
336
|
-
const dimensions: { height: number; width: number } = {
|
|
337
|
-
height: 0,
|
|
338
|
-
width: 0
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
if (document.documentElement && typeof document.documentElement.offsetWidth === 'number') {
|
|
342
|
-
dimensions.height = el.offsetHeight;
|
|
343
|
-
dimensions.width = el.offsetWidth;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return dimensions;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
public static getInnerDimensions(el: HTMLElement): { height: number; width: number } {
|
|
350
|
-
const dimensions: { height: number; width: number } = {
|
|
351
|
-
height: 0,
|
|
352
|
-
width: 0
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
if (document.documentElement && typeof document.documentElement.clientWidth === 'number') {
|
|
356
|
-
dimensions.height = el.clientHeight;
|
|
357
|
-
dimensions.width = el.clientWidth;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return dimensions;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
public static createCSS(content: string, id?: string, media?: string): HTMLStyleElement {
|
|
364
|
-
if (content === null) {
|
|
365
|
-
throw new Error(`[DomUtility.createCSS] passed content is not a string. Is type ${typeof content}`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const head: HTMLHeadElement = document.head;
|
|
369
|
-
const style: HTMLStyleElement = document.createElement('style');
|
|
370
|
-
|
|
371
|
-
if (typeof id === 'string') {
|
|
372
|
-
style.id = id;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (typeof media === 'string' && media.length > 0) {
|
|
376
|
-
style.setAttribute('media', media);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (typeof style['styleSheet' as keyof typeof style] === 'object') {
|
|
380
|
-
(style['styleSheet' as keyof typeof style] as unknown as CSSRule).cssText = content;
|
|
381
|
-
} else {
|
|
382
|
-
style.appendChild(document.createTextNode(content));
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
head.appendChild(style);
|
|
386
|
-
|
|
387
|
-
return style;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
public static getInnerHTML(element: Element): string {
|
|
391
|
-
return element.innerHTML;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
public static getOuterHTML(element: Node): string {
|
|
395
|
-
const clone: Node = element.cloneNode(false);
|
|
396
|
-
|
|
397
|
-
return (clone as Element).outerHTML;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
public static getNodeHTML(element: Element): string {
|
|
401
|
-
return element.outerHTML;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
public static getEscapedNodeHTML(element: Element): string {
|
|
405
|
-
return TextUtility.escape(DomUtility.getNodeHTML(element));
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
public static getEscapedInnerHTML(element: Element): string {
|
|
409
|
-
return TextUtility.escape(DomUtility.getInnerHTML(element));
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
public static getEscapedOuterHTML(element: Element | null): string {
|
|
413
|
-
if (element) {
|
|
414
|
-
return TextUtility.escape(DomUtility.getOuterHTML(element));
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return '';
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
public static getEscapedOuterTruncatedHTML(element: Context | null): string {
|
|
421
|
-
if (element === null || ('innerHTML' in element === false) && ('outerHTML' in element === false)) {
|
|
422
|
-
return '';
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const tags: string[] = DomUtility.getOuterHTML(element).split('>');
|
|
426
|
-
let html: string = '';
|
|
427
|
-
|
|
428
|
-
if (tags.length === 3) {
|
|
429
|
-
// <tag></tag>
|
|
430
|
-
html = `${tags[0]}>${TextUtility.truncateInTheMiddle((element as Element).innerHTML)}${tags[1]}>`;
|
|
431
|
-
} else if (tags.length === 2) {
|
|
432
|
-
// <tag/>
|
|
433
|
-
html = TextUtility.truncateInTheMiddle((element as Element).outerHTML);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return TextUtility.escape(html);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
public static nodesToText(node: Node): string {
|
|
440
|
-
if (node.childNodes.length > 0) {
|
|
441
|
-
return DomUtility.getTextFromNodes(node.childNodes);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return DomUtility.getTextFromNode(node);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
public static getNodeWithTextContent(node: Node, limitTextContent?: number, toUpperCase?: boolean): string {
|
|
448
|
-
const clone: Node = node.cloneNode(false);
|
|
449
|
-
let innerText: string | null = node.textContent;
|
|
450
|
-
|
|
451
|
-
if (innerText === null) {
|
|
452
|
-
console.warn(`[DomUtility.getNodeWithTextContent] node.textContent got null value`, node);
|
|
453
|
-
|
|
454
|
-
return '';
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (typeof limitTextContent === 'number') {
|
|
458
|
-
innerText = TextUtility.truncateWords(innerText, limitTextContent);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (typeof toUpperCase === 'boolean') {
|
|
462
|
-
innerText = innerText.toUpperCase();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
clone.textContent = innerText;
|
|
466
|
-
|
|
467
|
-
return TextUtility.escape((clone as Element).outerHTML);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
public static empty(node: Node): Node {
|
|
471
|
-
while (node.firstChild) {
|
|
472
|
-
node.removeChild(node.firstChild);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return node;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
public static remove(node: Node): void {
|
|
479
|
-
if (node && node.parentNode) {
|
|
480
|
-
node.parentNode.removeChild(node);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
public static removeScript(url: string): void {
|
|
485
|
-
const scripts: HTMLCollectionOf<HTMLScriptElement> = document.scripts;
|
|
486
|
-
const len: number = scripts.length;
|
|
487
|
-
|
|
488
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
489
|
-
const script: HTMLScriptElement = scripts[i];
|
|
490
|
-
|
|
491
|
-
if (script.src === url && script.parentNode !== null) {
|
|
492
|
-
script.parentNode.removeChild(scripts[i]);
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Note about XPath: https://www.bennadel.com/blog/2142-using-and-expressions-in-xpath-xml-search-directives-in-coldfusion.htm
|
|
499
|
-
public static getXPath(el: Context): string {
|
|
500
|
-
let element: Element | Node & ParentNode = el;
|
|
501
|
-
let parent: Element | Node & ParentNode | null;
|
|
502
|
-
let sames: Node[];
|
|
503
|
-
let elementType: number;
|
|
504
|
-
let result = '';
|
|
505
|
-
|
|
506
|
-
const filterNode = (_node: Node): void => {
|
|
507
|
-
if (_node.nodeName === element.nodeName) {
|
|
508
|
-
sames.push(_node);
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
if (element instanceof Node === false) {
|
|
513
|
-
return result;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
parent = el.parentNode;
|
|
517
|
-
|
|
518
|
-
while (parent !== null) {
|
|
519
|
-
elementType = element.nodeType;
|
|
520
|
-
sames = [];
|
|
521
|
-
parent.childNodes.forEach(filterNode);
|
|
522
|
-
|
|
523
|
-
if (elementType === NODE_TYPE.ELEMENT_NODE) {
|
|
524
|
-
|
|
525
|
-
const nodeName: string = element.nodeName.toLowerCase();
|
|
526
|
-
const name: string = nodeName === 'svg' ? `*[name()='${nodeName}']` : nodeName;
|
|
527
|
-
|
|
528
|
-
result = `/${name}${sames.length > 1 ? `[${[].indexOf.call(sames, element as never) + 1}]` : ''}${result}`;
|
|
529
|
-
} else if (elementType === NODE_TYPE.TEXT_NODE) {
|
|
530
|
-
result = `/text()${result}`;
|
|
531
|
-
} else if (elementType === NODE_TYPE.ATTRIBUTE_NODE) {
|
|
532
|
-
result = `/@${element.nodeName.toLowerCase()}${result}`;
|
|
533
|
-
} else if (elementType === NODE_TYPE.COMMENT_NODE) {
|
|
534
|
-
result = `/comment()${result}`;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
element = parent;
|
|
538
|
-
parent = element.parentNode;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return `./${result}`;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
public static setScrollPositionToElement(element: Element): boolean {
|
|
545
|
-
if (ObjectUtility.isHostMethod(element, 'scrollIntoView')) {
|
|
546
|
-
element.scrollIntoView();
|
|
547
|
-
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
public static firstElementChild(context: Element): Element | null {
|
|
555
|
-
return context.firstElementChild;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
public static isVisibleForAssistiveTechnologies(el: Element): boolean {
|
|
559
|
-
let ariaLabelledByDestination: HTMLElement | null;
|
|
560
|
-
|
|
561
|
-
if (ObjectUtility.isHtmlElement(el) === false) {
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const clientRect: ClientRect | DOMRect = el.getBoundingClientRect();
|
|
566
|
-
const ariaHidden: string | null = el.getAttribute('aria-hidden');
|
|
567
|
-
const ariaLabel: string | null = el.getAttribute('aria-label');
|
|
568
|
-
const ariaLabelledBy: string | null = el.getAttribute('aria-labelledby');
|
|
569
|
-
const styles: CSSStyleDeclaration | null = Css.getComputedStyle(el);
|
|
570
|
-
|
|
571
|
-
if (styles === null) {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (styles.display === 'none' || styles.visibility === 'hidden') {
|
|
576
|
-
return false;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (ariaHidden && ariaHidden === 'true') {
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (ariaLabel && ariaLabel.length > 0) {
|
|
584
|
-
return true;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (ariaLabelledBy) {
|
|
588
|
-
ariaLabelledByDestination = document.getElementById(ariaLabelledBy);
|
|
589
|
-
|
|
590
|
-
if (ariaLabelledByDestination === null) {
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return clientRect.width >= 1 || clientRect.height >= 1;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
public static getPageSize(): { height: number; width: number } {
|
|
599
|
-
const body: HTMLBodyElement = DomUtility.getBodyElement() as HTMLBodyElement;
|
|
600
|
-
const root: HTMLElement = DomUtility.getRootElement();
|
|
601
|
-
const pageHeight: number = Math.max(body.scrollHeight, body.offsetHeight, root.clientHeight, root.scrollHeight, root.offsetHeight);
|
|
602
|
-
const pageWidth: number = Math.max(body.scrollWidth, body.offsetWidth, root.clientWidth, root.scrollWidth, root.offsetWidth);
|
|
603
|
-
|
|
604
|
-
return {
|
|
605
|
-
height: pageHeight,
|
|
606
|
-
width: pageWidth
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
public static getElementAttribute(element: Element, attribute: string): null | Attr {
|
|
611
|
-
return element.attributes.getNamedItem(attribute);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
public static isRangeOffPage(range: Range): boolean {
|
|
615
|
-
const rect: DOMRect = range.getBoundingClientRect();
|
|
616
|
-
const pageSize: { height: number; width: number } = DomUtility.getPageSize();
|
|
617
|
-
|
|
618
|
-
return ((rect.x + rect.width) < 0 || (rect.y + rect.height) < 0 || (rect.x > pageSize.width || rect.y > pageSize.height));
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
public static isElementOffPage(element: Element): boolean {
|
|
622
|
-
const rect: DOMRect = element.getBoundingClientRect();
|
|
623
|
-
const pageSize: { height: number; width: number } = DomUtility.getPageSize();
|
|
624
|
-
|
|
625
|
-
return ((rect.x + rect.width) < 0 || (rect.y + rect.height) < 0 || (rect.x > pageSize.width || rect.y > pageSize.height));
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
public static isAnyPartOfElementRenderedOnPage(element: Element): boolean {
|
|
629
|
-
const rect: ClientRect | DOMRect = element.getBoundingClientRect();
|
|
630
|
-
const pageSize: { height: number; width: number } = DomUtility.getPageSize();
|
|
631
|
-
|
|
632
|
-
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
|
|
633
|
-
const verticalInView: boolean = (rect.top <= pageSize.height) && ((rect.top + rect.height) >= 0);
|
|
634
|
-
const horizontalInView: boolean = (rect.left <= pageSize.width) && ((rect.left + rect.width) >= 0);
|
|
635
|
-
|
|
636
|
-
return (verticalInView && horizontalInView);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
public static isElementInViewport(element: Element): boolean {
|
|
640
|
-
const clientRect: ClientRect | DOMRect = element.getBoundingClientRect();
|
|
641
|
-
|
|
642
|
-
/*
|
|
643
|
-
* offsetParent returns null when the element has style.display set to "none"
|
|
644
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
|
645
|
-
* element.offsetParent
|
|
646
|
-
*/
|
|
647
|
-
|
|
648
|
-
return (
|
|
649
|
-
clientRect.top >= 0 &&
|
|
650
|
-
clientRect.left >= 0 &&
|
|
651
|
-
clientRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
652
|
-
clientRect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
public static isElementHidden(element: Element): boolean {
|
|
657
|
-
if (!(element instanceof (element.ownerDocument.defaultView as any).HTMLElement)) {
|
|
658
|
-
return false;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const style: CSSStyleDeclaration = window.getComputedStyle(element, null);
|
|
662
|
-
|
|
663
|
-
return style.display === 'none' || style.visibility === 'collapse' || style.visibility === 'hidden' || style.opacity === '0' || (element as HTMLElement).offsetHeight === 0;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
public static isElementVisible(element: Element | HTMLElement): boolean {
|
|
667
|
-
if (ObjectUtility.isHtmlElement(element) === false) {
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const rect: ClientRect | DOMRect = element.getBoundingClientRect();
|
|
672
|
-
const style: CSSStyleDeclaration = window.getComputedStyle(element, null);
|
|
673
|
-
|
|
674
|
-
if (typeof (element as HTMLElement).hidden === 'boolean' && (element as HTMLElement).hidden === true) {
|
|
675
|
-
return false;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
if (style.display === 'none') {
|
|
679
|
-
return false;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (style.visibility === 'collapse' || style.visibility === 'hidden') {
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (!isNaN(parseFloat(style.opacity)) && Number(style.opacity) < 0.1) {
|
|
687
|
-
return false;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
let elementSize: number = (element as HTMLElement).offsetWidth + (element as HTMLElement).offsetHeight;
|
|
691
|
-
|
|
692
|
-
if (typeof rect === 'object' && typeof rect.height === 'number' && typeof rect.width === 'number') {
|
|
693
|
-
elementSize += rect.height + rect.width;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (elementSize === 0) {
|
|
697
|
-
return false;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if (typeof rect === 'object' && typeof rect.height === 'number' && typeof rect.width === 'number' && rect.width === 1 && rect.height === 1 && style.overflow === 'hidden') {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if (DomUtility.isAnyPartOfElementRenderedOnPage(element) === false) {
|
|
705
|
-
return false;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
return true;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
public static getRootElement(): HTMLElement {
|
|
712
|
-
return document.documentElement || document.getElementsByTagName('html')[0];
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
public static isHiddenByParent(element: Element): boolean {
|
|
716
|
-
let result: boolean = false;
|
|
717
|
-
const root: HTMLElement = DomUtility.getRootElement();
|
|
718
|
-
let parent: Element | null = element;
|
|
719
|
-
|
|
720
|
-
while (parent !== root) {
|
|
721
|
-
if (parent && DomUtility.isElementVisible(parent) === false) {
|
|
722
|
-
result = true;
|
|
723
|
-
|
|
724
|
-
break;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (parent === null || parent.parentElement === null) {
|
|
728
|
-
break;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
parent = parent.parentElement;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
return result;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
public static isHiddenForAT(element: Element): boolean {
|
|
738
|
-
let result: boolean = false;
|
|
739
|
-
const root: HTMLElement = DomUtility.getRootElement();
|
|
740
|
-
let parent: Element | null = element;
|
|
741
|
-
let ariaHidden: string | null = null;
|
|
742
|
-
|
|
743
|
-
while (parent !== root) {
|
|
744
|
-
if (parent === null || parent.parentElement === null) {
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
ariaHidden = parent.getAttribute('aria-hidden');
|
|
749
|
-
|
|
750
|
-
if (parent && typeof ariaHidden === 'string' && ariaHidden === 'true') {
|
|
751
|
-
result = true;
|
|
752
|
-
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
parent = parent.parentElement;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
return result;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
public static getTextFromDescendantContent(node: Element): string {
|
|
763
|
-
const onlyTextNodes = (previousValue: string, currentValue: ChildNode, _currentIndex: number, _array: ChildNode[]): string => {
|
|
764
|
-
if (currentValue.nodeType === NODE_TYPE.TEXT_NODE) {
|
|
765
|
-
// eslint-disable-next-line no-param-reassign
|
|
766
|
-
previousValue += (currentValue as CharacterData).data;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
return previousValue;
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
return Array.from(node.childNodes).reduce(onlyTextNodes, '');
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* Determine if element has any direct text descendant.
|
|
777
|
-
*
|
|
778
|
-
* @static
|
|
779
|
-
* @param {Node} element
|
|
780
|
-
* @returns {boolean} true if element has direct text descendant; false otherwise
|
|
781
|
-
* @memberof DomUtility
|
|
782
|
-
*/
|
|
783
|
-
|
|
784
|
-
public static hasDirectTextDescendant(element: HTMLElement): boolean {
|
|
785
|
-
const childs: NodeListOf<ChildNode> = element.childNodes;
|
|
786
|
-
const len: number = childs.length;
|
|
787
|
-
let result: boolean = false;
|
|
788
|
-
|
|
789
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
790
|
-
if (childs[i].nodeType === NODE_TYPE.TEXT_NODE) {
|
|
791
|
-
result = true;
|
|
792
|
-
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return result;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
public static isElementDisabled(element: Element): boolean {
|
|
801
|
-
let parent: (Node & ParentNode) | null = element;
|
|
802
|
-
|
|
803
|
-
if (element.matches('[aria-disabled=true], [aria-disabled=true] *')) {
|
|
804
|
-
return true;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (!DomUtility.isNativelyDisableable(element)) {
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
while (parent && parent.nodeName) {
|
|
812
|
-
if (DomUtility.isNativelyDisableable(parent) && (parent as Element).hasAttribute('disabled')) {
|
|
813
|
-
return true;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
if (parent === null || parent.parentNode === null) {
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
parent = parent.parentNode;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
public static canSetFocus(element: Element): boolean {
|
|
827
|
-
return ObjectUtility.isHostMethod(element, 'focus');
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
public static isFocusableElement(element: HTMLOrSVGElement): boolean {
|
|
831
|
-
const originalFocus: Element | null = document.activeElement;
|
|
832
|
-
let result: boolean = false;
|
|
833
|
-
|
|
834
|
-
if (originalFocus === null) {
|
|
835
|
-
return result;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (typeof element.focus === 'function') {
|
|
839
|
-
element.focus();
|
|
840
|
-
result = element === document.activeElement as unknown as HTMLOrSVGElement;
|
|
841
|
-
|
|
842
|
-
(originalFocus as unknown as HTMLOrSVGElement).focus();
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
return result;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
public static toJSON(node: Element | ChildNode): string {
|
|
849
|
-
const obj: any = {
|
|
850
|
-
nodeType: node.nodeType
|
|
851
|
-
};
|
|
852
|
-
const attrs: NamedNodeMap = (node as Element).attributes;
|
|
853
|
-
let attr: Attr;
|
|
854
|
-
let len: number;
|
|
855
|
-
|
|
856
|
-
if (node.nodeName) {
|
|
857
|
-
obj.nodeName = node.nodeName.toLowerCase();
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
if (node.nodeValue) {
|
|
861
|
-
obj.nodeValue = node.nodeValue;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
if (attrs) {
|
|
865
|
-
len = attrs.length;
|
|
866
|
-
obj.attributes = [len];
|
|
867
|
-
|
|
868
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
869
|
-
attr = attrs[i];
|
|
870
|
-
obj.attributes[i] = [attr.nodeName, attr.value];
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const childNodes: NodeListOf<ChildNode> = node.childNodes;
|
|
875
|
-
|
|
876
|
-
if (childNodes.length > 0) {
|
|
877
|
-
len = childNodes.length;
|
|
878
|
-
obj.childNodes = new Array(len);
|
|
879
|
-
|
|
880
|
-
for (let i: number = 0; i < len; i += 1) {
|
|
881
|
-
obj.childNodes[i] = DomUtility.toJSON(childNodes[i]);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
return obj;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
public static insertAfter(newElement: Element, targetElement: Element): void {
|
|
889
|
-
const parent: (Node & ParentNode) | null = targetElement.parentNode;
|
|
890
|
-
|
|
891
|
-
if (parent === null) {
|
|
892
|
-
console.warn('[DomUtility.insertAfter] Unable to insert element after unavailable targetElement', targetElement);
|
|
893
|
-
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
if (parent.lastChild === targetElement) {
|
|
898
|
-
parent.appendChild(newElement);
|
|
899
|
-
} else {
|
|
900
|
-
parent.insertBefore(newElement, targetElement.nextSibling);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
public static getIFrameDocument(frame: HTMLIFrameElement): Document | null {
|
|
905
|
-
if (ObjectUtility.isHtmlElement(frame) === false) {
|
|
906
|
-
return null;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
if (ObjectUtility.isHostObjectProperty(frame, 'contentWindow')) {
|
|
910
|
-
return frame.contentWindow ? frame.contentWindow.document : null;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
if (ObjectUtility.isHostObjectProperty(frame, 'contentDocument')) {
|
|
914
|
-
return frame.contentDocument;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
return null;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
public static htmlDecode(input: string): string | null {
|
|
921
|
-
const e: HTMLTextAreaElement = document.createElement('textarea');
|
|
922
|
-
|
|
923
|
-
e.innerHTML = input;
|
|
924
|
-
|
|
925
|
-
return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
public static ready(): Promise<void> {
|
|
929
|
-
const checkReadyState = (resolve: Function): void => {
|
|
930
|
-
const onReady = (): void => {
|
|
931
|
-
document.removeEventListener('DOMContentLoaded', onReady, true);
|
|
932
|
-
resolve();
|
|
933
|
-
};
|
|
934
|
-
|
|
935
|
-
if (document.readyState !== undefined && document.readyState === 'complete') {
|
|
936
|
-
resolve();
|
|
937
|
-
} else {
|
|
938
|
-
document.addEventListener('DOMContentLoaded', onReady, true);
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
return new Promise(checkReadyState);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
public static getElementFromCssSelectorOrXpath(xPathOrCssSelector: string): IContextElement {
|
|
946
|
-
const result: IContextElement = {
|
|
947
|
-
element: null,
|
|
948
|
-
error: null
|
|
949
|
-
};
|
|
950
|
-
|
|
951
|
-
if (xPathOrCssSelector.trim().length === 0) {
|
|
952
|
-
return result;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
try {
|
|
956
|
-
result.element = document.querySelector(xPathOrCssSelector);
|
|
957
|
-
} catch (e) {
|
|
958
|
-
result.element = null;
|
|
959
|
-
result.error = e;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (ObjectUtility.isHtmlElement(result.element)) {
|
|
963
|
-
return result;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
try {
|
|
967
|
-
const xPathResult: XPathResult = document.evaluate(xPathOrCssSelector, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
|
968
|
-
let node: Node | null = xPathResult.iterateNext();
|
|
969
|
-
const nodes: Node[] = [];
|
|
970
|
-
|
|
971
|
-
while (node) {
|
|
972
|
-
nodes.push(node);
|
|
973
|
-
node = xPathResult.iterateNext();
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
if (nodes.length > 0 && ObjectUtility.isHtmlElement(nodes[0])) {
|
|
977
|
-
result.element = nodes[0] as Element;
|
|
978
|
-
result.error = null;
|
|
979
|
-
}
|
|
980
|
-
} catch (e) {
|
|
981
|
-
result.element = null;
|
|
982
|
-
result.error = e;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return result;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
public static hasElementSemiOpacity(element: Element, styles: CSSStyleDeclaration | null = null): boolean {
|
|
989
|
-
let elementStyles: CSSStyleDeclaration | null = styles;
|
|
990
|
-
|
|
991
|
-
if (elementStyles === null) {
|
|
992
|
-
elementStyles = Css.getComputedStyle(element);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
if (elementStyles === null) {
|
|
996
|
-
return false;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
return elementStyles.opacity.length > 0 && Number(elementStyles.opacity) < 1;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
public static hasElementSemiTransparentBackground(element: Element, styles: CSSStyleDeclaration | null = null): boolean {
|
|
1003
|
-
let elementStyles: CSSStyleDeclaration | null = styles;
|
|
1004
|
-
|
|
1005
|
-
if (elementStyles === null) {
|
|
1006
|
-
elementStyles = Css.getComputedStyle(element);
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (elementStyles === null) {
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
const bgColor: TinyColor = new TinyColor(elementStyles.backgroundColor);
|
|
1014
|
-
|
|
1015
|
-
return bgColor.getAlpha() > 0 && bgColor.getAlpha() < 1;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
public static getHtmlInfo(root: Node, skipNodeNames: string[] = ['script', 'style']): IHtmlInfo {
|
|
1019
|
-
let htmlSize: number = 0;
|
|
1020
|
-
let nodesNum: number = 0;
|
|
1021
|
-
|
|
1022
|
-
const nodeIterator: NodeIterator = document.createNodeIterator(
|
|
1023
|
-
root,
|
|
1024
|
-
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
|
|
1025
|
-
{
|
|
1026
|
-
acceptNode(node: Node): number {
|
|
1027
|
-
return node.nodeType === Node.ELEMENT_NODE && skipNodeNames.includes(node.nodeName.toLowerCase()) || node.nodeType === Node.TEXT_NODE && node.parentNode && skipNodeNames.includes(node.parentNode.nodeName.toLowerCase()) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
);
|
|
1031
|
-
|
|
1032
|
-
let currentNode: Node | null = nodeIterator.nextNode();
|
|
1033
|
-
|
|
1034
|
-
while (currentNode) {
|
|
1035
|
-
if (currentNode.nodeType === Node.ELEMENT_NODE) {
|
|
1036
|
-
htmlSize += (currentNode.cloneNode(false) as Element).outerHTML.length;
|
|
1037
|
-
nodesNum += 1;
|
|
1038
|
-
} else if (currentNode.nodeType === Node.TEXT_NODE && typeof currentNode.textContent === 'string') {
|
|
1039
|
-
htmlSize += currentNode.textContent.length;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
currentNode = nodeIterator.nextNode();
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
return {
|
|
1046
|
-
htmlSize: htmlSize,
|
|
1047
|
-
nodesNum: nodesNum
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
}
|