| 1 | /// <reference lib="dom" /> |
| 2 | import { pageRegistry } from "./pageSemantic.js"; |
| 3 | |
| 4 | export function pageCancelPicker(): void { |
| 5 | (window as unknown as { __reasonixCancelPicker?: () => void }).__reasonixCancelPicker?.(); |
| 6 | } |
| 7 | |
| 8 | export async function pagePickElement(input: { key: string }): Promise<unknown> { |
| 9 | const registry = pageRegistry(input.key); |
| 10 | const host = window as unknown as { __reasonixCancelPicker?: () => void }; |
| 11 | host.__reasonixCancelPicker?.(); |
| 12 | return new Promise(resolve => { |
| 13 | const overlay = document.createElement("div"); |
| 14 | overlay.style.cssText = "position:fixed;pointer-events:none;z-index:2147483647;border:2px solid #4d8dff;background:rgba(77,141,255,.12);box-sizing:border-box;display:none"; |
| 15 | document.documentElement.append(overlay); |
| 16 | let target: Element | null = null; |
| 17 | let finished = false; |
| 18 | const finish = (result: unknown) => { |
| 19 | if (finished) return; |
| 20 | finished = true; |
| 21 | clearTimeout(timer); overlay.remove(); |
| 22 | window.removeEventListener("pointermove", move, true); |
| 23 | window.removeEventListener("click", click, true); |
| 24 | window.removeEventListener("keydown", key, true); |
| 25 | for (const name of ["pointerdown", "mousedown", "mouseup"]) window.removeEventListener(name, block, true); |
| 26 | if (host.__reasonixCancelPicker === cancel) delete host.__reasonixCancelPicker; |
| 27 | resolve(result); |
| 28 | }; |
| 29 | const cancel = () => finish(null); |
| 30 | const block = (event: Event) => { if (event.isTrusted) { event.preventDefault(); event.stopImmediatePropagation(); } }; |
| 31 | const move = (event: PointerEvent) => { |
| 32 | if (!event.isTrusted) return; |
| 33 | target = event.composedPath().find(node => node instanceof Element && node !== overlay) as Element | undefined ?? null; |
| 34 | if (!target) return; |
| 35 | const box = target.getBoundingClientRect(); |
| 36 | Object.assign(overlay.style, { display: "block", left: `${box.x}px`, top: `${box.y}px`, width: `${box.width}px`, height: `${box.height}px` }); |
| 37 | }; |
| 38 | const click = (event: MouseEvent) => { |
| 39 | if (!event.isTrusted) return; |
| 40 | block(event); |
| 41 | const element = event.composedPath().find(node => node instanceof Element && node !== overlay) as Element | undefined ?? target; |
| 42 | if (!element) return; |
| 43 | const box = element.getBoundingClientRect(), style = getComputedStyle(element); |
| 44 | const sensitive = element instanceof HTMLInputElement && element.type === "password"; |
| 45 | finish({ role: registry.kernel.utils.getAriaRole(element), name: registry.kernel.utils.getElementAccessibleNameText(element, false).slice(0, 160), text: sensitive ? "" : (element.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 300), tag: element.tagName.toLowerCase(), box: { x: box.x, y: box.y, width: box.width, height: box.height }, style: { display: style.display, color: style.color, backgroundColor: style.backgroundColor, fontSize: style.fontSize }, historical: true }); |
| 46 | }; |
| 47 | const key = (event: KeyboardEvent) => { if (event.isTrusted && event.key === "Escape") { block(event); cancel(); } }; |
| 48 | const timer = setTimeout(cancel, 90_000); |
| 49 | host.__reasonixCancelPicker = cancel; |
| 50 | window.addEventListener("pointermove", move, true); |
| 51 | window.addEventListener("click", click, true); |
| 52 | window.addEventListener("keydown", key, true); |
| 53 | for (const name of ["pointerdown", "mousedown", "mouseup"]) window.addEventListener(name, block, true); |
| 54 | }); |
| 55 | } |
| 56 |