I am trying to make a ToolTip directive. When I hover over my tooltip component I get this error:
Uncaught TypeError: instance.showTooltip is not a function
show tooltip.js:20
mounted tooltip.js:26
callWithErrorHandling runtime-core.esm-bundler.js:195
callWithAsyncErrorHandling runtime-core.esm-bundler.js:202
invokeDirectiveHook runtime-core.esm-bundler.js:2105
mountElement runtime-core.esm-bundler.js:5550
flushPostFlushCbs runtime-core.esm-bundler.js:370
flushJobs runtime-core.esm-bundler.js:408
promise callback*queueFlush runtime-core.esm-bundler.js:311
queueJob runtime-core.esm-bundler.js:305
scheduler runtime-core.esm-bundler.js:1968
resetScheduling reactivity.esm-bundler.js:264
trigger reactivity.esm-bundler.js:404
set reactivity.esm-bundler.js:525
set value reactivity.esm-bundler.js:1191
set reactivity.esm-bundler.js:514
set pinia.mjs:929
searchTracks tracks.js:30
wrapAction pinia.mjs:1380
actionName pinia.mjs:935
fetchTracks requests.js:12
setup Hero.vue:55
createHook runtime-core.esm-bundler.js:2865
callWithErrorHandling runtime-core.esm-bundler.js:195
callWithAsyncErrorHandling runtime-core.esm-bundler.js:202
__weh runtime-core.esm-bundler.js:2845
flushPostFlushCbs runtime-core.esm-bundler.js:370
render2 runtime-core.esm-bundler.js:6669
mount runtime-core.esm-bundler.js:3921
mount runtime-dom.esm-bundler.js:1511
<anonymous> main.js:37
promise callback* main.js:32
It looks like the way the showTooltip
and hideTooltip
function are exposed from the ToolTip definition isn’t really working.
Here is the ToolTip component:
<template>
<div v-if="visible" :class="tooltipClasses" :style="style">
{{ text }}
</div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const props = defineProps({
text: {
type: String,
required: true
},
position: {
type: String,
default: 'top'
}
})
const visible = ref(true)
const style = ref({})
const showTooltip = (event) => {
visible.value = true
const { top, left, width, height } = event.target.getBoundingClientRect()
const tooltipHeight = 30 // Approximate height of the tooltip
switch (props.position) {
case 'top':
style.value = {
left: `${left + width / 2}px`,
top: `${top - tooltipHeight}px`,
transform: 'translateX(-50%)'
}
break
case 'bottom':
style.value = {
left: `${left + width / 2}px`,
top: `${top + height + tooltipHeight}px`,
transform: 'translateX(-50%)'
}
break
case 'left':
style.value = {
left: `${left - tooltipHeight}px`,
top: `${top + height / 2}px`,
transform: 'translateY(-50%)'
}
break
case 'right':
style.value = {
left: `${left + width + tooltipHeight}px`,
top: `${top + height / 2}px`,
transform: 'translateY(-50%)'
}
break
}
}
const hideTooltip = () => {
visible.value = false
}
// Expose the methods so they can be called from outside
defineExpose({
showTooltip,
hideTooltip
})
const tooltipClasses = ref("absolute top-2 left-2 bg-black text-white text-sm px-2 py-1 rounded shadow-lg pointer-events-none z-50")
</script>
<style scoped>
.tooltip {
transition: opacity 0.3s ease;
}
</style>
Here is the directive definition:
import { createApp, h } from 'vue'
import Tooltip from '@/components/Tooltip.vue'
const TooltipDirective = {
mounted(el, binding) {
const app = createApp({
render() {
return h(Tooltip, {
text: binding.value,
position: binding.arg || 'top'
})
}
})
const instance = app.mount(document.createElement('div'))
document.body.appendChild(instance.$el)
const show = (event) => {
instance.showTooltip(event)
}
const hide = () => {
instance.hideTooltip()
}
el.addEventListener('mouseenter', show)
el.addEventListener('mouseleave', hide)
el._tooltip = {
instance,
show,
hide
}
},
unmounted(el) {
const { instance, show, hide } = el._tooltip
el.removeEventListener('mouseenter', show)
el.removeEventListener('mouseleave', hide)
document.body.removeChild(instance.$el)
}
}
export default TooltipDirective
This is the ToolTip in use:
<div v-tooltip="'This is a tooltip kragh'" class="relative bg-blue-500 text-white p-2 rounded">
Hover me !!
</div>