fix(app): stabilize session timeline layout continuity (#34533)

This commit is contained in:
Luke Parker 2026-07-02 12:36:47 +10:00 committed by GitHub
commit 3cf71808c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 6159 additions and 142 deletions

View file

@ -9,7 +9,8 @@ export interface ScrollViewProps extends ComponentProps<"div"> {
}
export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
if (event.altKey || event.ctrlKey || event.metaKey) return
if (event.shiftKey && event.key !== " ") return
switch (event.key) {
case "PageDown":
@ -24,9 +25,36 @@ export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey
return "up"
case "ArrowDown":
return "down"
case " ":
return event.shiftKey ? "page-up" : "page-down"
}
}
export function canScrollKey(element: HTMLElement, key: NonNullable<ReturnType<typeof scrollKey>>) {
const up = key === "up" || key === "page-up" || key === "home"
return up ? element.scrollTop > 0 : element.scrollTop + element.clientHeight < element.scrollHeight
}
export function scrollKeyOwner(
root: HTMLElement,
target: EventTarget | null,
key: NonNullable<ReturnType<typeof scrollKey>>,
) {
const element = target instanceof Element ? target : undefined
const owner = element?.closest<HTMLElement>("[data-scrollable]")
if (!owner || owner === root) return root
if (!root.contains(owner)) return owner
return canScrollKey(owner, key) ? owner : root
}
export function isScrollKeyTarget(target: EventTarget | null, key: NonNullable<ReturnType<typeof scrollKey>>) {
const element = target instanceof HTMLElement ? target : undefined
if (!element) return true
if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) || element.isContentEditable) return false
if ((key === "page-up" || key === "page-down") && element.closest('button, a[href], [role="button"]')) return false
return true
}
export function scrollTopFromThumbPointer(input: {
pointer: number
viewportTop: number
@ -160,9 +188,10 @@ export function ScrollView(props: ScrollViewProps) {
if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) {
return
}
const next = scrollKey(e)
if (!next) return
if (!isScrollKeyTarget(e.target, next)) return
if (scrollKeyOwner(viewportRef, e.target, next) !== viewportRef) return
const scrollAmount = viewportRef.clientHeight * 0.8
const lineAmount = 40
@ -208,6 +237,7 @@ export function ScrollView(props: ScrollViewProps) {
<div
ref={viewportRef}
class="scroll-view__viewport"
data-scrollable
onScroll={(e) => {
updateThumb()
if (typeof events.onScroll === "function") events.onScroll(e as any)