diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 4eb2e44888..5537504bf0 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Use correct value when resetting `` and `` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626)) +- Render `` in `Popover.Group` component only ([#2634](https://github.com/tailwindlabs/headlessui/pull/2634)) ## [1.7.16] - 2023-07-27 diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index b911d3fe94..5fb0d28dfb 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -54,7 +54,7 @@ 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 { useMainTreeNode, useRootContainers } from '../../hooks/use-root-containers' import { useNestedPortals } from '../../components/portal/portal' type MouseEvent = Parameters>[0] @@ -177,6 +177,7 @@ let PopoverGroupContext = createContext<{ unregisterPopover(registerbag: PopoverRegisterBag): void isFocusWithinPopoverGroup(): boolean closeOthers(buttonId: string): void + mainTreeNodeRef: MutableRefObject } | null>(null) PopoverGroupContext.displayName = 'PopoverGroupContext' @@ -313,6 +314,7 @@ function PopoverFn( let [portals, PortalWrapper] = useNestedPortals() let root = useRootContainers({ + mainTreeNodeRef: groupContext?.mainTreeNodeRef, portals, defaultContainers: [button, panel], }) @@ -971,6 +973,7 @@ function GroupFn( let internalGroupRef = useRef(null) let groupRef = useSyncRefs(internalGroupRef, ref) let [popovers, setPopovers] = useState([]) + let root = useMainTreeNode() let unregisterPopover = useEvent((registerbag: PopoverRegisterBag) => { setPopovers((existing) => { @@ -1017,8 +1020,15 @@ function GroupFn( unregisterPopover: unregisterPopover, isFocusWithinPopoverGroup, closeOthers, + mainTreeNodeRef: root.mainTreeNodeRef, }), - [registerPopover, unregisterPopover, isFocusWithinPopoverGroup, closeOthers] + [ + registerPopover, + unregisterPopover, + isFocusWithinPopoverGroup, + closeOthers, + root.mainTreeNodeRef, + ] ) let slot = useMemo(() => ({}), []) @@ -1035,6 +1045,7 @@ function GroupFn( defaultTag: DEFAULT_GROUP_TAG, name: 'Popover.Group', })} + ) } diff --git a/packages/@headlessui-react/src/hooks/use-root-containers.tsx b/packages/@headlessui-react/src/hooks/use-root-containers.tsx index 04bf746478..1085980c3e 100644 --- a/packages/@headlessui-react/src/hooks/use-root-containers.tsx +++ b/packages/@headlessui-react/src/hooks/use-root-containers.tsx @@ -1,17 +1,20 @@ import React, { useRef, useMemo, MutableRefObject } from 'react' import { Hidden, Features as HiddenFeatures } from '../internal/hidden' +import { useComputed } from './use-computed' import { useEvent } from './use-event' import { useOwnerDocument } from './use-owner' export function useRootContainers({ defaultContainers = [], portals, + mainTreeNodeRef: _mainTreeNodeRef, }: { defaultContainers?: (HTMLElement | null | MutableRefObject)[] portals?: MutableRefObject + mainTreeNodeRef?: MutableRefObject } = {}) { // Reference to a node in the "main" tree, not in the portalled Dialog tree. - let mainTreeNodeRef = useRef(null) + let mainTreeNodeRef = useRef(_mainTreeNodeRef?.current ?? null) let ownerDocument = useOwnerDocument(mainTreeNodeRef) let resolveContainers = useEvent(() => { @@ -54,6 +57,25 @@ export function useRootContainers({ contains: useEvent((element: HTMLElement) => resolveContainers().some((container) => container.contains(element)) ), + mainTreeNodeRef, + MainTreeNode: useMemo(() => { + return function MainTreeNode() { + let hasPassedInMainTreeNode = useComputed( + () => (_mainTreeNodeRef?.current ?? null) !== null, + [_mainTreeNodeRef] + ) + if (hasPassedInMainTreeNode) return null + + return + } + }, [mainTreeNodeRef]), + } +} + +export function useMainTreeNode() { + let mainTreeNodeRef = useRef(null) + + return { mainTreeNodeRef, MainTreeNode: useMemo(() => { return function MainTreeNode() { diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index faf9e47560..6a6ec9c9fa 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix form elements for uncontrolled `` and `` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626)) - Use correct value when resetting `` and `` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626)) +- Render `` in `PopoverGroup` component only ([#2634](https://github.com/tailwindlabs/headlessui/pull/2634)) ## [1.7.15] - 2023-07-27 diff --git a/packages/@headlessui-vue/src/components/popover/popover.ts b/packages/@headlessui-vue/src/components/popover/popover.ts index ce70985755..94834e371f 100644 --- a/packages/@headlessui-vue/src/components/popover/popover.ts +++ b/packages/@headlessui-vue/src/components/popover/popover.ts @@ -37,7 +37,7 @@ import { useEventListener } from '../../hooks/use-event-listener' import { Hidden, Features as HiddenFeatures } from '../../internal/hidden' import { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-direction' import { microTask } from '../../utils/micro-task' -import { useRootContainers } from '../../hooks/use-root-containers' +import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-containers' import { useNestedPortals } from '../../components/portal/portal' enum PopoverStates { @@ -82,6 +82,7 @@ let PopoverGroupContext = Symbol('PopoverGroupContext') as InjectionKey<{ unregisterPopover(registerbag: PopoverRegisterBag): void isFocusWithinPopoverGroup(): boolean closeOthers(buttonId: string): void + mainTreeNodeRef: Ref } | null> function usePopoverGroupContext() { @@ -103,6 +104,7 @@ interface PopoverRegisterBag { export let Popover = defineComponent({ name: 'Popover', + inheritAttrs: false, props: { as: { type: [Object, String], default: 'div' }, }, @@ -208,6 +210,7 @@ export let Popover = defineComponent({ let [portals, PortalWrapper] = useNestedPortals() let root = useRootContainers({ + mainTreeNodeRef: groupContext?.mainTreeNodeRef, portals, defaultContainers: [button, panel], }) @@ -259,16 +262,19 @@ export let Popover = defineComponent({ return () => { let slot = { open: popoverState.value === PopoverStates.Open, close: api.close } - return h(PortalWrapper, {}, () => - render({ - theirProps: { ...props, ...attrs }, - ourProps: { ref: internalPopoverRef }, - slot, - slots, - attrs, - name: 'Popover', - }) - ) + return h(Fragment, [ + h(PortalWrapper, {}, () => + render({ + theirProps: { ...props, ...attrs }, + ourProps: { ref: internalPopoverRef }, + slot, + slots, + attrs, + name: 'Popover', + }) + ), + h(root.MainTreeNode), + ]) } }, }) @@ -745,6 +751,7 @@ export let PopoverPanel = defineComponent({ export let PopoverGroup = defineComponent({ name: 'PopoverGroup', + inheritAttrs: false, props: { as: { type: [Object, String], default: 'div' }, }, @@ -752,6 +759,7 @@ export let PopoverGroup = defineComponent({ let groupRef = ref(null) let popovers = shallowRef([]) let ownerDocument = computed(() => getOwnerDocument(groupRef)) + let root = useMainTreeNode() expose({ el: groupRef, $el: groupRef }) @@ -794,19 +802,23 @@ export let PopoverGroup = defineComponent({ unregisterPopover, isFocusWithinPopoverGroup, closeOthers, + mainTreeNodeRef: root.mainTreeNodeRef, }) return () => { let ourProps = { ref: groupRef } - return render({ - ourProps, - theirProps: props, - slot: {}, - attrs, - slots, - name: 'PopoverGroup', - }) + return h(Fragment, [ + render({ + ourProps, + theirProps: { ...props, ...attrs }, + slot: {}, + attrs, + slots, + name: 'PopoverGroup', + }), + h(root.MainTreeNode), + ]) } }, }) diff --git a/packages/@headlessui-vue/src/hooks/use-root-containers.ts b/packages/@headlessui-vue/src/hooks/use-root-containers.ts index bf0590a24c..4af9381a5d 100644 --- a/packages/@headlessui-vue/src/hooks/use-root-containers.ts +++ b/packages/@headlessui-vue/src/hooks/use-root-containers.ts @@ -1,4 +1,4 @@ -import { ref, h, Ref } from 'vue' +import { ref, h, Ref, computed } from 'vue' import { Hidden, Features as HiddenFeatures } from '../internal/hidden' import { getOwnerDocument } from '../utils/owner' import { dom } from '../utils/dom' @@ -6,12 +6,14 @@ import { dom } from '../utils/dom' export function useRootContainers({ defaultContainers = [], portals, + mainTreeNodeRef: _mainTreeNodeRef, }: { defaultContainers?: (HTMLElement | null | Ref)[] portals?: Ref + mainTreeNodeRef?: Ref } = {}) { // Reference to a node in the "main" tree, not in the portalled Dialog tree. - let mainTreeNodeRef = ref(null) + let mainTreeNodeRef = ref(null) let ownerDocument = getOwnerDocument(mainTreeNodeRef) function resolveContainers() { @@ -54,6 +56,19 @@ export function useRootContainers({ contains(element: HTMLElement) { return resolveContainers().some((container) => container.contains(element)) }, + mainTreeNodeRef, + MainTreeNode() { + let hasPassedInMainTreeNode = (_mainTreeNodeRef?.value ?? null) !== null + if (hasPassedInMainTreeNode) return null + return h(Hidden, { features: HiddenFeatures.Hidden, ref: mainTreeNodeRef }) + }, + } +} + +export function useMainTreeNode() { + let mainTreeNodeRef = ref(null) + + return { mainTreeNodeRef, MainTreeNode() { return h(Hidden, { features: HiddenFeatures.Hidden, ref: mainTreeNodeRef })