summaryrefslogtreecommitdiffhomepage
path: root/src/adapters/portal.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/adapters/portal.ts')
-rw-r--r--src/adapters/portal.ts28
1 files changed, 28 insertions, 0 deletions
diff --git a/src/adapters/portal.ts b/src/adapters/portal.ts
new file mode 100644
index 0000000..afe42e7
--- /dev/null
+++ b/src/adapters/portal.ts
@@ -0,0 +1,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);
+ }
+ },
+ };
+}