diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 8c395f9440..cebbb74bb9 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1685,6 +1685,7 @@ function OptionsFn( actions.setOptionsElement, setLocalOptionsElement ) + let portalOwnerDocument = useOwnerDocument(data.buttonElement || data.inputElement) let ownerDocument = useOwnerDocument(data.optionsElement) let usesOpenClosedState = useOpenClosed() @@ -1819,7 +1820,7 @@ function OptionsFn( let render = useRender() return ( - + diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index fdde28dd46..65d06cff81 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -951,6 +951,7 @@ function OptionsFn( let data = useData('Listbox.Options') let actions = useActions('Listbox.Options') + let portalOwnerDocument = useOwnerDocument(data.buttonElement) let ownerDocument = useOwnerDocument(data.optionsElement) let usesOpenClosedState = useOpenClosed() @@ -1163,7 +1164,7 @@ function OptionsFn( let render = useRender() return ( - + diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index f93afd2ed2..478b3709ba 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -638,6 +638,7 @@ function ItemsFn( useEvent((element) => dispatch({ type: ActionTypes.SetItemsElement, element })), setLocalItemsElement ) + let portalOwnerDocument = useOwnerDocument(state.buttonElement) let ownerDocument = useOwnerDocument(state.itemsElement) // Always enable `portal` functionality, when `anchor` is enabled @@ -824,7 +825,7 @@ function ItemsFn( let render = useRender() return ( - + {render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index 3d772c2e0d..68e11ca6bb 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -888,6 +888,7 @@ function PanelFn( useEvent((panel) => dispatch({ type: ActionTypes.SetPanel, panel })), setLocalPanelElement ) + let portalOwnerDocument = useOwnerDocument(state.button) let ownerDocument = useOwnerDocument(internalPanelRef) useIsoMorphicEffect(() => { @@ -1080,7 +1081,10 @@ function PanelFn( - + {visible && isPortalled && ( ): HTMLElement | null { +function usePortalTarget(ownerDocument: Document | null): HTMLElement | null { let forceInRoot = usePortalRoot() let groupTarget = useContext(PortalGroupContext) - let ownerDocument = useOwnerDocument(ref) - let [target, setTarget] = useState(() => { // Group context is used, but still null if (!forceInRoot && groupTarget !== null) return groupTarget.current ?? null @@ -77,13 +75,14 @@ export type PortalProps = PortalPropsWeControl, { enabled?: boolean + ownerDocument?: Document | null } > let InternalPortalFn = forwardRefWithAs(function InternalPortalFn< TTag extends ElementType = typeof DEFAULT_PORTAL_TAG, >(props: PortalProps, ref: Ref) { - let theirProps = props + let { ownerDocument: incomingOwnerDocument = null, ...theirProps } = props let internalPortalRootRef = useRef(null) let portalRef = useSyncRefs( optionalRef<(typeof internalPortalRootRef)['current']>((ref) => { @@ -91,8 +90,9 @@ let InternalPortalFn = forwardRefWithAs(function InternalPortalFn< }), ref ) - let ownerDocument = useOwnerDocument(internalPortalRootRef) - let target = usePortalTarget(internalPortalRootRef) + let defaultOwnerDocument = useOwnerDocument(internalPortalRootRef) + let ownerDocument = incomingOwnerDocument ?? defaultOwnerDocument + let target = usePortalTarget(ownerDocument) let [element] = useState(() => env.isServer ? null : ownerDocument?.createElement('div') ?? null ) @@ -154,12 +154,12 @@ function PortalFn( ) { let portalRef = useSyncRefs(ref) - let { enabled = true, ...theirProps } = props + let { enabled = true, ownerDocument, ...theirProps } = props let render = useRender() return enabled ? ( - + ) : ( render({ ourProps: { ref: portalRef }, diff --git a/packages/@headlessui-react/src/utils/owner.ts b/packages/@headlessui-react/src/utils/owner.ts index 7e5ff4a3c5..951c3def0f 100644 --- a/packages/@headlessui-react/src/utils/owner.ts +++ b/packages/@headlessui-react/src/utils/owner.ts @@ -3,12 +3,11 @@ import { env } from './env' export function getOwnerDocument>( element: T | null | undefined -) { +): Document | null { if (env.isServer) return null - if (element instanceof Node) return element.ownerDocument - if (element?.hasOwnProperty('current')) { - if (element.current instanceof Node) return element.current.ownerDocument - } + if (!element) return document + if ('ownerDocument' in element) return element.ownerDocument + if ('current' in element) return element.current?.ownerDocument ?? document - return document + return null } diff --git a/packages/@headlessui-vue/src/utils/owner.ts b/packages/@headlessui-vue/src/utils/owner.ts index d11083c1f7..75ab04cbcf 100644 --- a/packages/@headlessui-vue/src/utils/owner.ts +++ b/packages/@headlessui-vue/src/utils/owner.ts @@ -2,15 +2,13 @@ import type { Ref } from 'vue' import { dom } from './dom' import { env } from './env' -export function getOwnerDocument>( +export function getOwnerDocument>( element: T | null | undefined -) { +): Document | null { if (env.isServer) return null - if (element instanceof Node) return element.ownerDocument - if (element?.hasOwnProperty('value')) { - let domElement = dom(element as any) - if (domElement) return domElement.ownerDocument - } + if (!element) return document + if ('ownerDocument' in element) return element.ownerDocument + if ('value' in element) return dom(element as any)?.ownerDocument ?? document - return document + return null }