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 15// Check whether a DOM element contains another, or whether they're the same 16export function isOrContains(container: Element, target: Element): boolean { 17 return container === target || container.contains(target); 18} 19 20// Find a DOM element with a given "ref" attribute 21export function findRef(root: Element, ref: string): Element | null { 22 const query = `[ref=${ref}]`; 23 if (root.matches(query)) { 24 return root; 25 } else { 26 return root.querySelector(query); 27 } 28} 29 30// Safely cast an Element to an HTMLElement. 31// Throws if the element is not an HTMLElement. 32export function toHTMLElement(el: Element): HTMLElement { 33 if (!(el instanceof HTMLElement)) { 34 throw new Error('Element is not an HTMLElement'); 35 } 36 return el as HTMLElement; 37} 38 39// Return true if EventTarget is or is inside an editable element. 40// Editable elements incluce: <input type="text">, <textarea>, or elements with 41// the |contenteditable| attribute set. 42export function elementIsEditable(target: EventTarget | null): boolean { 43 if (target === null) { 44 return false; 45 } 46 47 if (!(target instanceof Element)) { 48 return false; 49 } 50 51 const editable = target.closest('input, textarea, [contenteditable=true]'); 52 53 if (editable === null) { 54 return false; 55 } 56 57 if (editable instanceof HTMLInputElement) { 58 if (['radio', 'checkbox', 'button'].includes(editable.type)) { 59 return false; 60 } 61 } 62 63 return true; 64} 65 66// Returns the mouse pointer's position relative to |e.currentTarget| for a 67// given |MouseEvent|. 68// Similar to |offsetX|, |offsetY| but for |currentTarget| rather than |target|. 69// If the event has no currentTarget or it is not an element, offsetX & offsetY 70// are returned instead. 71export function currentTargetOffset(e: MouseEvent): {x: number; y: number} { 72 if (e.currentTarget === e.target) { 73 return {x: e.offsetX, y: e.offsetY}; 74 } 75 76 if (e.currentTarget && e.currentTarget instanceof Element) { 77 const rect = e.currentTarget.getBoundingClientRect(); 78 const offsetX = e.clientX - rect.left; 79 const offsetY = e.clientY - rect.top; 80 return {x: offsetX, y: offsetY}; 81 } 82 83 return {x: e.offsetX, y: e.offsetY}; 84} 85