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