mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 08:22:24 +00:00
The shared engine treats every panel as an ARIA menu and preventDefault'd single-char + arrow keys, so typing in a panel with inputs (the zap comment/amount fields) was swallowed — "you can't type in the popup". handleMenuKeydown now ignores editable targets. Also relocate a pre-existing in-host panel (the in-modal zap dialog) into the body-level portal on open so position:fixed is viewport-relative instead of anchoring to the modal edge, restoring it on close. Pure DOM helpers extracted to popoverPanelDom.js to keep the engine under its cap. Co-Authored-By: Claude Opus 4.8 <[email protected]>
65 lines
2.4 KiB
JavaScript
65 lines
2.4 KiB
JavaScript
// Pure DOM helpers for the popover engine, extracted to keep popoverEngine.js
|
|
// under its size budget.
|
|
|
|
function isElement(node) {
|
|
return Boolean(node) && typeof node === "object" && node.nodeType === 1;
|
|
}
|
|
|
|
// True when the event target is a form control / editable element. The engine
|
|
// treats panels as ARIA menus (arrow-key nav + single-char typeahead, both
|
|
// preventDefault'd); that must NOT hijack keystrokes when the user is typing in a
|
|
// panel that contains inputs (e.g. the zap comment/amount fields) — otherwise
|
|
// letters and caret keys never reach the field and "you can't type" in the popup.
|
|
export function isEditableTarget(target) {
|
|
if (!isElement(target)) {
|
|
return false;
|
|
}
|
|
const tag = typeof target.tagName === "string" ? target.tagName.toLowerCase() : "";
|
|
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
return true;
|
|
}
|
|
if (target.isContentEditable === true) {
|
|
return true;
|
|
}
|
|
const role =
|
|
typeof target.getAttribute === "function" ? target.getAttribute("role") : "";
|
|
return role === "textbox" || role === "searchbox" || role === "combobox";
|
|
}
|
|
|
|
// Move a panel into the portal (body-level overlay root) for the duration of the
|
|
// open state, so `position: fixed` is viewport-relative rather than resolving
|
|
// against a transformed/contained in-modal ancestor. Fresh render-created panels
|
|
// are already in the portal (no-op); this only relocates pre-existing, app-owned
|
|
// panels. Returns the origin { parent, nextSibling } to restore later, or null.
|
|
export function movePanelIntoPortal(panel, portal) {
|
|
if (!panel || !portal || panel.parentNode === portal) {
|
|
return null;
|
|
}
|
|
const origin = panel.parentNode
|
|
? { parent: panel.parentNode, nextSibling: panel.nextSibling }
|
|
: null;
|
|
portal.appendChild(panel);
|
|
return origin;
|
|
}
|
|
|
|
// Put a relocated app-owned panel back exactly where it came from so the host
|
|
// (e.g. the video modal) keeps an intact DOM and the popover can be re-opened.
|
|
export function restorePanelToOrigin(panel, origin) {
|
|
if (!panel || !origin) {
|
|
return;
|
|
}
|
|
const { parent, nextSibling } = origin;
|
|
if (!parent || typeof parent.insertBefore !== "function") {
|
|
return;
|
|
}
|
|
try {
|
|
if (nextSibling && nextSibling.parentNode === parent) {
|
|
parent.insertBefore(panel, nextSibling);
|
|
} else {
|
|
parent.appendChild(panel);
|
|
}
|
|
} catch (error) {
|
|
// Best-effort: a failed restore must not break close().
|
|
}
|
|
}
|