-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
domUtils.ts
136 lines (124 loc) · 5.17 KB
/
domUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let browserNameForWorkarounds = '';
export function setBrowserName(name: string) {
browserNameForWorkarounds = name;
}
export function isInsideScope(scope: Node, element: Element | undefined): boolean {
while (element) {
if (scope.contains(element))
return true;
element = enclosingShadowHost(element);
}
return false;
}
export function enclosingElement(node: Node) {
if (node.nodeType === 1 /* Node.ELEMENT_NODE */)
return node as Element;
return node.parentElement ?? undefined;
}
export function parentElementOrShadowHost(element: Element): Element | undefined {
if (element.parentElement)
return element.parentElement;
if (!element.parentNode)
return;
if (element.parentNode.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && (element.parentNode as ShadowRoot).host)
return (element.parentNode as ShadowRoot).host;
}
export function enclosingShadowRootOrDocument(element: Element): Document | ShadowRoot | undefined {
let node: Node = element;
while (node.parentNode)
node = node.parentNode;
if (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ || node.nodeType === 9 /* Node.DOCUMENT_NODE */)
return node as Document | ShadowRoot;
}
function enclosingShadowHost(element: Element): Element | undefined {
while (element.parentElement)
element = element.parentElement;
return parentElementOrShadowHost(element);
}
// Assumption: if scope is provided, element must be inside scope's subtree.
export function closestCrossShadow(element: Element | undefined, css: string, scope?: Document | Element): Element | undefined {
while (element) {
const closest = element.closest(css);
if (scope && closest !== scope && closest?.contains(scope))
return;
if (closest)
return closest;
element = enclosingShadowHost(element);
}
}
export function getElementComputedStyle(element: Element, pseudo?: string): CSSStyleDeclaration | undefined {
return element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element, pseudo) : undefined;
}
export function isElementStyleVisibilityVisible(element: Element, style?: CSSStyleDeclaration): boolean {
style = style ?? getElementComputedStyle(element);
if (!style)
return true;
// Element.checkVisibility checks for content-visibility and also looks at
// styles up the flat tree including user-agent ShadowRoots, such as the
// details element for example.
// All the browser implement it, but WebKit has a bug which prevents us from using it:
// https://bugs.webkit.org/show_bug.cgi?id=264733
// @ts-ignore
if (Element.prototype.checkVisibility && browserNameForWorkarounds !== 'webkit') {
if (!element.checkVisibility())
return false;
} else {
// Manual workaround for WebKit that does not have checkVisibility.
const detailsOrSummary = element.closest('details,summary');
if (detailsOrSummary !== element && detailsOrSummary?.nodeName === 'DETAILS' && !(detailsOrSummary as HTMLDetailsElement).open)
return false;
}
if (style.visibility !== 'visible')
return false;
return true;
}
export function isElementVisible(element: Element): boolean {
// Note: this logic should be similar to waitForDisplayedAtStablePosition() to avoid surprises.
const style = getElementComputedStyle(element);
if (!style)
return true;
if (style.display === 'contents') {
// display:contents is not rendered itself, but its child nodes are.
for (let child = element.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 1 /* Node.ELEMENT_NODE */ && isElementVisible(child as Element))
return true;
if (child.nodeType === 3 /* Node.TEXT_NODE */ && isVisibleTextNode(child as Text))
return true;
}
return false;
}
if (!isElementStyleVisibilityVisible(element, style))
return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
export function isVisibleTextNode(node: Text) {
// https://stackoverflow.com/questions/1461059/is-there-an-equivalent-to-getboundingclientrect-for-text-nodes
const range = node.ownerDocument.createRange();
range.selectNode(node);
const rect = range.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
export function elementSafeTagName(element: Element) {
// Named inputs, e.g. <input name=tagName>, will be exposed as fields on the parent <form>
// and override its properties.
if (element instanceof HTMLFormElement)
return 'FORM';
// Elements from the svg namespace do not have uppercase tagName right away.
return element.tagName.toUpperCase();
}