Preserve session scroll when returning
This commit is contained in:
@@ -125,6 +125,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
const [activeMessageId, setActiveMessageId] = createSignal<string | null>(null)
|
const [activeMessageId, setActiveMessageId] = createSignal<string | null>(null)
|
||||||
|
|
||||||
const changeToken = createMemo(() => String(sessionRevision()))
|
const changeToken = createMemo(() => String(sessionRevision()))
|
||||||
|
const isActive = createMemo(() => props.isActive !== false)
|
||||||
|
|
||||||
|
|
||||||
const scrollCache = useScrollCache({
|
const scrollCache = useScrollCache({
|
||||||
@@ -236,11 +237,12 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom(immediate = false) {
|
function scrollToBottom(immediate = false, options?: { suppressAutoAnchor?: boolean }) {
|
||||||
if (!containerRef) return
|
if (!containerRef) return
|
||||||
const sentinel = bottomSentinel()
|
const sentinel = bottomSentinel()
|
||||||
const behavior = immediate ? "auto" : "smooth"
|
const behavior = immediate ? "auto" : "smooth"
|
||||||
if (!immediate) {
|
const suppressAutoAnchor = options?.suppressAutoAnchor ?? !immediate
|
||||||
|
if (suppressAutoAnchor) {
|
||||||
suppressAutoScrollOnce = true
|
suppressAutoScrollOnce = true
|
||||||
}
|
}
|
||||||
sentinel?.scrollIntoView({ block: "end", inline: "nearest", behavior })
|
sentinel?.scrollIntoView({ block: "end", inline: "nearest", behavior })
|
||||||
@@ -260,6 +262,10 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function requestScrollToBottom(immediate = true) {
|
function requestScrollToBottom(immediate = true) {
|
||||||
|
if (!isActive()) {
|
||||||
|
pendingActiveScroll = true
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!containerRef || !bottomSentinel()) {
|
if (!containerRef || !bottomSentinel()) {
|
||||||
pendingActiveScroll = true
|
pendingActiveScroll = true
|
||||||
return
|
return
|
||||||
@@ -277,7 +283,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
|
|
||||||
function resolvePendingActiveScroll() {
|
function resolvePendingActiveScroll() {
|
||||||
if (!pendingActiveScroll) return
|
if (!pendingActiveScroll) return
|
||||||
if (!props.isActive) return
|
if (!isActive()) return
|
||||||
requestScrollToBottom(true)
|
requestScrollToBottom(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,8 +298,15 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
|
|
||||||
function scheduleAnchorScroll(immediate = false) {
|
function scheduleAnchorScroll(immediate = false) {
|
||||||
if (!autoScroll()) return
|
if (!autoScroll()) return
|
||||||
|
if (!isActive()) {
|
||||||
|
pendingActiveScroll = true
|
||||||
|
return
|
||||||
|
}
|
||||||
const sentinel = bottomSentinel()
|
const sentinel = bottomSentinel()
|
||||||
if (!sentinel) return
|
if (!sentinel) {
|
||||||
|
pendingActiveScroll = true
|
||||||
|
return
|
||||||
|
}
|
||||||
if (pendingAnchorScroll !== null) {
|
if (pendingAnchorScroll !== null) {
|
||||||
cancelAnimationFrame(pendingAnchorScroll)
|
cancelAnimationFrame(pendingAnchorScroll)
|
||||||
pendingAnchorScroll = null
|
pendingAnchorScroll = null
|
||||||
@@ -415,9 +428,14 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
|
|
||||||
let lastActiveState = false
|
let lastActiveState = false
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const active = Boolean(props.isActive)
|
const active = isActive()
|
||||||
if (active && !lastActiveState) {
|
if (active) {
|
||||||
requestScrollToBottom(true)
|
resolvePendingActiveScroll()
|
||||||
|
if (!lastActiveState && autoScroll()) {
|
||||||
|
requestScrollToBottom(true)
|
||||||
|
}
|
||||||
|
} else if (autoScroll()) {
|
||||||
|
pendingActiveScroll = true
|
||||||
}
|
}
|
||||||
lastActiveState = active
|
lastActiveState = active
|
||||||
})
|
})
|
||||||
@@ -670,7 +688,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
onFork={props.onFork}
|
onFork={props.onFork}
|
||||||
onContentRendered={handleContentRendered}
|
onContentRendered={handleContentRendered}
|
||||||
setBottomSentinel={setBottomSentinel}
|
setBottomSentinel={setBottomSentinel}
|
||||||
suspendMeasurements={() => props.isActive === false}
|
suspendMeasurements={() => !isActive()}
|
||||||
onInitialRenderComplete={handleInitialRenderComplete}
|
onInitialRenderComplete={handleInitialRenderComplete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -688,7 +706,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="message-scroll-button"
|
class="message-scroll-button"
|
||||||
onClick={() => scrollToBottom()}
|
onClick={() => scrollToBottom(false, { suppressAutoAnchor: false })}
|
||||||
aria-label="Scroll to latest message"
|
aria-label="Scroll to latest message"
|
||||||
>
|
>
|
||||||
<span class="message-scroll-icon" aria-hidden="true">↓</span>
|
<span class="message-scroll-icon" aria-hidden="true">↓</span>
|
||||||
|
|||||||
@@ -167,6 +167,18 @@ export default function VirtualItem(props: VirtualItemProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const normalized = nextHeight
|
const normalized = nextHeight
|
||||||
|
const previous = sizeCache.get(props.cacheKey) ?? measuredHeight()
|
||||||
|
const shouldKeepPrevious = previous > 0 && (normalized === 0 || (normalized > 0 && normalized < previous))
|
||||||
|
if (shouldKeepPrevious) {
|
||||||
|
if (!hasReportedMeasurement) {
|
||||||
|
hasReportedMeasurement = true
|
||||||
|
props.onMeasured?.()
|
||||||
|
}
|
||||||
|
setHasMeasured(true)
|
||||||
|
sizeCache.set(props.cacheKey, previous)
|
||||||
|
setMeasuredHeight(previous)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (normalized > 0) {
|
if (normalized > 0) {
|
||||||
sizeCache.set(props.cacheKey, normalized)
|
sizeCache.set(props.cacheKey, normalized)
|
||||||
setHasMeasured(true)
|
setHasMeasured(true)
|
||||||
@@ -262,6 +274,7 @@ export default function VirtualItem(props: VirtualItemProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
measurementsSuspended()
|
||||||
const root = props.scrollContainer ? props.scrollContainer() : null
|
const root = props.scrollContainer ? props.scrollContainer() : null
|
||||||
refreshIntersectionObserver(root ?? null)
|
refreshIntersectionObserver(root ?? null)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user