Skip to content

Commit

Permalink
Render <MainTreeNode /> indicators in Popover.Group only (#2634)
Browse files Browse the repository at this point in the history
* only render `<MainTreeNode />` in `Popover.Group` instead of after every `Popover`

* make Vue Popover consistent

* apply same `MainTreeNode` logic to Vue version

* update changelog
  • Loading branch information
RobinMalfait committed Aug 2, 2023
1 parent b380d03 commit 8a37854
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 24 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

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

## [1.7.16] - 2023-07-27

Expand Down
15 changes: 13 additions & 2 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = Parameters<MouseEventHandler<T>>[0]
Expand Down Expand Up @@ -177,6 +177,7 @@ let PopoverGroupContext = createContext<{
unregisterPopover(registerbag: PopoverRegisterBag): void
isFocusWithinPopoverGroup(): boolean
closeOthers(buttonId: string): void
mainTreeNodeRef: MutableRefObject<HTMLElement | null>
} | null>(null)
PopoverGroupContext.displayName = 'PopoverGroupContext'

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

let [portals, PortalWrapper] = useNestedPortals()
let root = useRootContainers({
mainTreeNodeRef: groupContext?.mainTreeNodeRef,
portals,
defaultContainers: [button, panel],
})
Expand Down Expand Up @@ -971,6 +973,7 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
let internalGroupRef = useRef<HTMLElement | null>(null)
let groupRef = useSyncRefs(internalGroupRef, ref)
let [popovers, setPopovers] = useState<PopoverRegisterBag[]>([])
let root = useMainTreeNode()

let unregisterPopover = useEvent((registerbag: PopoverRegisterBag) => {
setPopovers((existing) => {
Expand Down Expand Up @@ -1017,8 +1020,15 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
unregisterPopover: unregisterPopover,
isFocusWithinPopoverGroup,
closeOthers,
mainTreeNodeRef: root.mainTreeNodeRef,
}),
[registerPopover, unregisterPopover, isFocusWithinPopoverGroup, closeOthers]
[
registerPopover,
unregisterPopover,
isFocusWithinPopoverGroup,
closeOthers,
root.mainTreeNodeRef,
]
)

let slot = useMemo<GroupRenderPropArg>(() => ({}), [])
Expand All @@ -1035,6 +1045,7 @@ function GroupFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
defaultTag: DEFAULT_GROUP_TAG,
name: 'Popover.Group',
})}
<root.MainTreeNode />
</PopoverGroupContext.Provider>
)
}
Expand Down
24 changes: 23 additions & 1 deletion packages/@headlessui-react/src/hooks/use-root-containers.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLElement | null>)[]
portals?: MutableRefObject<HTMLElement[]>
mainTreeNodeRef?: MutableRefObject<HTMLElement | null>
} = {}) {
// Reference to a node in the "main" tree, not in the portalled Dialog tree.
let mainTreeNodeRef = useRef<HTMLDivElement | null>(null)
let mainTreeNodeRef = useRef<HTMLElement | null>(_mainTreeNodeRef?.current ?? null)
let ownerDocument = useOwnerDocument(mainTreeNodeRef)

let resolveContainers = useEvent(() => {
Expand Down Expand Up @@ -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 <Hidden features={HiddenFeatures.Hidden} ref={mainTreeNodeRef} />
}
}, [mainTreeNodeRef]),
}
}

export function useMainTreeNode() {
let mainTreeNodeRef = useRef<HTMLElement | null>(null)

return {
mainTreeNodeRef,
MainTreeNode: useMemo(() => {
return function MainTreeNode() {
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

## [1.7.15] - 2023-07-27

Expand Down
50 changes: 31 additions & 19 deletions packages/@headlessui-vue/src/components/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -82,6 +82,7 @@ let PopoverGroupContext = Symbol('PopoverGroupContext') as InjectionKey<{
unregisterPopover(registerbag: PopoverRegisterBag): void
isFocusWithinPopoverGroup(): boolean
closeOthers(buttonId: string): void
mainTreeNodeRef: Ref<HTMLElement | null>
} | null>

function usePopoverGroupContext() {
Expand All @@ -103,6 +104,7 @@ interface PopoverRegisterBag {

export let Popover = defineComponent({
name: 'Popover',
inheritAttrs: false,
props: {
as: { type: [Object, String], default: 'div' },
},
Expand Down Expand Up @@ -208,6 +210,7 @@ export let Popover = defineComponent({

let [portals, PortalWrapper] = useNestedPortals()
let root = useRootContainers({
mainTreeNodeRef: groupContext?.mainTreeNodeRef,
portals,
defaultContainers: [button, panel],
})
Expand Down Expand Up @@ -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),
])
}
},
})
Expand Down Expand Up @@ -745,13 +751,15 @@ export let PopoverPanel = defineComponent({

export let PopoverGroup = defineComponent({
name: 'PopoverGroup',
inheritAttrs: false,
props: {
as: { type: [Object, String], default: 'div' },
},
setup(props, { attrs, slots, expose }) {
let groupRef = ref<HTMLElement | null>(null)
let popovers = shallowRef<PopoverRegisterBag[]>([])
let ownerDocument = computed(() => getOwnerDocument(groupRef))
let root = useMainTreeNode()

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

Expand Down Expand Up @@ -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),
])
}
},
})
19 changes: 17 additions & 2 deletions packages/@headlessui-vue/src/hooks/use-root-containers.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
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'

export function useRootContainers({
defaultContainers = [],
portals,
mainTreeNodeRef: _mainTreeNodeRef,
}: {
defaultContainers?: (HTMLElement | null | Ref<HTMLElement | null>)[]
portals?: Ref<HTMLElement[]>
mainTreeNodeRef?: Ref<HTMLElement | null>
} = {}) {
// Reference to a node in the "main" tree, not in the portalled Dialog tree.
let mainTreeNodeRef = ref<HTMLDivElement | null>(null)
let mainTreeNodeRef = ref<HTMLElement | null>(null)
let ownerDocument = getOwnerDocument(mainTreeNodeRef)

function resolveContainers() {
Expand Down Expand Up @@ -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<HTMLElement | null>(null)

return {
mainTreeNodeRef,
MainTreeNode() {
return h(Hidden, { features: HiddenFeatures.Hidden, ref: mainTreeNodeRef })
Expand Down

2 comments on commit 8a37854

@vercel
Copy link

@vercel vercel bot commented on 8a37854 Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

headlessui-vue – ./packages/playground-vue

headlessui-vue-tailwindlabs.vercel.app
headlessui-vue.vercel.app
headlessui-vue-git-main-tailwindlabs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 8a37854 Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

headlessui-react – ./packages/playground-react

headlessui-react.vercel.app
headlessui-react-tailwindlabs.vercel.app
headlessui-react-git-main-tailwindlabs.vercel.app

Please sign in to comment.