Restore row depth only after the viewer has actually gone away
Driving the re-apply from the observer callbacks caught the refocus case but fired on far more than that: ResizeObserver reports every reflow, so dragging the panel, a scrollbar appearing, or anything that changed the layout re-applied the stored depth and discarded whatever had just been expanded by hand. From the outside that is the pivot snapping to a different layout while clicking around. Going away is what discards the view; a reflow is not. So the callbacks now record the departure -- lost intersection, collapsed to zero size, or a hidden document -- and only a return after one of those restores anything. The flag clears once the depth is back, so one departure causes one restore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
507cae4e51
commit
7cfd0068e7
@ -226,6 +226,9 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
const viewSeqRef = useRef(0)
|
||||
const viewIdsRef = useRef(new WeakMap())
|
||||
const lastViewIdRef = useRef(null)
|
||||
// set when the viewer stops being visible; only then is there anything to
|
||||
// restore, and only then may a callback re-apply depth
|
||||
const wentAwayRef = useRef(false)
|
||||
|
||||
function dbg(msg, extra) {
|
||||
let on = false
|
||||
@ -301,6 +304,12 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
if (!viewerRef.current) return dbg('bail: no viewer')
|
||||
if (expandDepthRef.current == null) return dbg('bail: no depth recorded (toolbar EXPAND never used, or per-node +/- only)')
|
||||
if (queued) return dbg('bail: already queued')
|
||||
// Only after the viewer has actually been away. Re-applying on every
|
||||
// callback meant a panel drag, an appearing scrollbar or any reflow reset
|
||||
// the tree to the stored depth, discarding whatever had just been expanded
|
||||
// by hand -- the pivot "snapping to a different layout" while clicking
|
||||
// around. Going away is what discards the view; a reflow does not.
|
||||
if (!wentAwayRef.current) return dbg('bail: viewer never went away, nothing to restore')
|
||||
queued = true
|
||||
// Let the viewer finish its own redraw first, then wait for it to actually
|
||||
// have a table again. A fixed delay loses this race most of the time -- the
|
||||
@ -312,7 +321,10 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
try {
|
||||
const d = expandDepthRef.current
|
||||
if (d == null) return dbg('re-apply aborted: depth became null')
|
||||
if (await applyDepthWhenReady(d)) dbg(`re-apply done depth=${d}`)
|
||||
if (await applyDepthWhenReady(d)) {
|
||||
wentAwayRef.current = false
|
||||
dbg(`re-apply done depth=${d}`)
|
||||
}
|
||||
} catch (err) {
|
||||
dbg('re-apply THREW', err)
|
||||
} finally {
|
||||
@ -334,26 +346,41 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
let settle = null
|
||||
const onObserved = (ev) => {
|
||||
const d = ev.detail || {}
|
||||
// A resize to zero, or losing intersection, is the element going away --
|
||||
// nothing to re-apply to yet. Only act on it coming back.
|
||||
const returning = d.entries?.some(e =>
|
||||
e.isIntersecting === true || (e.width > 0 && e.height > 0))
|
||||
if (!returning) return
|
||||
// Losing intersection, or collapsing to zero size, is the element going
|
||||
// away: record it, because that is when the view gets discarded.
|
||||
const leaving = d.entries?.some(e =>
|
||||
e.isIntersecting === false || (e.width === 0 && e.height === 0))
|
||||
if (leaving) { wentAwayRef.current = true; return dbg(`observer ${d.kind} -> viewer went away`) }
|
||||
|
||||
// A resize with a non-zero size is an ordinary reflow. It is not a
|
||||
// restoration, so it must not re-apply anything.
|
||||
if (!wentAwayRef.current) return
|
||||
|
||||
clearTimeout(settle)
|
||||
settle = setTimeout(() => {
|
||||
dbg(`observer ${d.kind} settled -> viewer visible again`)
|
||||
dbg(`observer ${d.kind} settled -> viewer back after going away`)
|
||||
reapply({ type: `observer:${d.kind}` })
|
||||
}, 150)
|
||||
}
|
||||
|
||||
// A hidden tab is the other way the viewer goes away, and the one the
|
||||
// observers may not report on every browser.
|
||||
const onVisibility = (ev) => {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
wentAwayRef.current = true
|
||||
return dbg('document hidden -> viewer went away')
|
||||
}
|
||||
reapply(ev)
|
||||
}
|
||||
|
||||
window.addEventListener(PF_OBSERVER_EVENT, onObserved)
|
||||
document.addEventListener('visibilitychange', reapply)
|
||||
document.addEventListener('visibilitychange', onVisibility)
|
||||
window.addEventListener('focus', reapply)
|
||||
window.addEventListener('pageshow', reapply)
|
||||
return () => {
|
||||
clearTimeout(settle)
|
||||
window.removeEventListener(PF_OBSERVER_EVENT, onObserved)
|
||||
document.removeEventListener('visibilitychange', reapply)
|
||||
document.removeEventListener('visibilitychange', onVisibility)
|
||||
window.removeEventListener('focus', reapply)
|
||||
window.removeEventListener('pageshow', reapply)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user