xref: /aosp_15_r20/external/perfetto/ui/src/base/dom_utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {Vector2D} from './geom';
16
17// Check whether a DOM element contains another, or whether they're the same
18export function isOrContains(container: Element, target: Element): boolean {
19  return container === target || container.contains(target);
20}
21
22// Find a DOM element with a given "ref" attribute
23export function findRef(root: Element, ref: string): Element | null {
24  const query = `[ref=${ref}]`;
25  if (root.matches(query)) {
26    return root;
27  } else {
28    return root.querySelector(query);
29  }
30}
31
32// Safely cast an Element to an HTMLElement.
33// Throws if the element is not an HTMLElement.
34export function toHTMLElement(el: Element): HTMLElement {
35  if (!(el instanceof HTMLElement)) {
36    throw new Error('Element is not an HTMLElement');
37  }
38  return el as HTMLElement;
39}
40
41// Return true if EventTarget is or is inside an editable element.
42// Editable elements incluce: <input type="text">, <textarea>, or elements with
43// the |contenteditable| attribute set.
44export function elementIsEditable(target: EventTarget | null): boolean {
45  if (target === null) {
46    return false;
47  }
48
49  if (!(target instanceof Element)) {
50    return false;
51  }
52
53  const editable = target.closest('input, textarea, [contenteditable=true]');
54
55  if (editable === null) {
56    return false;
57  }
58
59  if (editable instanceof HTMLInputElement) {
60    if (['radio', 'checkbox', 'button'].includes(editable.type)) {
61      return false;
62    }
63  }
64
65  return true;
66}
67
68// Returns the mouse pointer's position relative to |e.currentTarget| for a
69// given |MouseEvent|.
70// Similar to |offsetX|, |offsetY| but for |currentTarget| rather than |target|.
71// If the event has no currentTarget or it is not an element, offsetX & offsetY
72// are returned instead.
73export function currentTargetOffset(e: MouseEvent): Vector2D {
74  if (e.currentTarget === e.target) {
75    return new Vector2D({x: e.offsetX, y: e.offsetY});
76  }
77
78  if (e.currentTarget && e.currentTarget instanceof Element) {
79    const rect = e.currentTarget.getBoundingClientRect();
80    const offsetX = e.clientX - rect.left;
81    const offsetY = e.clientY - rect.top;
82    return new Vector2D({x: offsetX, y: offsetY});
83  }
84
85  return new Vector2D({x: e.offsetX, y: e.offsetY});
86}
87