feat(app): make session timelines much faster AND without flicker or scroll jumps (#32331)

This commit is contained in:
Luke Parker
2026-06-16 18:53:57 +02:00
committed by GitHub
parent e772664389
commit 3b811bd019
49 changed files with 2822 additions and 843 deletions
+32 -16
View File
@@ -27,6 +27,24 @@ export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey
}
}
export function scrollTopFromThumbPointer(input: {
pointer: number
viewportTop: number
grabOffset: number
clientHeight: number
scrollHeight: number
thumbHeight: number
}) {
const padding = 8
const maxThumbTop = input.clientHeight - padding * 2 - input.thumbHeight
if (maxThumbTop <= 0) return 0
const thumbTop = Math.max(
0,
Math.min(input.pointer - input.viewportTop - padding - input.grabOffset, maxThumbTop),
)
return (thumbTop / maxThumbTop) * Math.max(0, input.scrollHeight - input.clientHeight)
}
export function ScrollView(props: ScrollViewProps) {
const i18n = useI18n()
const merged = mergeProps({ orientation: "vertical" }, props)
@@ -103,39 +121,37 @@ export function ScrollView(props: ScrollViewProps) {
updateThumb()
})
let startY = 0
let startScrollTop = 0
const onThumbPointerDown = (e: PointerEvent) => {
e.preventDefault()
e.stopPropagation()
setState("isDragging", true)
startY = e.clientY
startScrollTop = viewportRef.scrollTop
const grabOffset = e.clientY - thumbRef.getBoundingClientRect().top
thumbRef.setPointerCapture(e.pointerId)
const onPointerMove = (e: PointerEvent) => {
const deltaY = e.clientY - startY
const { scrollHeight, clientHeight } = viewportRef
const maxScrollTop = scrollHeight - clientHeight
const maxThumbTop = clientHeight - thumbHeight()
if (maxThumbTop > 0) {
const scrollDelta = deltaY * (maxScrollTop / maxThumbTop)
viewportRef.scrollTop = startScrollTop + scrollDelta
}
viewportRef.scrollTop = scrollTopFromThumbPointer({
pointer: e.clientY,
viewportTop: viewportRef.getBoundingClientRect().top,
grabOffset,
clientHeight,
scrollHeight,
thumbHeight: thumbHeight(),
})
}
const onPointerUp = (e: PointerEvent) => {
const done = (e: PointerEvent) => {
setState("isDragging", false)
thumbRef.releasePointerCapture(e.pointerId)
thumbRef.removeEventListener("pointermove", onPointerMove)
thumbRef.removeEventListener("pointerup", onPointerUp)
thumbRef.removeEventListener("pointerup", done)
thumbRef.removeEventListener("pointercancel", done)
}
thumbRef.addEventListener("pointermove", onPointerMove)
thumbRef.addEventListener("pointerup", onPointerUp)
thumbRef.addEventListener("pointerup", done)
thumbRef.addEventListener("pointercancel", done)
}
// Keybinds implementation