@ithinkdt/naive
Version:
iThinkDT Naive UI
168 lines (150 loc) • 5.22 kB
JSX
import { defineComponent, h, ref, shallowRef, isVNode, computed, nextTick, mergeProps } from 'vue'
import { NTooltip } from 'ithinkdt-ui'
import { ThemeProvider } from '../frame/naive'
const show = ref(false)
const current = shallowRef()
const props = shallowRef({})
const tip = shallowRef()
const pos = shallowRef([0, 0])
const _tip = computed(() =>
typeof tip.value === 'function'
? tip.value()
: isVNode(tip.value)
? tip.value
: h('span', { innerHTML: tip.value || current.value?.innerHTML }),
)
export const TooltipProvider = defineComponent({
name: 'DtTooltipDirectiveProvider',
setup() {
const hoverd = ref(false)
return () => (
<NTooltip
{...props.value}
trigger="manual"
show={hoverd.value || show.value}
x={pos.value[0]}
y={pos.value[1]}
onMouseenter={() => {
if (props.value.keepAliveOnHover !== false) {
hoverd.value = true
}
}}
onMouseleave={() => {
hoverd.value = false
}}
onClickoutside={() => {
hoverd.value = false
show.value = false
}}
>
<ThemeProvider theme="dark">{_tip.value}</ThemeProvider>
</NTooltip>
)
},
})
function getPlace(modifiers) {
return Object.keys(modifiers).find((m) => ['top', 'right', 'left', 'bottom'].find((k) => m.startsWith(k)))
}
const handle = function (el, binding) {
if (!el.__tooltip) {
let enter, leave
const onEnter = () => {
clearTimer(el)
props.value = mergeProps({ keepAliveOnHover: false }, el.__tooltip.props)
tip.value = el.__tooltip.tip
current.value = el
show.value = true
const rect = el.getBoundingClientRect()
switch ((props.value?.placement || getPlace(el.__tooltip.binding.modifiers) || 'top').split('-')[0]) {
case 'top': {
pos.value = [rect.left + rect.width / 2, rect.top]
break
}
case 'right': {
pos.value = [rect.left + rect.width, rect.top + rect.height / 2]
break
}
case 'left': {
pos.value = [rect.left, rect.top + rect.height / 2]
break
}
case 'bottom': {
pos.value = [rect.left + rect.width / 2, rect.bottom]
break
}
}
}
enter = () => {
if (!el.__tooltip.binding.modifiers.auto) {
onEnter()
} else if (
el.offsetWidth > el.parentElement.clientWidth ||
el.offsetHeight > el.parentElement.clientHeight
) {
el.__tooltip.timer = setTimeout(onEnter, 160)
}
}
leave = async () => {
clearTimer(el)
await nextTick()
return new Promise((resolve) => {
if (el.__tooltip.timer === undefined) {
el.__tooltip.timer2 = setTimeout(() => {
el.__tooltip.timer2 = undefined
if (current.value !== el) return resolve()
show.value = false
el.__tooltip.timer3 = setTimeout(() => {
current.value = undefined
el.__tooltip.timer3 = undefined
resolve()
}, 300)
}, 100)
} else {
resolve()
}
})
}
el.addEventListener('mouseenter', enter, { passive: true })
el.addEventListener('mouseleave', leave, { passive: false })
Object.defineProperty(el, '__tooltip', {
value: {
binding,
tip: undefined,
props: {},
enter,
leave,
timer: undefined,
},
enumerable: false,
})
}
el.__tooltip.binding = binding
if (typeof binding.value === 'string' || typeof binding.value === 'function') {
el.__tooltip.tip = binding.value
} else {
const { tip, ...props } = binding.value || {}
el.__tooltip.tip = tip
el.__tooltip.props = props
}
}
export const vTooltip = {
mounted: handle,
updated: handle,
beforeUnmount(el) {
el?.removeEventListener('mouseenter', el.__tooltip?.enter)
el?.removeEventListener('mouseleave', el.__tooltip?.leave)
clearTimer(el)
if (current.value == el) {
show.value = false
current.value = undefined
}
},
}
const clearTimer = (el) => {
for (let t of ['timer', 'timer2', 'timer3']) {
if (el.__tooltip?.[t]) {
clearTimeout(el.__tooltip[t])
el.__tooltip[t] = undefined
}
}
}