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 viewSeqRef = useRef(0)
|
||||||
const viewIdsRef = useRef(new WeakMap())
|
const viewIdsRef = useRef(new WeakMap())
|
||||||
const lastViewIdRef = useRef(null)
|
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) {
|
function dbg(msg, extra) {
|
||||||
let on = false
|
let on = false
|
||||||
@ -301,6 +304,12 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
if (!viewerRef.current) return dbg('bail: no viewer')
|
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 (expandDepthRef.current == null) return dbg('bail: no depth recorded (toolbar EXPAND never used, or per-node +/- only)')
|
||||||
if (queued) return dbg('bail: already queued')
|
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
|
queued = true
|
||||||
// Let the viewer finish its own redraw first, then wait for it to actually
|
// 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
|
// 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 {
|
try {
|
||||||
const d = expandDepthRef.current
|
const d = expandDepthRef.current
|
||||||
if (d == null) return dbg('re-apply aborted: depth became null')
|
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) {
|
} catch (err) {
|
||||||
dbg('re-apply THREW', err)
|
dbg('re-apply THREW', err)
|
||||||
} finally {
|
} finally {
|
||||||
@ -334,26 +346,41 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
let settle = null
|
let settle = null
|
||||||
const onObserved = (ev) => {
|
const onObserved = (ev) => {
|
||||||
const d = ev.detail || {}
|
const d = ev.detail || {}
|
||||||
// A resize to zero, or losing intersection, is the element going away --
|
// Losing intersection, or collapsing to zero size, is the element going
|
||||||
// nothing to re-apply to yet. Only act on it coming back.
|
// away: record it, because that is when the view gets discarded.
|
||||||
const returning = d.entries?.some(e =>
|
const leaving = d.entries?.some(e =>
|
||||||
e.isIntersecting === true || (e.width > 0 && e.height > 0))
|
e.isIntersecting === false || (e.width === 0 && e.height === 0))
|
||||||
if (!returning) return
|
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)
|
clearTimeout(settle)
|
||||||
settle = setTimeout(() => {
|
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}` })
|
reapply({ type: `observer:${d.kind}` })
|
||||||
}, 150)
|
}, 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)
|
window.addEventListener(PF_OBSERVER_EVENT, onObserved)
|
||||||
document.addEventListener('visibilitychange', reapply)
|
document.addEventListener('visibilitychange', onVisibility)
|
||||||
window.addEventListener('focus', reapply)
|
window.addEventListener('focus', reapply)
|
||||||
window.addEventListener('pageshow', reapply)
|
window.addEventListener('pageshow', reapply)
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(settle)
|
clearTimeout(settle)
|
||||||
window.removeEventListener(PF_OBSERVER_EVENT, onObserved)
|
window.removeEventListener(PF_OBSERVER_EVENT, onObserved)
|
||||||
document.removeEventListener('visibilitychange', reapply)
|
document.removeEventListener('visibilitychange', onVisibility)
|
||||||
window.removeEventListener('focus', reapply)
|
window.removeEventListener('focus', reapply)
|
||||||
window.removeEventListener('pageshow', reapply)
|
window.removeEventListener('pageshow', reapply)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user