forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2const props = defineProps<{
3 /** Tooltip text (optional when using content slot) */
4 text?: string
5 /** Position: 'top' | 'bottom' | 'left' | 'right' */
6 position?: 'top' | 'bottom' | 'left' | 'right'
7 /** Enable interactive tooltip (pointer events + hide delay for clickable content) */
8 interactive?: boolean
9 /** Teleport target for the tooltip content (defaults to 'body') */
10 to?: string | HTMLElement
11 /** Whether to defer teleport rendering until after the component is mounted */
12 defer?: boolean
13 /** Offset distance in pixels (default: 4) */
14 offset?: number
15}>()
16
17const isVisible = shallowRef(false)
18const tooltipId = useId()
19const hideTimeout = shallowRef<ReturnType<typeof setTimeout> | null>(null)
20
21function show() {
22 if (hideTimeout.value) {
23 clearTimeout(hideTimeout.value)
24 hideTimeout.value = null
25 }
26 isVisible.value = true
27}
28
29function hide() {
30 if (props.interactive) {
31 // Delay hide so cursor can travel from trigger to tooltip
32 hideTimeout.value = setTimeout(() => {
33 isVisible.value = false
34 }, 150)
35 } else {
36 isVisible.value = false
37 }
38}
39
40const tooltipAttrs = computed(() => {
41 const attrs: Record<string, unknown> = { role: 'tooltip', id: tooltipId }
42 if (props.interactive) {
43 attrs.onMouseenter = show
44 attrs.onMouseleave = hide
45 }
46 return attrs
47})
48</script>
49
50<template>
51 <TooltipBase
52 :text
53 :isVisible
54 :position
55 :interactive
56 :to
57 :defer
58 :offset
59 :tooltip-attr="tooltipAttrs"
60 @mouseenter="show"
61 @mouseleave="hide"
62 @focusin="show"
63 @focusout="hide"
64 :aria-describedby="isVisible ? tooltipId : undefined"
65 >
66 <slot />
67 <template v-if="$slots.content" #content>
68 <slot name="content" />
69 </template>
70 </TooltipBase>
71</template>