element3
Version:
A Component Library for Vue3
280 lines (256 loc) • 6.88 kB
JavaScript
import { vuePopperProps, useVuePopper } from '../popover/vue-popper'
import { debounce } from 'throttle-debounce'
import { addClass, removeClass, on, off } from '../../src/utils/dom'
import { generateId } from '../../src/utils/util'
// eslint-disable-next-line
import {
createApp,
ref,
watch,
onMounted,
onUnmounted,
getCurrentInstance,
Transition,
onBeforeMount
} from 'vue'
export default {
name: 'ElTooltip',
emits: ['input', 'update:modelValue', 'created'],
props: {
...vuePopperProps,
openDelay: {
type: Number,
default: 0
},
disabled: Boolean,
manual: Boolean,
effect: {
type: String,
default: 'dark'
},
arrowOffset: {
type: Number,
default: 0
},
popperClass: String,
content: {
type: String,
default: 'dark'
},
visibleArrow: {
default: true
},
transition: {
type: String,
default: 'el-fade-in-linear'
},
popperOptions: {
default() {
return {
boundariesPadding: 10,
gpuAcceleration: false
}
}
},
enterable: {
type: Boolean,
default: true
},
hideAfter: {
type: Number,
default: 0
},
tabindex: {
type: Number,
default: 0
}
},
beforeCreate() {
if (this.$isServer) return
this.popperVM = createApp({
data() {
return {
node: ''
}
},
render() {
return this.node
}
}).mount(document.createElement('div'))
},
setup(props, context) {
const timeoutPending = ref(null)
const timeout = ref(null)
const focusing = ref(false)
const expectedState = ref(false)
const referenceElm = ref(null)
const { emit, slots } = context
const {
modelValue,
openDelay,
disabled,
manual,
effect,
popperClass,
transition,
enterable,
hideAfter,
tabindex
} = props
const { showPopper, updatePopper, doDestroy } = useVuePopper(props, {
emit,
slots,
referenceEl: referenceElm
})
const instance = getCurrentInstance()
const tooltipId = `el-tooltip-${generateId()}`
const debounceClose = debounce(200, () => handleClosePopper())
const show = () => {
setExpectedState(true)
handleShowPopper()
}
const hide = () => {
setExpectedState(false)
debounceClose()
}
const handleFocus = () => {
focusing.value = true
show()
}
const handleBlur = () => {
focusing.value = false
hide()
}
const removeFocusing = () => {
focusing.value = false
}
const addTooltipClass = (prev) => {
if (!prev) {
return 'el-tooltip'
} else {
return 'el-tooltip ' + prev.replace('el-tooltip', '')
}
}
const handleShowPopper = () => {
if (!expectedState.value || manual) return
clearTimeout(timeout.value)
timeout.value = setTimeout(() => {
showPopper.value = true
}, openDelay)
if (hideAfter > 0) {
timeoutPending.value = setTimeout(() => {
showPopper.value = false
}, hideAfter)
}
}
const handleClosePopper = () => {
if ((enterable && expectedState.value) || manual) return
clearTimeout(timeout.value)
if (timeoutPending.value) {
clearTimeout(timeoutPending.value)
}
showPopper.value = false
if (disabled) {
doDestroy()
}
}
const setExpectedState = (state) => {
if (state === false) {
clearTimeout(timeoutPending.value)
}
expectedState.value = state
}
const getFirstElement = () => {
const slotsDefault = slots.default()
if (!Array.isArray(slotsDefault)) return null
let element = null
for (let index = 0; index < slotsDefault.length; index++) {
if (slotsDefault[index] && slotsDefault[index].type) {
element = slotsDefault[index]
}
}
return element
}
watch(focusing, (val) => {
if (val) {
addClass(referenceElm.value, 'focusing')
} else {
removeClass(referenceElm.value, 'focusing')
}
})
onMounted(() => {
referenceElm.value = instance.proxy.$el
if (referenceElm.value.nodeType === 1) {
referenceElm.value.setAttribute('aria-describedby', tooltipId)
referenceElm.value.setAttribute('tabindex', tabindex)
on(referenceElm.value, 'mouseenter', show)
on(referenceElm.value, 'mouseleave', hide)
on(referenceElm.value, 'focus', () => {
if (!slots.default || !slots.default().length) {
handleFocus()
return
}
const slotsProps = slots.default()[0].props
if (slotsProps && slotsProps.onFocus) {
slotsProps.onFocus()
} else {
handleFocus()
}
})
on(referenceElm.value, 'blur', handleBlur)
on(referenceElm.value, 'click', removeFocusing)
}
// fix issue https://github.com/ElemeFE/element/issues/14424
if (modelValue && instance.proxy.popperVM) {
instance.proxy.popperVM.$nextTick(() => {
if (modelValue) {
updatePopper()
}
})
}
})
onBeforeMount(() => {
instance.proxy.updatePopper = updatePopper
})
onUnmounted(() => {
const reference = referenceElm.value
if (reference.nodeType === 1) {
off(reference, 'mouseenter', show)
off(reference, 'mouseleave', hide)
off(reference, 'focus', handleFocus)
off(reference, 'blur', handleBlur)
off(reference, 'click', removeFocusing)
}
})
return () => {
if (instance.proxy.popperVM) {
instance.proxy.popperVM.node = (
<Transition name={transition} onAfterLeave={doDestroy}>
<div
onMouseleave={() => {
setExpectedState(false)
debounceClose()
}}
onMouseenter={() => {
setExpectedState(true)
}}
ref="popper"
role="tooltip"
id={tooltipId}
aria-hidden={disabled || !showPopper.value ? 'true' : 'false'}
v-show={!disabled && showPopper.value}
class={['el-tooltip__popper', 'is-' + effect, popperClass]}
>
{slots.content ? slots.content() : instance.proxy.content}
</div>
</Transition>
)
}
const firstElement = getFirstElement()
if (!firstElement) return null
const firstElementProps = (firstElement.props = firstElement.props || {})
firstElementProps.class = addTooltipClass(firstElementProps.class)
return firstElement
}
}
}