Skip to content

Commit

Permalink
feat: complete reparenting demo
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Sep 7, 2023
1 parent c0ef557 commit a633519
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 49 deletions.
11 changes: 0 additions & 11 deletions src/lib/Box.svelte

This file was deleted.

13 changes: 5 additions & 8 deletions src/lib/Limbo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@
-->

<script lang="ts">
import Box from "./Box.svelte";
import type { SvelteComponent } from "svelte";
export let component: SvelteComponent;
export let component: HTMLElement;
</script>

<!--
We don't want to render this component,
but we use it as the initial holder before teleporting it.
this allows us to manage when a Portal gets destroyed without
destroying the current node.
Wrap it in a box to guarantee that {component} is a DOM component,
since we cant guarantee that all svelte components only have 1 root node.
-->
<div hidden>
<!-- Wrap it in a box to guarantee that {component} is a Svelte component. -->
<Box bind:this={component}><slot /></Box>
</div>
<div style="display: contents;" bind:this={component}><slot /></div>
40 changes: 27 additions & 13 deletions src/lib/Portal.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
<script context="module" lang="ts">
import type { SvelteComponent } from "svelte";
import { writable } from 'svelte/store';
import { tick } from 'svelte';
type StrictSvelteComponent = SvelteComponent<unknown, unknown, unknown>;
type Container = HTMLElement;
const components: { [key: StrictSvelteComponent]: string } = {
}
export function teleport(component: StrictSvelteComponent, key: string) {
components[component] = key;
}
// universal map to keep track of what portal a component wants to be in
let components = new Map<Container, string>();
// dirty tracker - a Map isn't reactive, so we need to coerce Svelte to re-render
let dirty = writable(false);
export function hasComponent(component: StrictSvelteComponent) {
return components[component] !== undefined;
export async function teleport(component: Container, key: string) {
components.set(component, key);
// trigger a re-render
dirty.set(true);
await tick();
dirty.set(false);
}
</script>

<script lang="ts">
export let key: string;
export let component: SvelteComponent;
export let component: Container;
/*
- component may be nil before mount
- listen to dirty to force a re-render
*/
$: if (component && $dirty == true && components.get(component) == key) {
// appendChild forces a move, not a copy - we can safely use this as the DOM
// handles ownership of the node for us
container.appendChild(component);
}
let container: HTMLDivElement;
</script>

<div style="display: contents" bind:this={container} hidden={components[component] != key} />
<div style="display: contents;" bind:this={container} hidden={components.get(component) != key} />
6 changes: 3 additions & 3 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Limbo, { teleport } from "./Limbo.svelte";
import Portal from "./Portal.svelte";
import Limbo from './Limbo.svelte';
import Portal, { teleport } from './Portal.svelte';

export { Limbo, teleport, Portal };
export { Limbo, teleport, Portal };
25 changes: 11 additions & 14 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
<script>
import { onMount } from "svelte";
import Limbo from "$lib/Limbo.svelte";
import Portal, { teleport } from "$lib/Portal.svelte"
<script lang="ts">
import { onMount } from 'svelte';
import { Portal, Limbo, teleport } from '$lib';
let component;
let component: HTMLElement;
function send(label: string) {
return () => {
teleport(label)
}
teleport(component, label);
};
}
onMount(() => {
teleport("a")
})
onMount((): void => send('a')());
</script>

<main>
<Limbo bind:component={component}>
<input placeholder="Enter unkept state">
<Limbo bind:component>
<input placeholder="Enter unkept state" />
</Limbo>
<div class="container">
<h1>Container A</h1>
<Portal key="a" {component} />
<button on:click={send("a")}>Move Component Here</button>
<button on:click={send('a')}>Move Component Here</button>
</div>
<div class="container">
<h1>Container B</h1>
<Portal key="b" {component} />
<button on:click={send("b")}>Move Component Here</button>
<button on:click={send('b')}>Move Component Here</button>
</div>
</main>

Expand Down

0 comments on commit a633519

Please sign in to comment.