167 lines
4.5 KiB
Vue
167 lines
4.5 KiB
Vue
<template>
|
|
<div ref="box" @mouseover="mouseover" @mouseleave="mouseleave">
|
|
<slot />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
text: {
|
|
type: [String, Number],
|
|
required: true
|
|
},
|
|
direction: {
|
|
type: String,
|
|
default: 'right'
|
|
},
|
|
/**
|
|
* Delay showing the tooltip after X milliseconds of hovering
|
|
*/
|
|
delayOnShow: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
disabled: Boolean
|
|
},
|
|
data() {
|
|
return {
|
|
tooltip: null,
|
|
tooltipId: null,
|
|
isShowing: false,
|
|
hideTimeout: null,
|
|
delayOnShowTimeout: null
|
|
}
|
|
},
|
|
watch: {
|
|
text() {
|
|
this.updateText()
|
|
},
|
|
disabled(newVal) {
|
|
if (newVal && this.isShowing) {
|
|
this.hideTooltip()
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
updateText() {
|
|
if (this.tooltip) {
|
|
this.tooltip.innerHTML = this.text
|
|
this.setTooltipPosition(this.tooltip)
|
|
}
|
|
},
|
|
createTooltip() {
|
|
if (!this.$refs.box) return
|
|
var tooltip = document.createElement('div')
|
|
this.tooltipId = String(Math.floor(Math.random() * 10000))
|
|
tooltip.id = this.tooltipId
|
|
tooltip.className = 'tooltip-wrapper absolute px-2 py-1 text-white text-xs rounded shadow-lg max-w-xs text-center hidden sm:block'
|
|
tooltip.style.zIndex = 100
|
|
tooltip.style.backgroundColor = 'rgba(0,0,0,0.85)'
|
|
tooltip.innerHTML = this.text
|
|
tooltip.addEventListener('mouseover', this.cancelHide)
|
|
tooltip.addEventListener('mouseleave', this.hideTooltip)
|
|
|
|
this.setTooltipPosition(tooltip)
|
|
|
|
this.tooltip = tooltip
|
|
},
|
|
setTooltipPosition(tooltip) {
|
|
const boxRect = this.$refs.box.getBoundingClientRect()
|
|
|
|
const shouldMount = !tooltip.isConnected
|
|
|
|
// Calculate size of tooltip
|
|
if (shouldMount) document.body.appendChild(tooltip)
|
|
const tooltipRect = tooltip.getBoundingClientRect()
|
|
if (shouldMount) tooltip.remove()
|
|
|
|
// Subtracting scrollbar size
|
|
const windowHeight = window.innerHeight - 8
|
|
const windowWidth = window.innerWidth - 8
|
|
|
|
let top = 0
|
|
let left = 0
|
|
if (this.direction === 'right') {
|
|
top = Math.max(0, boxRect.top - tooltipRect.height / 2 + boxRect.height / 2)
|
|
left = Math.max(0, boxRect.left + boxRect.width + 4)
|
|
} else if (this.direction === 'bottom') {
|
|
top = Math.max(0, boxRect.top + boxRect.height + 4)
|
|
left = Math.max(0, boxRect.left - tooltipRect.width / 2 + boxRect.width / 2)
|
|
} else if (this.direction === 'top') {
|
|
top = Math.max(0, boxRect.top - tooltipRect.height - 4)
|
|
left = Math.max(0, boxRect.left - tooltipRect.width / 2 + boxRect.width / 2)
|
|
} else if (this.direction === 'left') {
|
|
top = Math.max(0, boxRect.top - tooltipRect.height / 2 + boxRect.height / 2)
|
|
left = Math.max(0, boxRect.left - tooltipRect.width - 4)
|
|
}
|
|
|
|
// Shift left if tooltip would overflow the window on the right
|
|
if (left + tooltipRect.width > windowWidth) {
|
|
left -= left + tooltipRect.width - windowWidth
|
|
}
|
|
// Shift up if tooltip would overflow the window on the bottom
|
|
if (top + tooltipRect.height > windowHeight) {
|
|
top -= top + tooltipRect.height - windowHeight
|
|
}
|
|
|
|
tooltip.style.top = top + 'px'
|
|
tooltip.style.left = left + 'px'
|
|
},
|
|
showTooltip() {
|
|
if (this.disabled) return
|
|
if (!this.tooltip) {
|
|
this.createTooltip()
|
|
if (!this.tooltip) return
|
|
}
|
|
if (!this.$refs.box) return // Ensure element is not destroyed
|
|
try {
|
|
document.body.appendChild(this.tooltip)
|
|
this.setTooltipPosition(this.tooltip)
|
|
} catch (error) {
|
|
console.error(error)
|
|
}
|
|
|
|
this.isShowing = true
|
|
},
|
|
hideTooltip() {
|
|
if (!this.tooltip) return
|
|
this.tooltip.remove()
|
|
this.isShowing = false
|
|
},
|
|
cancelHide() {
|
|
clearTimeout(this.hideTimeout)
|
|
},
|
|
mouseover() {
|
|
if (this.isShowing || this.disabled) return
|
|
|
|
if (this.delayOnShow) {
|
|
if (this.delayOnShowTimeout) {
|
|
// Delay already running
|
|
return
|
|
}
|
|
|
|
this.delayOnShowTimeout = setTimeout(() => {
|
|
this.showTooltip()
|
|
this.delayOnShowTimeout = null
|
|
}, this.delayOnShow)
|
|
} else {
|
|
this.showTooltip()
|
|
}
|
|
},
|
|
mouseleave() {
|
|
if (!this.isShowing) {
|
|
clearTimeout(this.delayOnShowTimeout)
|
|
this.delayOnShowTimeout = null
|
|
return
|
|
}
|
|
|
|
this.hideTimeout = setTimeout(this.hideTooltip, 100)
|
|
}
|
|
},
|
|
beforeDestroy() {
|
|
this.hideTooltip()
|
|
}
|
|
}
|
|
</script>
|