{"version":3,"file":"overlayStack.cjs","sources":["../../../../src/components/utils/overlayStack.ts"],"sourcesContent":["// Central registry of currently-open overlays (Popup / Menu / Modal / Overlay)\n// and their parent → child relationships. Several overlay behaviors break\n// without a shared view of the open set because each overlay portals its\n// content to document.body as a sibling, so DOM ancestry alone can't tell that\n// a child popup \"belongs to\" the popup that opened it:\n//\n//  - close-on-click-outside: a click inside a portaled child popup must NOT\n//    count as \"outside\" its parent (otherwise interacting with a submenu\n//    closes the menu).\n//  - background inert: a Modal must inert the page behind it, but NOT the\n//    overlays portaled to body that belong to it (e.g. an in-modal menu).\n//  - focus traversal: a Modal's focus trap needs to see focusables in the\n//    overlays portaled out of it.\n//\n// The registry is module-global (one document = one overlay tree), mirroring\n// escapeStack / scrollLock.\n\nexport interface OverlayStackEntry {\n  /** The overlay's outermost rendered element (its portal root). */\n  el: HTMLElement;\n  /** The parent overlay's element, if this overlay was opened from inside another. */\n  parent: HTMLElement | null;\n}\n\nconst entries: OverlayStackEntry[] = [];\nconst listeners = new Set<() => void>();\n\n/**\n * Subscribe to register/unregister changes. Used by the background-inert pass\n * so an open dialog re-reconciles when a sibling overlay (e.g. a stacked modal\n * or an in-modal popup) opens or closes, and is never left inert.\n * Returns an unsubscribe function.\n */\nexport function subscribeOverlayStack(listener: () => void): () => void {\n  listeners.add(listener);\n  return () => {\n    listeners.delete(listener);\n  };\n}\n\nfunction notifyOverlayStackChange(): void {\n  for (const listener of listeners) listener();\n}\n\n/**\n * Register an open overlay. Returns an unregister function.\n * `parent` is the element of the overlay this one was opened from (or null for\n * a top-level overlay) — typically resolved via {@link findOverlayContaining}\n * on the new overlay's anchor/trigger.\n */\nexport function registerOverlay(el: HTMLElement, parent: HTMLElement | null = null): () => void {\n  entries.push({ el, parent });\n  notifyOverlayStackChange();\n  return () => unregisterOverlay(el);\n}\n\nexport function unregisterOverlay(el: HTMLElement): void {\n  const idx = entries.findIndex((e) => e.el === el);\n  if (idx !== -1) {\n    entries.splice(idx, 1);\n    notifyOverlayStackChange();\n  }\n}\n\n/** Every currently-open overlay root element. Used to exclude them from background inert. */\nexport function getActiveOverlayPortals(): HTMLElement[] {\n  return entries.map((e) => e.el);\n}\n\n/**\n * The innermost registered overlay whose element contains `node`, or null.\n * Used to resolve a new overlay's parent from its anchor/trigger element.\n */\nexport function findOverlayContaining(node: Node | null): HTMLElement | null {\n  if (!node) return null;\n  let best: HTMLElement | null = null;\n  for (const entry of entries) {\n    if (entry.el.contains(node)) {\n      // pick the deepest match (an ancestor overlay contains the inner one)\n      if (!best || best.contains(entry.el)) best = entry.el;\n    }\n  }\n  return best;\n}\n\n/**\n * True if `target` is inside `rootEl` OR inside any overlay that descends from\n * `rootEl` through the registered parent chain. This is the \"family\" test for\n * close-on-click-outside: a click in a child/grandchild popup is inside the\n * family of its ancestor, even though the DOM portals make them body siblings.\n */\nexport function isInOverlayFamily(target: Node, rootEl: HTMLElement): boolean {\n  if (rootEl.contains(target)) return true;\n  for (const entry of entries) {\n    if (!entry.el.contains(target)) continue;\n    // walk this overlay's parent chain looking for rootEl\n    let parent = entry.parent;\n    const seen = new Set<HTMLElement>();\n    while (parent) {\n      if (parent === rootEl) return true;\n      if (seen.has(parent)) break;\n      seen.add(parent);\n      const parentEntry = entries.find((e) => e.el === parent);\n      parent = parentEntry ? parentEntry.parent : null;\n    }\n  }\n  return false;\n}\n\n/** All focusable-relevant descendant overlay roots of `rootEl` (direct + transitive children). */\nexport function getDescendantOverlays(rootEl: HTMLElement): HTMLElement[] {\n  const out: HTMLElement[] = [];\n  for (const entry of entries) {\n    if (entry.el === rootEl) continue;\n    let parent = entry.parent;\n    const seen = new Set<HTMLElement>();\n    while (parent) {\n      if (parent === rootEl) {\n        out.push(entry.el);\n        break;\n      }\n      if (seen.has(parent)) break;\n      seen.add(parent);\n      const parentEntry = entries.find((e) => e.el === parent);\n      parent = parentEntry ? parentEntry.parent : null;\n    }\n  }\n  return out;\n}\n\n/** test cleanup */\nexport function resetOverlayStack(): void {\n  entries.length = 0;\n}\n"],"names":[],"mappings":";;AAwBA,MAAM,UAA+B,EAAC;AACtC,MAAM,SAAA,uBAAgB,GAAA,EAAgB;AAQ/B,SAAS,sBAAsB,QAAA,EAAkC;AACtE,EAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,EAAA,OAAO,MAAM;AACX,IAAA,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,EAC3B,CAAA;AACF;AAEA,SAAS,wBAAA,GAAiC;AACxC,EAAA,KAAA,MAAW,QAAA,IAAY,WAAW,QAAA,EAAS;AAC7C;AAQO,SAAS,eAAA,CAAgB,EAAA,EAAiB,MAAA,GAA6B,IAAA,EAAkB;AAC9F,EAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,EAAA,EAAI,MAAA,EAAQ,CAAA;AAC3B,EAAA,wBAAA,EAAyB;AACzB,EAAA,OAAO,MAAM,kBAAkB,EAAE,CAAA;AACnC;AAEO,SAAS,kBAAkB,EAAA,EAAuB;AACvD,EAAA,MAAM,MAAM,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAChD,EAAA,IAAI,QAAQ,EAAA,EAAI;AACd,IAAA,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAA;AACrB,IAAA,wBAAA,EAAyB;AAAA,EAC3B;AACF;AAGO,SAAS,uBAAA,GAAyC;AACvD,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,EAAE,CAAA;AAChC;AAwBO,SAAS,iBAAA,CAAkB,QAAc,MAAA,EAA8B;AAC5E,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,IAAA;AACpC,EAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,IAAA,IAAI,CAAC,KAAA,CAAM,EAAA,CAAG,QAAA,CAAS,MAAM,CAAA,EAAG;AAEhC,IAAA,IAAI,SAAS,KAAA,CAAM,MAAA;AACnB,IAAA,MAAM,IAAA,uBAAW,GAAA,EAAiB;AAClC,IAAA,OAAO,MAAA,EAAQ;AACb,MAAA,IAAI,MAAA,KAAW,QAAQ,OAAO,IAAA;AAC9B,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,EAAG;AACtB,MAAA,IAAA,CAAK,IAAI,MAAM,CAAA;AACf,MAAA,MAAM,cAAc,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,MAAM,CAAA;AACvD,MAAA,MAAA,GAAS,WAAA,GAAc,YAAY,MAAA,GAAS,IAAA;AAAA,IAC9C;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,sBAAsB,MAAA,EAAoC;AACxE,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,IAAA,IAAI,KAAA,CAAM,OAAO,MAAA,EAAQ;AACzB,IAAA,IAAI,SAAS,KAAA,CAAM,MAAA;AACnB,IAAA,MAAM,IAAA,uBAAW,GAAA,EAAiB;AAClC,IAAA,OAAO,MAAA,EAAQ;AACb,MAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,QAAA,GAAA,CAAI,IAAA,CAAK,MAAM,EAAE,CAAA;AACjB,QAAA;AAAA,MACF;AACA,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,EAAG;AACtB,MAAA,IAAA,CAAK,IAAI,MAAM,CAAA;AACf,MAAA,MAAM,cAAc,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,MAAM,CAAA;AACvD,MAAA,MAAA,GAAS,WAAA,GAAc,YAAY,MAAA,GAAS,IAAA;AAAA,IAC9C;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;;;;;;;"}