import type { DOMElement } from './dom.js';
import { ClickEvent } from './events/click-event.js';
import type { EventHandlerProps } from './events/event-handlers.js';
import { nodeCache } from './node-cache.js';
export function hitTest(
node: DOMElement,
col: number,
row: number,
): DOMElement | null {
const rect = nodeCache.get(node);
if (!rect) return null;
if (
col < rect.x ||
col >= rect.x + rect.width ||
row < rect.y ||
row >= rect.y + rect.height
) {
return null;
}
for (let i = node.childNodes.length - 1; i >= 0; i--) {
const child = node.childNodes[i]!;
if (child.nodeName === '#text') continue;
const hit = hitTest(child, col, row);
if (hit) return hit;
}
return node;
}
export function dispatchClick(
root: DOMElement,
col: number,
row: number,
cellIsBlank = false,
): boolean {
let target: DOMElement | undefined = hitTest(root, col, row) ?? undefined;
if (!target) return false;
if (root.focusManager) {
let focusTarget: DOMElement | undefined = target;
while (focusTarget) {
if (typeof focusTarget.attributes['tabIndex'] === 'number') {
root.focusManager.handleClickFocus(focusTarget);
break;
}
focusTarget = focusTarget.parentNode;
}
}
const event = new ClickEvent(col, row, cellIsBlank);
let handled = false;
while (target) {
const handler = target._eventHandlers?.onClick as
| ((event: ClickEvent) => void)
| undefined;
if (handler) {
handled = true;
const rect = nodeCache.get(target);
if (rect) {
event.localCol = col - rect.x;
event.localRow = row - rect.y;
}
handler(event);
if (event.didStopImmediatePropagation()) return true;
}
target = target.parentNode;
}
return handled;
}
export function dispatchHover(
root: DOMElement,
col: number,
row: number,
hovered: Set<DOMElement>,
): void {
const next = new Set<DOMElement>();
let node: DOMElement | undefined = hitTest(root, col, row) ?? undefined;
while (node) {
const h = node._eventHandlers as EventHandlerProps | undefined;
if (h?.onMouseEnter || h?.onMouseLeave) next.add(node);
node = node.parentNode;
}
for (const old of hovered) {
if (!next.has(old)) {
hovered.delete(old);
if (old.parentNode) {
(old._eventHandlers as EventHandlerProps | undefined)?.onMouseLeave?.();
}
}
}
for (const n of next) {
if (!hovered.has(n)) {
hovered.add(n);
(n._eventHandlers as EventHandlerProps | undefined)?.onMouseEnter?.();
}
}
}