forked from google/perfetto
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdom_utils.ts
More file actions
156 lines (137 loc) · 4.19 KB
/
dom_utils.ts
File metadata and controls
156 lines (137 loc) · 4.19 KB
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (C) 2023 The Android Open Source Project
//
// 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.
import {Vector2D} from './geom';
export type CSSCursor =
| 'alias'
| 'all-scroll'
| 'auto'
| 'cell'
| 'context-menu'
| 'col-resize'
| 'copy'
| 'crosshair'
| 'default'
| 'e-resize'
| 'ew-resize'
| 'grab'
| 'grabbing'
| 'help'
| 'move'
| 'n-resize'
| 'ne-resize'
| 'nesw-resize'
| 'ns-resize'
| 'nw-resize'
| 'nwse-resize'
| 'no-drop'
| 'none'
| 'not-allowed'
| 'pointer'
| 'progress'
| 'row-resize'
| 's-resize'
| 'se-resize'
| 'sw-resize'
| 'text'
| 'vertical-text'
| 'w-resize'
| 'wait'
| 'zoom-in'
| 'zoom-out';
// Check whether a DOM element contains another, or whether they're the same
export function isOrContains(container: Element, target: Element): boolean {
return container === target || container.contains(target);
}
// Find a DOM element with a given "ref" attribute
export function findRef(root: Element, ref: string): Element | null {
const query = `[ref=${ref}]`;
if (root.matches(query)) {
return root;
} else {
return root.querySelector(query);
}
}
// Safely cast an Element to an HTMLElement.
// Throws if the element is not an HTMLElement.
export function toHTMLElement(el: Element): HTMLElement {
if (!(el instanceof HTMLElement)) {
throw new Error('Element is not an HTMLElement');
}
return el as HTMLElement;
}
// Return true if EventTarget is or is inside an editable element.
// Editable elements incluce: <input type="text">, <textarea>, or elements with
// the |contenteditable| attribute set.
export function elementIsEditable(target: EventTarget | null): boolean {
if (target === null) {
return false;
}
if (!(target instanceof Element)) {
return false;
}
const editable = target.closest('input, textarea, [contenteditable=true]');
if (editable === null) {
return false;
}
if (editable instanceof HTMLInputElement) {
if (['radio', 'checkbox', 'button'].includes(editable.type)) {
return false;
}
}
return true;
}
// Returns the mouse pointer's position relative to |e.currentTarget| for a
// given |MouseEvent|.
// Similar to |offsetX|, |offsetY| but for |currentTarget| rather than |target|.
// If the event has no currentTarget or it is not an element, offsetX & offsetY
// are returned instead.
export function currentTargetOffset(e: MouseEvent): Vector2D {
if (e.currentTarget === e.target) {
return new Vector2D({x: e.offsetX, y: e.offsetY});
}
if (e.currentTarget && e.currentTarget instanceof Element) {
const rect = e.currentTarget.getBoundingClientRect();
const offsetX = e.clientX - rect.left;
const offsetY = e.clientY - rect.top;
return new Vector2D({x: offsetX, y: offsetY});
}
return new Vector2D({x: e.offsetX, y: e.offsetY});
}
// Adds an event listener to a DOM element, returning a disposable to remove it.
export function bindEventListener<K extends keyof HTMLElementEventMap>(
element: EventTarget,
event: K,
handler: (event: HTMLElementEventMap[K]) => void,
options?: AddEventListenerOptions,
): Disposable {
element.addEventListener(event, handler as EventListener, options);
return {
[Symbol.dispose]() {
element.removeEventListener(event, handler as EventListener);
},
};
}
export function ancestorThat(el: Element | null, predicate: (htmlEl: HTMLElement) => boolean): HTMLElement | undefined {
let result: HTMLElement | undefined;
while (!result && el instanceof HTMLElement) {
if (predicate(el)) {
result = el;
}
el = el.parentElement;
}
return result;
}
export function matchesSelector(selector: string): (el: HTMLElement) => boolean {
return (el) => el.matches(selector);
}