03fe3c573d
This PR improves the internal `<Portal>` component by allowing to pass
in a custom `ownerDocument`.
This fixes an issue if you do something like this:
```ts
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { useState } from 'react'
import { createPortal } from 'react-dom'
export default function App() {
let [target, setTarget] = useState(null)
return (
<div className="grid min-h-full place-content-center">
<iframe
ref={(iframe) => {
if (!iframe) return
if (target) return
let el = iframe.contentDocument.createElement('div')
iframe.contentDocument.body.appendChild(el)
setTarget(el)
}}
className="h-[50px] w-[75px] border-black bg-white"
>
{target && createPortal(<MenuExample />, target)}
</iframe>
</div>
)
}
function MenuExample() {
return (
<Menu>
<MenuButton>Open</MenuButton>
<MenuItems
anchor="bottom"
className="flex min-w-[var(--button-width)] flex-col bg-white shadow"
>
<MenuItem>
<a className="block data-[focus]:bg-blue-100" href="/settings">
Settings
</a>
</MenuItem>
<MenuItem>
<a className="block data-[focus]:bg-blue-100" href="/support">
Support
</a>
</MenuItem>
<MenuItem>
<a className="block data-[focus]:bg-blue-100" href="/license">
License
</a>
</MenuItem>
</MenuItems>
</Menu>
)
}
```
---
Here is a little reproduction video. The `<Menu/>` you see is rendered
in an `<iframe>`, the goal is that `<MenuItems/>` _also_ render inside
of the `<iframe>`.
In the video below we start with the fix where you can see that the
items are inside the iframe (and unstyled because I didn't load any
styles). The second part of the video is the before, where you can see
that the `<MenuItems/>` escape the `<iframe>` and are styled. That's not
what we want.
https://github.com/user-attachments/assets/2da7627e-7846-4c4d-bb14-278f80a03cd8
14 lines
438 B
TypeScript
14 lines
438 B
TypeScript
import type { MutableRefObject } from 'react'
|
|
import { env } from './env'
|
|
|
|
export function getOwnerDocument<T extends Element | MutableRefObject<Element | null>>(
|
|
element: T | null | undefined
|
|
): Document | null {
|
|
if (env.isServer) return null
|
|
if (!element) return document
|
|
if ('ownerDocument' in element) return element.ownerDocument
|
|
if ('current' in element) return element.current?.ownerDocument ?? document
|
|
|
|
return null
|
|
}
|