Manually clean up <Portal> on destroy

Fixes #68

In some cases, when Svelte sees a parent and a child, it will detach the parent and assume that this suffices to detach the child. However, the <Portal> moves elements around in the DOM, so this assumption does not always hold. We need to make sure we detach the portal element ourselves.
This commit is contained in:
Ryan Gossiaux
2022-02-27 15:18:36 -08:00
parent 87aadf2490
commit b18fd2093b
2 changed files with 48 additions and 0 deletions

View File

@@ -290,3 +290,46 @@ it('should be possible to force the Portal into a specific element using PortalG
`"<div><main><aside id=\\"group-1\\">A<div>Next to A</div></aside> <section id=\\"group-2\\"><span>B</span></section> </main></div><div id=\\"headlessui-portal-root\\"><div>I am in the portal root</div></div>"` `"<div><main><aside id=\\"group-1\\">A<div>Next to A</div></aside> <section id=\\"group-2\\"><span>B</span></section> </main></div><div id=\\"headlessui-portal-root\\"><div>I am in the portal root</div></div>"`
) )
}) })
it('should cleanup the Portal properly when Svelte would not detach it', async () => {
expect(getPortalRoot()).toBe(null)
render(svelte`
<script>
let render = false;
</script>
<main id="parent">
<button id="a" on:click={() => render = !render}>
Toggle
</button>
{#if render}
<div>
<Portal>
<p id="content1">Contents 1 ...</p>
</Portal>
</div>
{/if}
</main>
`)
let a = document.getElementById('a')
expect(getPortalRoot()).toBe(null)
// Let's render the first Portal
await click(a)
expect(getPortalRoot()).not.toBe(null)
expect(getPortalRoot().childNodes).toHaveLength(1)
// Let's remove the first portal
await click(a)
expect(getPortalRoot()).toBe(null)
// Let's render the first Portal again
await click(a)
expect(getPortalRoot()).not.toBe(null)
expect(getPortalRoot().childNodes).toHaveLength(1)
})

View File

@@ -11,6 +11,11 @@ export function portal(
newTarget.append(element); newTarget.append(element);
}, },
destroy() { destroy() {
// Need to detach ourselves--we can't rely on Svelte always detaching
// us since we moved in the component tree.
if (target?.contains(element)) {
target.removeChild(element);
}
if (target && target.childNodes.length <= 0) { if (target && target.childNodes.length <= 0) {
target.parentElement?.removeChild(target); target.parentElement?.removeChild(target);
} }