diff --git a/src/lib/components/portal/portal.test.ts b/src/lib/components/portal/portal.test.ts index 9dc518a..252228b 100644 --- a/src/lib/components/portal/portal.test.ts +++ b/src/lib/components/portal/portal.test.ts @@ -3,6 +3,7 @@ import { render } from "@testing-library/svelte"; import Portal from "./Portal.svelte"; import PortalGroup from "./PortalGroup.svelte"; import { click } from "$lib/test-utils/interactions"; +import { tick } from "svelte"; function getPortalRoot() { return document.getElementById('headlessui-portal-root')! @@ -333,3 +334,26 @@ it('should cleanup the Portal properly when Svelte would not detach it', async ( expect(getPortalRoot()).not.toBe(null) expect(getPortalRoot().childNodes).toHaveLength(1) }) + +it('should move the Portal last during initial render', async () => { + expect(getPortalRoot()).toBe(null) + + // We need to use a custom target because of the implementation of + // render() in the testing library + render(svelte` + + + + Portal + +
Main
+ `) + + await tick(); + + expect(document.body.innerHTML).toMatchInlineSnapshot( + `"
Main
Portal
"` + ) +}) diff --git a/src/lib/hooks/use-portal.ts b/src/lib/hooks/use-portal.ts index bbda7e5..3669c92 100644 --- a/src/lib/hooks/use-portal.ts +++ b/src/lib/hooks/use-portal.ts @@ -1,9 +1,20 @@ +import { tick } from "svelte"; + export function portal( element: HTMLElement, target: HTMLElement | null | undefined ) { if (target) { target.append(element); + // During initial render, the "portal" might be constructed before + // the root component has even attached, causing the portal to not work. + // This is a workaround for this issue--it can't guarantee the portal is + // **always** last, but it should catch the normal cases. + tick().then(() => { + if (target && element !== target.lastChild) { + target.appendChild(element); + } + }); } return { update(newTarget: HTMLElement) {