Fix scripture verse popup clipped by overflow:hidden parents; v1.2.2
Publish Container / docker (push) Successful in 13m53s

Switch from position:absolute (relative to trigger) to position:fixed
with getBoundingClientRect() so the popup escapes any overflow:hidden
ancestor. Also closes on scroll and window resize.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-03 11:21:04 -04:00
1 parent 36dfa0c1d7
commit 525ae85f6a
3 files changed
+30 -11

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "siteforge", "name": "siteforge",
"private": true, "private": true,
"version": "1.2.1", "version": "1.2.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+2 -4
View File
@@ -8865,10 +8865,8 @@
} }
.scripture-popup { .scripture-popup {
position: absolute; position: fixed;
bottom: calc(100% + 8px); z-index: 1000;
left: 0;
z-index: 200;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
+27 -6
View File
@@ -218,7 +218,9 @@ function ScriptureTooltip({ refText, bookId, chapter, verseStart, verseEnd }: {
const [verses, setVerses] = useState<VerseData[]>([]) const [verses, setVerses] = useState<VerseData[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState(false) const [error, setError] = useState(false)
const [pos, setPos] = useState<{ top: number; left: number } | null>(null)
const wrapRef = useRef<HTMLSpanElement>(null) const wrapRef = useRef<HTMLSpanElement>(null)
const btnRef = useRef<HTMLButtonElement>(null)
useEffect(() => { useEffect(() => {
if (!open) return if (!open) return
@@ -251,20 +253,35 @@ function ScriptureTooltip({ refText, bookId, chapter, verseStart, verseEnd }: {
}) })
}, [open, bookId, chapter, verseStart, verseEnd, verses.length]) }, [open, bookId, chapter, verseStart, verseEnd, verses.length])
// Compute fixed position from the trigger button so the popup escapes overflow:hidden parents
useEffect(() => {
if (!open || !btnRef.current) { setPos(null); return }
const rect = btnRef.current.getBoundingClientRect()
const POPUP_W = 360
const left = Math.max(8, Math.min(rect.left, window.innerWidth - POPUP_W - 12))
setPos({ top: rect.bottom + 8, left })
}, [open])
useEffect(() => { useEffect(() => {
if (!open) return if (!open) return
function handleClickOutside(e: MouseEvent) { function handleClickOutside(e: MouseEvent) {
if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) { if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false)
setOpen(false)
}
} }
function handleDismiss() { setOpen(false) }
document.addEventListener('mousedown', handleClickOutside) document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside) window.addEventListener('scroll', handleDismiss, { passive: true, capture: true })
window.addEventListener('resize', handleDismiss)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
window.removeEventListener('scroll', handleDismiss, { capture: true })
window.removeEventListener('resize', handleDismiss)
}
}, [open]) }, [open])
return ( return (
<span ref={wrapRef} className="scripture-ref-wrap"> <span ref={wrapRef} className="scripture-ref-wrap">
<button <button
ref={btnRef}
type="button" type="button"
className="scripture-ref" className="scripture-ref"
onClick={() => setOpen(o => !o)} onClick={() => setOpen(o => !o)}
@@ -273,8 +290,12 @@ function ScriptureTooltip({ refText, bookId, chapter, verseStart, verseEnd }: {
> >
{refText} {refText}
</button> </button>
{open && ( {open && pos && (
<span className="scripture-popup" role="tooltip"> <span
className="scripture-popup"
role="tooltip"
style={{ position: 'fixed', top: pos.top, left: pos.left, bottom: 'auto' }}
>
<span className="scripture-popup-header"> <span className="scripture-popup-header">
<strong>{refText}</strong> <strong>{refText}</strong>
<button type="button" className="scripture-popup-close" onClick={() => setOpen(false)} aria-label="Close">✕</button> <button type="button" className="scripture-popup-close" onClick={() => setOpen(false)} aria-label="Close">✕</button>