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