Skip to content

Commit

Permalink
allow focus in portalled containers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RobinMalfait committed May 11, 2023
1 parent 20c4185 commit 849113f
Showing 1 changed file with 17 additions and 8 deletions.
25 changes: 17 additions & 8 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = Parameters<MouseEventHandler<T>>[0]

Expand Down Expand Up @@ -309,6 +311,9 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(

useEffect(() => registerPopover?.(registerBag), [registerPopover, registerBag])

let { resolveContainers, MainTreeNode } = useRootContainers([button, panel])
let [portals, PortalWrapper] = useNestedPortals()

// Handle focus out
useEventListener(
ownerDocument?.defaultView,
Expand All @@ -319,6 +324,7 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
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

Expand All @@ -329,7 +335,7 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(

// Handle outside click
useOutsideClick(
[button, panel],
resolveContainers,
(event, target) => {
dispatch({ type: ActionTypes.ClosePopover })

Expand Down Expand Up @@ -385,13 +391,16 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
[PopoverStates.Closed]: State.Closed,
})}
>
{render({
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_POPOVER_TAG,
name: 'Popover',
})}
<PortalWrapper>
{render({
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_POPOVER_TAG,
name: 'Popover',
})}
<MainTreeNode />
</PortalWrapper>
</OpenClosedProvider>
</PopoverAPIContext.Provider>
</PopoverContext.Provider>
Expand Down

0 comments on commit 849113f

Please sign in to comment.