forgot to commit the portal
This commit is contained in:
84
src/lib/components/util/Portal.svelte
Normal file
84
src/lib/components/util/Portal.svelte
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
type PortalProps = {
|
||||||
|
/**
|
||||||
|
* Content to render into the portal target.
|
||||||
|
*/
|
||||||
|
children: import("svelte").Snippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional CSS selector for the portal target element.
|
||||||
|
* Defaults to `document.body`.
|
||||||
|
*/
|
||||||
|
targetSelector?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional tag name used for the created mount node.
|
||||||
|
* Defaults to `div`.
|
||||||
|
*/
|
||||||
|
tagName?: keyof HTMLElementTagNameMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional class applied to the created mount node.
|
||||||
|
*/
|
||||||
|
class?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional id applied to the created mount node.
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
children,
|
||||||
|
targetSelector,
|
||||||
|
tagName = "div",
|
||||||
|
class: className,
|
||||||
|
id,
|
||||||
|
}: PortalProps = $props();
|
||||||
|
|
||||||
|
function createMountNode() {
|
||||||
|
const el = document.createElement(tagName);
|
||||||
|
if (className) el.className = className;
|
||||||
|
if (id) el.id = id;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTarget(): Element | null {
|
||||||
|
if (!browser) return null;
|
||||||
|
if (targetSelector) return document.querySelector(targetSelector);
|
||||||
|
return document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attachment factory: creates a mount node and appends it to the target.
|
||||||
|
* MUST return either:
|
||||||
|
* - void, or
|
||||||
|
* - a cleanup function, or
|
||||||
|
* - an object with a destroy() method
|
||||||
|
*
|
||||||
|
* The TS types in this project expect the cleanup-function form.
|
||||||
|
*/
|
||||||
|
function portal(node: HTMLElement) {
|
||||||
|
const target = resolveTarget();
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const mount = createMountNode();
|
||||||
|
target.appendChild(mount);
|
||||||
|
|
||||||
|
// Move the node into the mount (portaling)
|
||||||
|
mount.appendChild(node);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Remove mount (also removes `node` from DOM)
|
||||||
|
mount.remove();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if browser}
|
||||||
|
<div {@attach portal}>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
Reference in New Issue
Block a user