Skip to content

Commit 8a37854

Browse files
authored
Render <MainTreeNode /> indicators in Popover.Group only (#2634)
* only render `<MainTreeNode />` in `Popover.Group` instead of after every `Popover` * make Vue Popover consistent * apply same `MainTreeNode` logic to Vue version * update changelog
1 parent b380d03 commit 8a37854

File tree

6 files changed

+86
-24
lines changed

6 files changed

+86
-24
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111

1212
- Use correct value when resetting `<Listbox multiple>` and `<Combobox multiple>` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626))
13+
- Render `<MainTreeNode />` in `Popover.Group` component only ([#2634](https://github.com/tailwindlabs/headlessui/pull/2634))
1314

1415
## [1.7.16] - 2023-07-27
1516

packages/@headlessui-react/src/components/popover/popover.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-
5454
import { microTask } from '../../utils/micro-task'
5555
import { useLatestValue } from '../../hooks/use-latest-value'
5656
import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'
57-
import { useRootContainers } from '../../hooks/use-root-containers'
57+
import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-containers'
5858
import { useNestedPortals } from '../../components/portal/portal'
5959

6060
type MouseEvent<T> = Parameters<MouseEventHandler<T>>[0]
@@ -177,6 +177,7 @@ let PopoverGroupContext = createContext<{
177177
unregisterPopover(registerbag: PopoverRegisterBag): void
178178
isFocusWithinPopoverGroup(): boolean
179179
closeOthers(buttonId: string): void
180+
mainTreeNodeRef: MutableRefObject<HTMLElement | null>
180181
} | null>(null)
181182
PopoverGroupContext.displayName = 'PopoverGroupContext'
182183

@@ -313,6 +314,7 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
313314

314315
let [portals, PortalWrapper] = useNestedPortals()
315316
let root = useRootContainers({
317+
mainTreeNodeRef: groupContext?.mainTreeNodeRef,
316318
portals,
317319
defaultContainers: [button, panel],
318320
})
@@ -971,6 +973,7 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
971973
let internalGroupRef = useRef<HTMLElement | null>(null)
972974
let groupRef = useSyncRefs(internalGroupRef, ref)
973975
let [popovers, setPopovers] = useState<PopoverRegisterBag[]>([])
976+
let root = useMainTreeNode()
974977

975978
let unregisterPopover = useEvent((registerbag: PopoverRegisterBag) => {
976979
setPopovers((existing) => {
@@ -1017,8 +1020,15 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
10171020
unregisterPopover: unregisterPopover,
10181021
isFocusWithinPopoverGroup,
10191022
closeOthers,
1023+
mainTreeNodeRef: root.mainTreeNodeRef,
10201024
}),
1021-
[registerPopover, unregisterPopover, isFocusWithinPopoverGroup, closeOthers]
1025+
[
1026+
registerPopover,
1027+
unregisterPopover,
1028+
isFocusWithinPopoverGroup,
1029+
closeOthers,
1030+
root.mainTreeNodeRef,
1031+
]
10221032
)
10231033

10241034
let slot = useMemo<GroupRenderPropArg>(() => ({}), [])
@@ -1035,6 +1045,7 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
10351045
defaultTag: DEFAULT_GROUP_TAG,
10361046
name: 'Popover.Group',
10371047
})}
1048+
<root.MainTreeNode />
10381049
</PopoverGroupContext.Provider>
10391050
)
10401051
}

packages/@headlessui-react/src/hooks/use-root-containers.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import React, { useRef, useMemo, MutableRefObject } from 'react'
22
import { Hidden, Features as HiddenFeatures } from '../internal/hidden'
3+
import { useComputed } from './use-computed'
34
import { useEvent } from './use-event'
45
import { useOwnerDocument } from './use-owner'
56

67
export function useRootContainers({
78
defaultContainers = [],
89
portals,
10+
mainTreeNodeRef: _mainTreeNodeRef,
911
}: {
1012
defaultContainers?: (HTMLElement | null | MutableRefObject<HTMLElement | null>)[]
1113
portals?: MutableRefObject<HTMLElement[]>
14+
mainTreeNodeRef?: MutableRefObject<HTMLElement | null>
1215
} = {}) {
1316
// Reference to a node in the "main" tree, not in the portalled Dialog tree.
14-
let mainTreeNodeRef = useRef<HTMLDivElement | null>(null)
17+
let mainTreeNodeRef = useRef<HTMLElement | null>(_mainTreeNodeRef?.current ?? null)
1518
let ownerDocument = useOwnerDocument(mainTreeNodeRef)
1619

1720
let resolveContainers = useEvent(() => {
@@ -54,6 +57,25 @@ export function useRootContainers({
5457
contains: useEvent((element: HTMLElement) =>
5558
resolveContainers().some((container) => container.contains(element))
5659
),
60+
mainTreeNodeRef,
61+
MainTreeNode: useMemo(() => {
62+
return function MainTreeNode() {
63+
let hasPassedInMainTreeNode = useComputed(
64+
() => (_mainTreeNodeRef?.current ?? null) !== null,
65+
[_mainTreeNodeRef]
66+
)
67+
if (hasPassedInMainTreeNode) return null
68+
69+
return <Hidden features={HiddenFeatures.Hidden} ref={mainTreeNodeRef} />
70+
}
71+
}, [mainTreeNodeRef]),
72+
}
73+
}
74+
75+
export function useMainTreeNode() {
76+
let mainTreeNodeRef = useRef<HTMLElement | null>(null)
77+
78+
return {
5779
mainTreeNodeRef,
5880
MainTreeNode: useMemo(() => {
5981
return function MainTreeNode() {

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Fix form elements for uncontrolled `<Listbox multiple>` and `<Combobox multiple>` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626))
1313
- Use correct value when resetting `<Listbox multiple>` and `<Combobox multiple>` ([#2626](https://github.com/tailwindlabs/headlessui/pull/2626))
14+
- Render `<MainTreeNode />` in `PopoverGroup` component only ([#2634](https://github.com/tailwindlabs/headlessui/pull/2634))
1415

1516
## [1.7.15] - 2023-07-27
1617

packages/@headlessui-vue/src/components/popover/popover.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useEventListener } from '../../hooks/use-event-listener'
3737
import { Hidden, Features as HiddenFeatures } from '../../internal/hidden'
3838
import { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-direction'
3939
import { microTask } from '../../utils/micro-task'
40-
import { useRootContainers } from '../../hooks/use-root-containers'
40+
import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-containers'
4141
import { useNestedPortals } from '../../components/portal/portal'
4242

4343
enum PopoverStates {
@@ -82,6 +82,7 @@ let PopoverGroupContext = Symbol('PopoverGroupContext') as InjectionKey<{
8282
unregisterPopover(registerbag: PopoverRegisterBag): void
8383
isFocusWithinPopoverGroup(): boolean
8484
closeOthers(buttonId: string): void
85+
mainTreeNodeRef: Ref<HTMLElement | null>
8586
} | null>
8687

8788
function usePopoverGroupContext() {
@@ -103,6 +104,7 @@ interface PopoverRegisterBag {
103104

104105
export let Popover = defineComponent({
105106
name: 'Popover',
107+
inheritAttrs: false,
106108
props: {
107109
as: { type: [Object, String], default: 'div' },
108110
},
@@ -208,6 +210,7 @@ export let Popover = defineComponent({
208210

209211
let [portals, PortalWrapper] = useNestedPortals()
210212
let root = useRootContainers({
213+
mainTreeNodeRef: groupContext?.mainTreeNodeRef,
211214
portals,
212215
defaultContainers: [button, panel],
213216
})
@@ -259,16 +262,19 @@ export let Popover = defineComponent({
259262

260263
return () => {
261264
let slot = { open: popoverState.value === PopoverStates.Open, close: api.close }
262-
return h(PortalWrapper, {}, () =>
263-
render({
264-
theirProps: { ...props, ...attrs },
265-
ourProps: { ref: internalPopoverRef },
266-
slot,
267-
slots,
268-
attrs,
269-
name: 'Popover',
270-
})
271-
)
265+
return h(Fragment, [
266+
h(PortalWrapper, {}, () =>
267+
render({
268+
theirProps: { ...props, ...attrs },
269+
ourProps: { ref: internalPopoverRef },
270+
slot,
271+
slots,
272+
attrs,
273+
name: 'Popover',
274+
})
275+
),
276+
h(root.MainTreeNode),
277+
])
272278
}
273279
},
274280
})
@@ -745,13 +751,15 @@ export let PopoverPanel = defineComponent({
745751

746752
export let PopoverGroup = defineComponent({
747753
name: 'PopoverGroup',
754+
inheritAttrs: false,
748755
props: {
749756
as: { type: [Object, String], default: 'div' },
750757
},
751758
setup(props, { attrs, slots, expose }) {
752759
let groupRef = ref<HTMLElement | null>(null)
753760
let popovers = shallowRef<PopoverRegisterBag[]>([])
754761
let ownerDocument = computed(() => getOwnerDocument(groupRef))
762+
let root = useMainTreeNode()
755763

756764
expose({ el: groupRef, $el: groupRef })
757765

@@ -794,19 +802,23 @@ export let PopoverGroup = defineComponent({
794802
unregisterPopover,
795803
isFocusWithinPopoverGroup,
796804
closeOthers,
805+
mainTreeNodeRef: root.mainTreeNodeRef,
797806
})
798807

799808
return () => {
800809
let ourProps = { ref: groupRef }
801810

802-
return render({
803-
ourProps,
804-
theirProps: props,
805-
slot: {},
806-
attrs,
807-
slots,
808-
name: 'PopoverGroup',
809-
})
811+
return h(Fragment, [
812+
render({
813+
ourProps,
814+
theirProps: { ...props, ...attrs },
815+
slot: {},
816+
attrs,
817+
slots,
818+
name: 'PopoverGroup',
819+
}),
820+
h(root.MainTreeNode),
821+
])
810822
}
811823
},
812824
})

packages/@headlessui-vue/src/hooks/use-root-containers.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import { ref, h, Ref } from 'vue'
1+
import { ref, h, Ref, computed } from 'vue'
22
import { Hidden, Features as HiddenFeatures } from '../internal/hidden'
33
import { getOwnerDocument } from '../utils/owner'
44
import { dom } from '../utils/dom'
55

66
export function useRootContainers({
77
defaultContainers = [],
88
portals,
9+
mainTreeNodeRef: _mainTreeNodeRef,
910
}: {
1011
defaultContainers?: (HTMLElement | null | Ref<HTMLElement | null>)[]
1112
portals?: Ref<HTMLElement[]>
13+
mainTreeNodeRef?: Ref<HTMLElement | null>
1214
} = {}) {
1315
// Reference to a node in the "main" tree, not in the portalled Dialog tree.
14-
let mainTreeNodeRef = ref<HTMLDivElement | null>(null)
16+
let mainTreeNodeRef = ref<HTMLElement | null>(null)
1517
let ownerDocument = getOwnerDocument(mainTreeNodeRef)
1618

1719
function resolveContainers() {
@@ -54,6 +56,19 @@ export function useRootContainers({
5456
contains(element: HTMLElement) {
5557
return resolveContainers().some((container) => container.contains(element))
5658
},
59+
mainTreeNodeRef,
60+
MainTreeNode() {
61+
let hasPassedInMainTreeNode = (_mainTreeNodeRef?.value ?? null) !== null
62+
if (hasPassedInMainTreeNode) return null
63+
return h(Hidden, { features: HiddenFeatures.Hidden, ref: mainTreeNodeRef })
64+
},
65+
}
66+
}
67+
68+
export function useMainTreeNode() {
69+
let mainTreeNodeRef = ref<HTMLElement | null>(null)
70+
71+
return {
5772
mainTreeNodeRef,
5873
MainTreeNode() {
5974
return h(Hidden, { features: HiddenFeatures.Hidden, ref: mainTreeNodeRef })

0 commit comments

Comments
 (0)