mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
217 lines
5.1 KiB
Vue
217 lines
5.1 KiB
Vue
<template>
|
|
<el-teleport :disabled="!teleported" :to="appendTo">
|
|
<transition
|
|
v-if="shouldRender || !ariaHidden"
|
|
:name="transitionClass"
|
|
:appear="!persistentRef"
|
|
@after-leave="onTransitionLeave"
|
|
@before-enter="onBeforeEnter"
|
|
@after-enter="onAfterShow"
|
|
@before-leave="onBeforeLeave"
|
|
>
|
|
<el-popper-content
|
|
v-show="shouldShow"
|
|
:id="id"
|
|
ref="contentRef"
|
|
v-bind="$attrs"
|
|
:aria-label="ariaLabel"
|
|
:aria-hidden="ariaHidden"
|
|
:boundaries-padding="boundariesPadding"
|
|
:fallback-placements="fallbackPlacements"
|
|
:gpu-acceleration="gpuAcceleration"
|
|
:offset="offset"
|
|
:placement="placement"
|
|
:popper-options="popperOptions"
|
|
:arrow-offset="arrowOffset"
|
|
:strategy="strategy"
|
|
:effect="effect"
|
|
:enterable="enterable"
|
|
:pure="pure"
|
|
:popper-class="popperClass"
|
|
:popper-style="[popperStyle!, contentStyle]"
|
|
:reference-el="referenceEl"
|
|
:trigger-target-el="triggerTargetEl"
|
|
:visible="shouldShow"
|
|
:z-index="zIndex"
|
|
:loop="loop"
|
|
@mouseenter="onContentEnter"
|
|
@mouseleave="onContentLeave"
|
|
@blur="onBlur"
|
|
@close="onClose"
|
|
>
|
|
<slot />
|
|
</el-popper-content>
|
|
</transition>
|
|
</el-teleport>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, inject, onBeforeUnmount, ref, unref, watch } from 'vue'
|
|
import { computedEager, onClickOutside } from '@vueuse/core'
|
|
import { useNamespace, usePopperContainerId } from '@element-plus/hooks'
|
|
import {
|
|
castArray,
|
|
composeEventHandlers,
|
|
focusElement,
|
|
} from '@element-plus/utils'
|
|
import { ElPopperContent } from '@element-plus/components/popper'
|
|
import ElTeleport from '@element-plus/components/teleport'
|
|
import { TOOLTIP_INJECTION_KEY } from './constants'
|
|
import { useTooltipContentProps } from './content'
|
|
|
|
import type { PopperContentInstance } from '@element-plus/components/popper'
|
|
|
|
defineOptions({
|
|
name: 'ElTooltipContent',
|
|
inheritAttrs: false,
|
|
})
|
|
|
|
const props = defineProps(useTooltipContentProps)
|
|
|
|
const { selector } = usePopperContainerId()
|
|
const ns = useNamespace('tooltip')
|
|
|
|
const contentRef = ref<PopperContentInstance>()
|
|
const popperContentRef = computedEager(() => contentRef.value?.popperContentRef)
|
|
let stopHandle: ReturnType<typeof onClickOutside>
|
|
const {
|
|
controlled,
|
|
id,
|
|
open,
|
|
trigger,
|
|
onClose,
|
|
onOpen,
|
|
onShow,
|
|
onHide,
|
|
onBeforeShow,
|
|
onBeforeHide,
|
|
} = inject(TOOLTIP_INJECTION_KEY, undefined)!
|
|
const transitionClass = computed(() => {
|
|
return props.transition || `${ns.namespace.value}-fade-in-linear`
|
|
})
|
|
const persistentRef = computed(() => {
|
|
// For testing, we would always want the content to be rendered
|
|
// to the DOM, so we need to return true here.
|
|
if (process.env.NODE_ENV === 'test') {
|
|
if (!process.env.RUN_TEST_WITH_PERSISTENT) {
|
|
return true
|
|
}
|
|
}
|
|
return props.persistent
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
stopHandle?.()
|
|
})
|
|
|
|
const shouldRender = computed(() => {
|
|
return unref(persistentRef) ? true : unref(open)
|
|
})
|
|
|
|
const shouldShow = computed(() => {
|
|
return props.disabled ? false : unref(open)
|
|
})
|
|
|
|
const appendTo = computed(() => {
|
|
return props.appendTo || selector.value
|
|
})
|
|
|
|
const contentStyle = computed(() => props.style ?? {})
|
|
|
|
const ariaHidden = ref(true)
|
|
|
|
const onTransitionLeave = () => {
|
|
onHide()
|
|
isFocusInsideContent() && focusElement(document.body, { preventScroll: true })
|
|
ariaHidden.value = true
|
|
}
|
|
|
|
const stopWhenControlled = () => {
|
|
if (unref(controlled)) return true
|
|
}
|
|
|
|
const onContentEnter = composeEventHandlers(stopWhenControlled, () => {
|
|
if (props.enterable && unref(trigger) === 'hover') {
|
|
onOpen()
|
|
}
|
|
})
|
|
|
|
const onContentLeave = composeEventHandlers(stopWhenControlled, () => {
|
|
if (unref(trigger) === 'hover') {
|
|
onClose()
|
|
}
|
|
})
|
|
|
|
const onBeforeEnter = () => {
|
|
contentRef.value?.updatePopper?.()
|
|
onBeforeShow?.()
|
|
}
|
|
|
|
const onBeforeLeave = () => {
|
|
onBeforeHide?.()
|
|
}
|
|
|
|
const onAfterShow = () => {
|
|
onShow()
|
|
}
|
|
|
|
const onBlur = () => {
|
|
if (!props.virtualTriggering) {
|
|
onClose()
|
|
}
|
|
}
|
|
|
|
const isFocusInsideContent = (event?: FocusEvent) => {
|
|
const popperContent: HTMLElement | undefined =
|
|
contentRef.value?.popperContentRef
|
|
const activeElement = (event?.relatedTarget as Node) || document.activeElement
|
|
|
|
return popperContent?.contains(activeElement)
|
|
}
|
|
|
|
watch(
|
|
() => unref(open),
|
|
(val) => {
|
|
if (!val) {
|
|
stopHandle?.()
|
|
} else {
|
|
ariaHidden.value = false
|
|
stopHandle = onClickOutside(
|
|
popperContentRef,
|
|
() => {
|
|
if (unref(controlled)) return
|
|
const needClose = castArray(unref(trigger)).every((item) => {
|
|
return item !== 'hover' && item !== 'focus'
|
|
})
|
|
if (needClose) {
|
|
onClose()
|
|
}
|
|
},
|
|
{ detectIframe: true }
|
|
)
|
|
}
|
|
},
|
|
{
|
|
flush: 'post',
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.content,
|
|
() => {
|
|
contentRef.value?.updatePopper?.()
|
|
}
|
|
)
|
|
|
|
defineExpose({
|
|
/**
|
|
* @description el-popper-content component instance
|
|
*/
|
|
contentRef,
|
|
/**
|
|
* @description validate current focus event is trigger inside el-popper-content
|
|
*/
|
|
isFocusInsideContent,
|
|
})
|
|
</script>
|