From 849113fe88eca412c3e0ac9c8c76f6f473178414 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 11 May 2023 18:23:08 +0200 Subject: [PATCH] allow focus in portalled containers The `Popover` component will close by default if focus is moved outside of it. However, if you use a `Portal` comopnent inside the `Popover.Panel` then from a DOM perspective you are moving the focus outside of the `Popover.Panel`. This prevents the closing, and allows the focus into the `Portal`. It currently only allows for `Portal` components that originated from the `Popover` component. This means that if you open a `Dialog` component from within the `Popover` component, the `Dialog` already renders a `Portal` but since this is part of the `Dialog` and not the `Popover` it will close the `Popover` when focus is moved to the `Dialog` component. --- .../src/components/popover/popover.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index b9b3e589fb..1264279e3d 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -54,6 +54,8 @@ import { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab- import { microTask } from '../../utils/micro-task' import { useLatestValue } from '../../hooks/use-latest-value' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' +import { useRootContainers } from '../../hooks/use-root-containers' +import { useNestedPortals } from '../../components/portal/portal' type MouseEvent = Parameters>[0] @@ -309,6 +311,9 @@ function PopoverFn( useEffect(() => registerPopover?.(registerBag), [registerPopover, registerBag]) + let { resolveContainers, MainTreeNode } = useRootContainers([button, panel]) + let [portals, PortalWrapper] = useNestedPortals() + // Handle focus out useEventListener( ownerDocument?.defaultView, @@ -319,6 +324,7 @@ function PopoverFn( if (!button) return if (!panel) return if (event.target === window) return + if (portals.current.some((portal) => portal.contains(event.target as HTMLElement))) return if (beforePanelSentinel.current?.contains?.(event.target as HTMLElement)) return if (afterPanelSentinel.current?.contains?.(event.target as HTMLElement)) return @@ -329,7 +335,7 @@ function PopoverFn( // Handle outside click useOutsideClick( - [button, panel], + resolveContainers, (event, target) => { dispatch({ type: ActionTypes.ClosePopover }) @@ -385,13 +391,16 @@ function PopoverFn( [PopoverStates.Closed]: State.Closed, })} > - {render({ - ourProps, - theirProps, - slot, - defaultTag: DEFAULT_POPOVER_TAG, - name: 'Popover', - })} + + {render({ + ourProps, + theirProps, + slot, + defaultTag: DEFAULT_POPOVER_TAG, + name: 'Popover', + })} + +