fix(Popover): allow manual mode without blocking normal behaviour

This commit is contained in:
Benjamin Canac
2024-01-03 16:51:20 +01:00
parent 3844714644
commit 3334e2af3d
4 changed files with 49 additions and 36 deletions

View File

@@ -1,18 +1,19 @@
<script setup>
const open = ref(false)
const open = ref(true)
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<div class="flex gap-4 items-center">
<UToggle v-model="open" />
<UPopover :open="open">
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
<UPopover v-model:open="open">
<UButton color="white" :label="open.toString()" trailing-icon="i-heroicons-chevron-down-20-solid" />
<template #panel>
<div class="p-4">
<Placeholder class="h-20 w-48" />
</div>
</template>
</UPopover>
</div>
<template #panel>
<div class="p-4">
<Placeholder class="h-20 w-48" />
</div>
</template>
</UPopover>
</template>

View File

@@ -1,13 +1,11 @@
<template>
<div class="flex gap-4 items-center">
<UPopover overlay>
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
<UPopover overlay>
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
<template #panel>
<div class="p-4">
<Placeholder class="h-20 w-48" />
</div>
</template>
</UPopover>
</div>
<template #panel>
<div class="p-4">
<Placeholder class="h-20 w-48" />
</div>
</template>
</UPopover>
</template>

View File

@@ -21,7 +21,7 @@ Use the `mode` prop to switch between `click` and `hover` modes.
### Manual
Use the `open` prop to manually control showing the panel.
Use a `v-model:open` to manually control the state. In this example, press :shortcut{value="O"} to toggle the popover.
:component-example{component="popover-example-open"}
@@ -29,10 +29,6 @@ Use the `open` prop to manually control showing the panel.
:component-example{component="popover-example-overlay"}
::callout{icon="i-heroicons-light-bulb"}
Clicking on the `overlay` emits `update:open`. If you are manually controlling the `open` prop, you will need to use a [`v-model` argument](https://vuejs.org/guide/components/v-model.html#v-model-arguments) (`v-model:open`).
::
## Popper
Use the `popper` prop to customize the popper instance.

View File

@@ -1,5 +1,6 @@
<template>
<HPopover ref="popover" v-slot="{ open: headlessOpen, close }" :class="ui.wrapper" v-bind="attrs" @mouseleave="onMouseLeave">
<!-- eslint-disable-next-line vue/no-template-shadow -->
<HPopover ref="popover" v-slot="{ open, close }" :class="ui.wrapper" v-bind="attrs" @mouseleave="onMouseLeave">
<HPopoverButton
ref="trigger"
as="div"
@@ -8,7 +9,7 @@
role="button"
@mouseover="onMouseOver"
>
<slot :open="(open !== undefined) ? open : headlessOpen" :close="close">
<slot :open="open" :close="close">
<button :disabled="disabled">
Open
</button>
@@ -16,16 +17,16 @@
</HPopoverButton>
<Transition v-if="overlay" appear v-bind="ui.overlay.transition">
<div v-if="(open !== undefined) ? open : headlessOpen" :class="[ui.overlay.base, ui.overlay.background]" @click="$emit('update:open')" />
<div v-if="open" :class="[ui.overlay.base, ui.overlay.background]" />
</Transition>
<div v-if="(open !== undefined) ? open : headlessOpen" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
<div v-if="open" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
<Transition appear v-bind="ui.transition">
<div>
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
<HPopoverPanel :class="[ui.base, ui.background, ui.ring, ui.rounded, ui.shadow]" static>
<slot name="panel" :open="(open !== undefined) ? open : headlessOpen" :close="close" />
<slot name="panel" :open="open" :close="close" />
</HPopoverPanel>
</div>
</Transition>
@@ -94,7 +95,7 @@ export default defineComponent({
default: () => ({})
}
},
emits: ['update:open', 'open', 'close'],
emits: ['update:open'],
setup (props, { emit }) {
const { ui, attrs } = useUI('popover', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -102,8 +103,8 @@ export default defineComponent({
const [trigger, container] = usePopper(popper.value)
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
const popover = ref<any>(null)
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
const popoverApi = ref<any>(null)
let openTimeout: NodeJS.Timeout | null = null
@@ -116,6 +117,10 @@ export default defineComponent({
}
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
if (props.open) {
popoverApi.value?.togglePopover()
}
})
const containerStyle = computed(() => {
@@ -170,9 +175,22 @@ export default defineComponent({
}, props.closeDelay)
}
watch(() => props.open, (newValue: boolean, oldValue: boolean) => {
if (!popoverApi.value) return
if (oldValue === undefined || newValue === oldValue) return
if (newValue) {
// No `openPopover` method and `popoverApi.value.togglePopover` won't work because of the `watch` below
popoverApi.value.popoverState = 0
} else {
popoverApi.value.closePopover()
}
})
watch(() => popoverApi.value?.popoverState, (newValue: number, oldValue: number) => {
if (oldValue === undefined) return
emit(newValue === 0 ? 'open' : 'close')
if (oldValue === undefined || newValue === oldValue) return
emit('update:open', newValue === 0)
})
return {