/** * Which layer a teleported popup has to sit on to be above whatever opened it. * * ⚠⚠ THE BUG THIS EXISTS FOR. A menu that teleports to `` leaves its * container's stacking context behind — that is the point, it is how a menu * escapes `overflow: hidden` and a transformed ancestor. But it also leaves * behind that container's PLACE in the stack. The explorer's Connections / * API-keys overlay is `.fe-overlay`, `position: fixed; z-index: 130`; the * context menu's backdrop is `z-index: 80`. So a row's "Actions" menu opened * inside that overlay was painted UNDER it — the owner, testing v0.43.0: * "explore kısmında api anahtarlarında ya da nasıl bağlanılar (yani popup * ekranlar) içindeki aksiyon tuşlarının menüsü popup'ın altında kalıyor ve * açılmıyor gibi gözüküyor". It opened every time; nobody could see it. * * ⚠ Why not simply raise `.fe-ctx-backdrop` to a big constant. Because the * stack has deliberate orderings in it that a constant would flatten — the * onboarding tour sits at 96 ABOVE the filter popovers at 90 on purpose, so a * tour step is never hidden behind one. And in an EMBED the number we would * have to beat is not ours at all: the host page decides what its own * containers are worth, and no constant we pick is guaranteed to clear it. * * So the layer is MEASURED from the thing that opened the popup: walk the * opener's ancestors, take the highest z-index any positioned one carries, and * sit one above it — or stay on the base layer when nothing in the chain is * raised at all, which is the ordinary case and leaves every existing ordering * exactly as it was. */ /** The layer transient popups live on when nothing pushes them higher. */ export const POPUP_BASE_Z = 80; /** * The z-index for a popup opened from `openers`. * * Several openers may be passed because the caller usually has more than one * imperfect clue about what was clicked (the focused element, the element under * the pointer); the highest answer wins, and a null is simply ignored. */ export function popupLayer( openers: Array | Element | null | undefined, base: number = POPUP_BASE_Z, ): number { if (typeof window === 'undefined' || typeof document === 'undefined') return base; const list = Array.isArray(openers) ? openers : [openers]; let top = base; for (const opener of list) { let el: Element | null = opener ?? null; /* A cycle is impossible through parentElement, but a shadow host hop is a * jump the loop cannot prove terminates; 200 is far past any real tree. */ for (let guard = 0; el && guard < 200; guard++) { if (el === document.body || el === document.documentElement) break; let z = Number.NaN; let positioned = false; try { const cs = window.getComputedStyle(el); /* ⚠ `|| 'static'`: a DOM that has not resolved the cascade answers * with the empty string rather than the initial value (happy-dom does, * and so does a detached node in some engines). Reading that as "not * static" would honour every inert `z-index` in the tree — the pinned * Actions cell carries one — and shove the menu onto a layer nothing * asked for. */ positioned = (cs.position || 'static') !== 'static'; z = Number.parseInt(cs.zIndex, 10); } catch { /* A detached or cross-document node: nothing to learn, keep climbing. */ } /* ⚠ `position !== static` as well as a number: `z-index` on a static * element is inert — honouring it would push the menu above a dialog * because some unrelated cell in the row carried a leftover number. */ if (positioned && Number.isFinite(z) && z + 1 > top) top = z + 1; el = el.parentElement ?? /* Out the top of a shadow tree, if the host put us in one. */ ((el.getRootNode() as ShadowRoot | null)?.host ?? null); } } return top; } /** * The best guesses at what opened a popup, at the moment it opens. * * `document.activeElement` is the control a click just focused, and the element * under the pointer is what a right-click or a synthetic open landed on. Either * can be wrong on its own — a button that does not take focus, a coordinate on * a border — and both are in the same subtree as the real opener whenever they * are right, which is all `popupLayer` needs. * * ⚠ Call this BEFORE the popup renders. Once the backdrop is in the document it * covers the viewport, and `elementFromPoint` answers with the backdrop. */ export function openerCandidates(x?: number, y?: number): Array { if (typeof document === 'undefined') return []; const out: Array = [document.activeElement]; if (typeof x === 'number' && typeof y === 'number' && document.elementFromPoint) { try { out.push(document.elementFromPoint(x, y)); } catch { /* Out-of-viewport coordinates throw in some engines; the focus clue stands. */ } } return out; }