Skip to content

Commit

Permalink
feat: enhance popover reference
Browse files Browse the repository at this point in the history
  • Loading branch information
haoziqaq committed Dec 3, 2024
1 parent 9cf6783 commit 5ca38fb
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 73 deletions.
9 changes: 8 additions & 1 deletion packages/varlet-ui/src/menu-select/MenuSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ import { props } from './props'
import { createNamespace, formatElevation } from '../utils/components'
import { useMenuOptions, type MenuSelectProvider } from './provide'
import { useSelectController } from '../select/useSelectController'
import { type MenuOptionProvider } from '../menu-option/provide'
import { call, preventDefault } from '@varlet/shared'
import { useEventListener, useVModel } from '@varlet/use'
import { focusChildElementByKey } from '../utils/elements'
import { type MenuOptionProvider } from '../menu-option/provide'
import { type Reference } from '../menu/usePopover'
const { name, n, classes } = createNamespace('menu-select')
Expand Down Expand Up @@ -139,6 +140,11 @@ export default defineComponent({
menu.value?.resize()
}
// expose
function setReference(reference: Reference) {
menu.value?.setReference(reference)
}
return {
show,
menu,
Expand All @@ -149,6 +155,7 @@ export default defineComponent({
open,
close,
resize,
setReference,
}
},
})
Expand Down
23 changes: 7 additions & 16 deletions packages/varlet-ui/src/menu/Menu.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<template>
<div
ref="host"
:class="classes(n(), n('$--box'))"
@click="handleHostClick"
@mouseenter="handleHostMouseenter"
@mouseleave="handleHostMouseleave"
>
<div ref="host" :class="classes(n(), n('$--box'))">
<slot />

<Teleport :to="teleport === false ? undefined : teleport" :disabled="teleportDisabled || teleport === false">
Expand All @@ -14,7 +8,7 @@
ref="popover"
:style="{
zIndex,
width: sameWidth ? toSizeUnit(Math.ceil(hostSize.width)) : undefined,
width: sameWidth ? toSizeUnit(Math.ceil(referenceSize.width)) : undefined,
}"
:class="
classes(
Expand Down Expand Up @@ -54,12 +48,9 @@ export default defineComponent({
const {
popover,
host,
hostSize,
referenceSize,
show,
zIndex,
handleHostClick,
handleHostMouseenter,
handleHostMouseleave,
handlePopoverMouseenter,
handlePopoverMouseleave,
handlePopoverClose,
Expand All @@ -70,29 +61,29 @@ export default defineComponent({
close,
// expose
resize,
// expose
setReference,
} = usePopover(props)
return {
popover,
host,
hostSize,
referenceSize,
show,
zIndex,
teleportDisabled,
formatElevation,
toSizeUnit,
n,
classes,
handleHostClick,
handleHostMouseenter,
handleHostMouseleave,
handlePopoverMouseenter,
handlePopoverMouseleave,
handlePopoverClose,
handleClosed,
resize,
open,
close,
setReference,
}
},
})
Expand Down
4 changes: 2 additions & 2 deletions packages/varlet-ui/src/menu/props.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type PropType, type TeleportProps } from 'vue'
import { type Placement, type Trigger } from './usePopover'
import { type Placement, type Trigger, type Reference } from './usePopover'
import { type PositioningStrategy } from '@popperjs/core'
import { defineListenerProp } from '../utils/components'

Expand All @@ -10,7 +10,7 @@ export const props = {
type: String as PropType<Trigger>,
default: 'click',
},
reference: String,
reference: [String, Object] as PropType<Reference>,
placement: {
type: String as PropType<Placement>,
default: 'cover-top-start',
Expand Down
109 changes: 71 additions & 38 deletions packages/varlet-ui/src/menu/usePopover.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import flip from '@popperjs/core/lib/modifiers/flip.js'
import offset from '@popperjs/core/lib/modifiers/offset.js'
import computeStyles from '@popperjs/core/lib/modifiers/computeStyles.js'
import { onWindowResize, useClickOutside, useEventListener, useVModel } from '@varlet/use'
import { doubleRaf, getStyle, call, preventDefault } from '@varlet/shared'
import { onWindowResize, useEventListener, useVModel } from '@varlet/use'
import { doubleRaf, getStyle, call, preventDefault, isString } from '@varlet/shared'
import { toPxNum } from '../utils/elements'
import { type ListenerProp } from '../utils/components'
import { onMounted, onUnmounted, ref, watch, type Ref } from 'vue'
Expand All @@ -20,11 +20,13 @@ export interface Position {
distance: number
}

export interface HostSize {
export interface ReferenceSize {
width: number
height: number
}

export type Reference = string | HTMLElement

export type Placement =
| NeededPopperPlacement
| 'cover-top'
Expand All @@ -46,7 +48,7 @@ export interface UsePopoverOptions {
disabled: boolean
offsetX: string | number
offsetY: string | number
reference?: string
reference?: string | HTMLElement
closeOnClickReference?: boolean
closeOnKeyEscape?: boolean
onOpen?: ListenerProp<() => void>
Expand All @@ -59,7 +61,7 @@ export interface UsePopoverOptions {
export function usePopover(options: UsePopoverOptions) {
const host: Ref<null | HTMLElement> = ref(null)
const popover: Ref<null | HTMLElement> = ref(null)
const hostSize: Ref<HostSize> = ref({ width: 0, height: 0 })
const referenceSize: Ref<ReferenceSize> = ref({ width: 0, height: 0 })
const show = useVModel(options, 'show', {
passive: true,
defaultValue: false,
Expand All @@ -76,11 +78,11 @@ export function usePopover(options: UsePopoverOptions) {
useStack(() => show.value, zIndex)

let popoverInstance: Instance | null = null
let reference: string | HTMLElement
let enterPopover = false
let enterHost = false

useEventListener(() => window, 'keydown', handleKeydown)
useClickOutside(getReference, 'click', handleClickOutside)
onWindowResize(resize)
watch(() => [options.offsetX, options.offsetY, options.placement, options.strategy], resize)
watch(() => options.disabled, close)
Expand All @@ -93,21 +95,38 @@ export function usePopover(options: UsePopoverOptions) {
}
)

onMounted(() => {
popoverInstance = createPopper(getReference() ?? host.value!, popover.value!, getPopperOptions())
})
onUnmounted(() => {
onMounted(createPopperInstance)
onUnmounted(destroyPopperInstance)

function createPopperInstance() {
const reference = getReference()!

popoverInstance = createPopper(reference, popover.value!, getPopperOptions())
reference.addEventListener('mouseenter', handleHostMouseenter)
reference.addEventListener('mouseleave', handleHostMouseleave)
reference.addEventListener('click', handleReferenceClick)
document.addEventListener('click', handleClickOutside)
}

function destroyPopperInstance() {
const reference = getReference()!

popoverInstance!.destroy()
})
reference.removeEventListener('mouseenter', handleHostMouseenter)
reference.removeEventListener('mouseleave', handleHostMouseleave)
reference.removeEventListener('click', handleReferenceClick)
document.removeEventListener('click', handleClickOutside)
}

function computeHostSize() {
if (!host.value) {
function computeReferenceSize() {
const reference = getReference()
if (!reference) {
return
}

const { width, height } = getStyle(host.value)
const { width, height } = getStyle(reference)

hostSize.value = {
referenceSize.value = {
width: toPxNum(width),
height: toPxNum(height),
}
Expand Down Expand Up @@ -203,7 +222,7 @@ export function usePopover(options: UsePopoverOptions) {
close()
}

function handleHostClick() {
function handleReferenceClick() {
if (options.trigger !== 'click') {
return
}
Expand All @@ -216,17 +235,21 @@ export function usePopover(options: UsePopoverOptions) {
open()
}

function handlePopoverClose() {
close()
}

function handleClickOutside(e: Event) {
if (options.trigger !== 'click') {
return
const reference = getReference()

if (reference && !reference.contains(e.target as Node)) {
if (options.trigger !== 'click') {
return
}

handlePopoverClose()
call(options.onClickOutside, e)
}
}

handlePopoverClose()
call(options.onClickOutside, e)
function handlePopoverClose() {
close()
}

function handleClosed() {
Expand All @@ -237,7 +260,7 @@ export function usePopover(options: UsePopoverOptions) {
function getPosition(): Position {
const { offsetX, offsetY, placement } = options

computeHostSize()
computeReferenceSize()

const offset = {
x: toPxNum(offsetX),
Expand All @@ -249,56 +272,56 @@ export function usePopover(options: UsePopoverOptions) {
return {
placement: 'bottom',
skidding: offset.x,
distance: offset.y - hostSize.value.height,
distance: offset.y - referenceSize.value.height,
}

case 'cover-top-start':
return {
placement: 'bottom-start',
skidding: offset.x,
distance: offset.y - hostSize.value.height,
distance: offset.y - referenceSize.value.height,
}

case 'cover-top-end':
return {
placement: 'bottom-end',
skidding: offset.x,
distance: offset.y - hostSize.value.height,
distance: offset.y - referenceSize.value.height,
}

case 'cover-bottom':
return {
placement: 'top',
skidding: offset.x,
distance: -offset.y - hostSize.value.height,
distance: -offset.y - referenceSize.value.height,
}

case 'cover-bottom-start':
return {
placement: 'top-start',
skidding: offset.x,
distance: -offset.y - hostSize.value.height,
distance: -offset.y - referenceSize.value.height,
}

case 'cover-bottom-end':
return {
placement: 'top-end',
skidding: offset.x,
distance: -offset.y - hostSize.value.height,
distance: -offset.y - referenceSize.value.height,
}

case 'cover-left':
return {
placement: 'right',
skidding: offset.y,
distance: offset.x - hostSize.value.width,
distance: offset.x - referenceSize.value.width,
}

case 'cover-right':
return {
placement: 'left',
skidding: offset.y,
distance: -offset.x - hostSize.value.width,
distance: -offset.x - referenceSize.value.width,
}

case 'left':
Expand Down Expand Up @@ -378,7 +401,19 @@ export function usePopover(options: UsePopoverOptions) {
}

function getReference() {
return options.reference ? host.value!.querySelector(options.reference)! : host.value!
const targetReference = reference ?? options.reference ?? host.value

if (isString(targetReference)) {
return host.value?.querySelector(targetReference)
}

return targetReference
}

function setReference(newReference: Reference) {
destroyPopperInstance()
reference = newReference
createPopperInstance()
}

function handleKeydown(event: KeyboardEvent) {
Expand Down Expand Up @@ -415,14 +450,12 @@ export function usePopover(options: UsePopoverOptions) {
popover,
zIndex,
host,
hostSize,
handleHostClick,
handleHostMouseenter,
handleHostMouseleave,
referenceSize,
handlePopoverClose,
handlePopoverMouseenter,
handlePopoverMouseleave,
handleClosed,
setReference,
resize,
open,
close,
Expand Down
Loading

0 comments on commit 5ca38fb

Please sign in to comment.