| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- // useMouseTooltip.js
- let tooltipEl = null
- let currentTarget = null
- let observer = null
- let isInitialized = false
- let moveHandler = null
- let overHandler = null
- let outHandler = null
- let pointerDownHandler = null
- let rafId = null
- let showTimer = null
- let hideTimer = null
- let lastMouseX = 0
- let lastMouseY = 0
- export function useMouseTooltip() {
- const PADDING = 16
- const OFFSET = 15
- const SHOW_DELAY = 300
- const HIDE_DELAY = 80
- function createTooltip() {
- if (tooltipEl) return
- tooltipEl = document.createElement('div')
- tooltipEl.className = 'cursor-pointer-tooltip'
- tooltipEl.style.cssText = `
- position: fixed;
- z-index: 2147483647;
- pointer-events: none;
- left: 0;
- top: 0;
- background: rgba(var(--bs-body-bg-rgb),1);
- color: var(--bs-emphasis-color);
- border-radius: .5rem;
- box-shadow: 0 0 10px rgba(0,0,0,.1);
- font-size: .8125rem;
- line-height: 1.4;
- padding: .375rem .5rem;
- white-space: nowrap;
- opacity: 0;
- visibility: hidden;
- transform: scale(.85);
- transition:
- opacity .18s ease,
- transform .18s ease;
- will-change:
- opacity,
- transform,
- left,
- top;
- `
- document.body.appendChild(tooltipEl)
- }
- function getTooltipSize() {
- if (!tooltipEl) {
- return {
- width: 0,
- height: 0
- }
- }
- const rect = tooltipEl.getBoundingClientRect()
- return {
- width: rect.width,
- height: rect.height
- }
- }
- function updatePosition(clientX, clientY) {
- if (!tooltipEl) return
- const viewportWidth = window.innerWidth
- const viewportHeight = window.innerHeight
- const { width, height } = getTooltipSize()
- let x = clientX + OFFSET
- let y = clientY + OFFSET
- if (x + width > viewportWidth - PADDING) {
- x = clientX - width - OFFSET
- }
- if (y + height > viewportHeight - PADDING) {
- y = clientY - height - OFFSET
- }
- x = Math.max(PADDING, x)
- y = Math.max(PADDING, y)
- tooltipEl.style.left = `${x}px`
- tooltipEl.style.top = `${y}px`
- }
- function disconnectObserver() {
- if (observer) {
- observer.disconnect()
- observer = null
- }
- }
- function watchTooltip(target) {
- disconnectObserver()
- observer = new MutationObserver(() => {
- if (
- !tooltipEl ||
- currentTarget !== target
- ) {
- return
- }
- tooltipEl.textContent =
- target.dataset.tooltip || ''
- updatePosition(
- lastMouseX,
- lastMouseY
- )
- })
- observer.observe(target, {
- attributes: true,
- attributeFilter: ['data-tooltip']
- })
- }
- function forceHideTooltip() {
- disconnectObserver()
- if (showTimer) {
- clearTimeout(showTimer)
- showTimer = null
- }
- if (hideTimer) {
- clearTimeout(hideTimer)
- hideTimer = null
- }
- currentTarget = null
- if (!tooltipEl) return
- tooltipEl.style.opacity = '0'
- tooltipEl.style.visibility = 'hidden'
- tooltipEl.style.transform = 'scale(.85)'
- }
- function showTooltip(e, target) {
- const text =
- target.dataset.tooltip || ''
- if (!text) return
- createTooltip()
- if (hideTimer) {
- clearTimeout(hideTimer)
- hideTimer = null
- }
- if (showTimer) {
- clearTimeout(showTimer)
- showTimer = null
- }
- currentTarget = target
- lastMouseX = e.clientX
- lastMouseY = e.clientY
- tooltipEl.textContent = text
- watchTooltip(target)
- updatePosition(
- lastMouseX,
- lastMouseY
- )
- tooltipEl.style.opacity = '0'
- tooltipEl.style.visibility = 'hidden'
- tooltipEl.style.transform = 'scale(.85)'
- showTimer = setTimeout(() => {
- if (
- !currentTarget ||
- !document.contains(currentTarget)
- ) {
- forceHideTooltip()
- return
- }
- if (currentTarget !== target) {
- return
- }
- tooltipEl.style.visibility =
- 'visible'
- requestAnimationFrame(() => {
- tooltipEl.style.opacity = '1'
- tooltipEl.style.transform =
- 'scale(1)'
- })
- }, SHOW_DELAY)
- }
- function hideTooltip() {
- if (!tooltipEl) return
- disconnectObserver()
- if (showTimer) {
- clearTimeout(showTimer)
- showTimer = null
- }
- if (hideTimer) {
- clearTimeout(hideTimer)
- }
- hideTimer = setTimeout(() => {
- tooltipEl.style.opacity = '0'
- tooltipEl.style.transform =
- 'scale(.85)'
- currentTarget = null
- setTimeout(() => {
- if (
- !tooltipEl ||
- currentTarget
- ) {
- return
- }
- tooltipEl.style.visibility =
- 'hidden'
- }, 180)
- }, HIDE_DELAY)
- }
- function init() {
- if (isInitialized) return
- isInitialized = true
- moveHandler = (e) => {
- if (
- !currentTarget ||
- !tooltipEl
- ) {
- return
- }
- if (
- !document.contains(currentTarget)
- ) {
- forceHideTooltip()
- return
- }
- lastMouseX = e.clientX
- lastMouseY = e.clientY
- if (rafId) return
- rafId = requestAnimationFrame(() => {
- rafId = null
- if (
- !currentTarget ||
- !document.contains(currentTarget)
- ) {
- forceHideTooltip()
- return
- }
- updatePosition(
- lastMouseX,
- lastMouseY
- )
- })
- }
- overHandler = (e) => {
- const target =
- e.target.closest(
- '.cursor-pointer'
- )
- if (!target) return
- if (
- target === currentTarget
- ) {
- return
- }
- showTooltip(e, target)
- }
- outHandler = (e) => {
- const target =
- e.target.closest(
- '.cursor-pointer'
- )
- if (!target) return
- if (
- target !== currentTarget
- ) {
- return
- }
- if (
- e.relatedTarget &&
- target.contains(
- e.relatedTarget
- )
- ) {
- return
- }
- hideTooltip()
- }
- pointerDownHandler = () => {
- forceHideTooltip()
- }
- document.addEventListener(
- 'mouseover',
- overHandler,
- true
- )
- document.addEventListener(
- 'mouseout',
- outHandler,
- true
- )
- document.addEventListener(
- 'mousemove',
- moveHandler,
- true
- )
- document.addEventListener(
- 'pointerdown',
- pointerDownHandler,
- true
- )
- }
- function cleanup() {
- forceHideTooltip()
- if (rafId) {
- cancelAnimationFrame(rafId)
- rafId = null
- }
- if (moveHandler) {
- document.removeEventListener(
- 'mousemove',
- moveHandler,
- true
- )
- }
- if (overHandler) {
- document.removeEventListener(
- 'mouseover',
- overHandler,
- true
- )
- }
- if (outHandler) {
- document.removeEventListener(
- 'mouseout',
- outHandler,
- true
- )
- }
- if (pointerDownHandler) {
- document.removeEventListener(
- 'pointerdown',
- pointerDownHandler,
- true
- )
- }
- moveHandler = null
- overHandler = null
- outHandler = null
- pointerDownHandler = null
- currentTarget = null
- isInitialized = false
- }
- init()
- return cleanup
- }
|