GlobalTooltip: delay 중 마우스 벗어나면 title 복원 안 되던 버그 수정
showFor 가 호출되면 title 속성을 즉시 제거하고 titleMap 에 저장한 뒤 setTimeout 으로 지연. 지연 중에 마우스가 벗어나 handleOut 가 호출되어도 triggerRef 는 아직 null 이라 hide() 가 title 을 복원하지 못해 해당 요소의 title 이 영구 제거되고 이후 [title] 선택자에 걸리지 않아 툴팁이 안 뜸. - pendingRef 추가해 "delay 중 title 제거된 타겟" 추적 - hide / showFor / handleOver / handleOut 모두 pendingRef 고려해 복원 - 타이머 중 다른 타겟으로 이동 / 같은 타겟 반복 hover 케이스 처리 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2d52a1668f
commit
01bbbbd6af
1 changed files with 40 additions and 16 deletions
|
|
@ -17,42 +17,62 @@ const DELAY_DEFAULT = 200
|
|||
*/
|
||||
export default function GlobalTooltip() {
|
||||
const [state, setState] = useState({ open: false, text: '', placement: 'top', coords: null })
|
||||
const triggerRef = useRef(null)
|
||||
const triggerRef = useRef(null) // 툴팁이 실제로 떠있는 타겟
|
||||
const pendingRef = useRef(null) // delay 중이라 title 을 벗긴 타겟 (아직 툴팁 안 뜸)
|
||||
const tooltipRef = useRef(null)
|
||||
const timerRef = useRef(null)
|
||||
const titleMap = useRef(new WeakMap())
|
||||
|
||||
useEffect(() => {
|
||||
function restoreTitle() {
|
||||
const el = triggerRef.current
|
||||
function stripTitle(el) {
|
||||
const t = el.getAttribute('title')
|
||||
if (t && !titleMap.current.has(el)) {
|
||||
titleMap.current.set(el, t)
|
||||
el.removeAttribute('title')
|
||||
}
|
||||
}
|
||||
|
||||
function restoreTitle(el) {
|
||||
if (el && titleMap.current.has(el)) {
|
||||
const prev = titleMap.current.get(el)
|
||||
el.setAttribute('title', prev)
|
||||
el.setAttribute('title', titleMap.current.get(el))
|
||||
titleMap.current.delete(el)
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
clearTimeout(timerRef.current)
|
||||
restoreTitle()
|
||||
restoreTitle(pendingRef.current)
|
||||
restoreTitle(triggerRef.current)
|
||||
pendingRef.current = null
|
||||
triggerRef.current = null
|
||||
setState((s) => (s.open ? { ...s, open: false } : s))
|
||||
}
|
||||
|
||||
function showFor(target) {
|
||||
// 이미 같은 타겟이 처리중이면 무시
|
||||
if (triggerRef.current === target || pendingRef.current === target) return
|
||||
|
||||
// 다른 pending 이 있다면 먼저 정리
|
||||
if (pendingRef.current) {
|
||||
clearTimeout(timerRef.current)
|
||||
restoreTitle(pendingRef.current)
|
||||
pendingRef.current = null
|
||||
}
|
||||
|
||||
const nativeTitle = target.getAttribute('title')
|
||||
const dataTooltip = target.getAttribute('data-tooltip')
|
||||
const text = nativeTitle || dataTooltip
|
||||
if (!text) return
|
||||
if (nativeTitle && !titleMap.current.has(target)) {
|
||||
titleMap.current.set(target, nativeTitle)
|
||||
target.removeAttribute('title')
|
||||
}
|
||||
|
||||
if (nativeTitle) stripTitle(target)
|
||||
pendingRef.current = target
|
||||
|
||||
const placement = target.getAttribute('data-tooltip-placement') || 'top'
|
||||
const delay = Number(target.getAttribute('data-tooltip-delay')) || DELAY_DEFAULT
|
||||
clearTimeout(timerRef.current)
|
||||
timerRef.current = setTimeout(() => {
|
||||
triggerRef.current = target
|
||||
pendingRef.current = null
|
||||
setState({ open: true, text, placement, coords: null })
|
||||
}, delay)
|
||||
}
|
||||
|
|
@ -60,19 +80,22 @@ export default function GlobalTooltip() {
|
|||
function handleOver(e) {
|
||||
const target = e.target.closest?.('[title], [data-tooltip]')
|
||||
if (!target) return
|
||||
if (triggerRef.current === target) return
|
||||
// 다른 타겟으로 이동 시 기존 정리
|
||||
if (triggerRef.current === target || pendingRef.current === target) return
|
||||
|
||||
// 다른 trigger 가 떠있으면 먼저 정리
|
||||
if (triggerRef.current && triggerRef.current !== target) {
|
||||
restoreTitle()
|
||||
restoreTitle(triggerRef.current)
|
||||
triggerRef.current = null
|
||||
setState((s) => (s.open ? { ...s, open: false } : s))
|
||||
}
|
||||
showFor(target)
|
||||
}
|
||||
|
||||
function handleOut(e) {
|
||||
if (!triggerRef.current) return
|
||||
const active = triggerRef.current || pendingRef.current
|
||||
if (!active) return
|
||||
const rt = e.relatedTarget
|
||||
if (rt && triggerRef.current.contains(rt)) return
|
||||
if (rt && active.contains(rt)) return
|
||||
hide()
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +121,8 @@ export default function GlobalTooltip() {
|
|||
window.removeEventListener('scroll', hide, true)
|
||||
window.removeEventListener('resize', hide)
|
||||
clearTimeout(timerRef.current)
|
||||
restoreTitle()
|
||||
restoreTitle(pendingRef.current)
|
||||
restoreTitle(triggerRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue