summaryrefslogtreecommitdiffhomepage
path: root/src/adapters/portal.ts
blob: afe42e7480ad86102363287b89ea1766ce4bc657 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * A Svelte `use:` action that teleports a node to `document.body`, escaping any
 * ancestor that establishes a containing block for `position: fixed` (most
 * commonly an ancestor with a `transform`, `filter`, `perspective`, or
 * `will-change` — e.g. the sidebar's `transform: translateX(...)` container).
 *
 * Without this, a `position: fixed` modal rendered inside such an ancestor is
 * positioned relative to the ANCESTOR, not the viewport (so it only covers the
 * sidebar area instead of the full screen). Moving the node to `document.body`
 * restores viewport-relative `fixed` positioning. Svelte still owns the node's
 * lifecycle (children, bindings, events); we just relocate it + remove it on
 * destroy as hygiene.
 *
 * No-op safely when there is no `document` (SSR / jsdom guards).
 */
export function portal(node: HTMLElement): { destroy(): void } {
  if (typeof document === "undefined") {
    return { destroy() {} };
  }
  document.body.appendChild(node);
  return {
    destroy() {
      if (node.parentNode === document.body) {
        document.body.removeChild(node);
      }
    },
  };
}