app: unify auto scroll ref handling (#20716)

pull/17559/merge
Brendan Allan 2026-04-03 00:44:52 +08:00 committed by GitHub
parent 3faabdadb7
commit c4b3971548
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 13 additions and 16 deletions

View File

@ -1,6 +1,6 @@
import { createEffect, createSignal, on, onCleanup } from "solid-js" import { createEffect, on, onCleanup } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { makeEventListener } from "@solid-primitives/event-listener" import { createEventListener } from "@solid-primitives/event-listener"
import { createResizeObserver } from "@solid-primitives/resize-observer" import { createResizeObserver } from "@solid-primitives/resize-observer"
export interface AutoScrollOptions { export interface AutoScrollOptions {
@ -11,7 +11,6 @@ export interface AutoScrollOptions {
} }
export function createAutoScroll(options: AutoScrollOptions) { export function createAutoScroll(options: AutoScrollOptions) {
let scroll: HTMLElement | undefined
let settling = false let settling = false
let settleTimer: ReturnType<typeof setTimeout> | undefined let settleTimer: ReturnType<typeof setTimeout> | undefined
let autoTimer: ReturnType<typeof setTimeout> | undefined let autoTimer: ReturnType<typeof setTimeout> | undefined
@ -21,6 +20,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
const [store, setStore] = createStore({ const [store, setStore] = createStore({
contentRef: undefined as HTMLElement | undefined, contentRef: undefined as HTMLElement | undefined,
scrollRef: undefined as HTMLElement | undefined,
userScrolled: false, userScrolled: false,
}) })
@ -64,7 +64,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
} }
const scrollToBottomNow = (behavior: ScrollBehavior) => { const scrollToBottomNow = (behavior: ScrollBehavior) => {
const el = scroll const el = store.scrollRef
if (!el) return if (!el) return
markAuto(el) markAuto(el)
if (behavior === "smooth") { if (behavior === "smooth") {
@ -81,7 +81,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
if (force && store.userScrolled) setStore("userScrolled", false) if (force && store.userScrolled) setStore("userScrolled", false)
const el = scroll const el = store.scrollRef
if (!el) return if (!el) return
if (!force && store.userScrolled) return if (!force && store.userScrolled) return
@ -98,7 +98,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
} }
const stop = () => { const stop = () => {
const el = scroll const el = store.scrollRef
if (!el) return if (!el) return
if (!canScroll(el)) { if (!canScroll(el)) {
if (store.userScrolled) setStore("userScrolled", false) if (store.userScrolled) setStore("userScrolled", false)
@ -115,7 +115,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
// If the user is scrolling within a nested scrollable region (tool output, // If the user is scrolling within a nested scrollable region (tool output,
// code block, etc), don't treat it as leaving the "follow bottom" mode. // code block, etc), don't treat it as leaving the "follow bottom" mode.
// Those regions opt in via `data-scrollable`. // Those regions opt in via `data-scrollable`.
const el = scroll const el = store.scrollRef
const target = e.target instanceof Element ? e.target : undefined const target = e.target instanceof Element ? e.target : undefined
const nested = target?.closest("[data-scrollable]") const nested = target?.closest("[data-scrollable]")
if (el && nested && nested !== el) return if (el && nested && nested !== el) return
@ -123,7 +123,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
} }
const handleScroll = () => { const handleScroll = () => {
const el = scroll const el = store.scrollRef
if (!el) return if (!el) return
if (!canScroll(el)) { if (!canScroll(el)) {
@ -172,7 +172,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
createResizeObserver( createResizeObserver(
() => store.contentRef, () => store.contentRef,
() => { () => {
const el = scroll const el = store.scrollRef
if (el && !canScroll(el)) { if (el && !canScroll(el)) {
if (store.userScrolled) setStore("userScrolled", false) if (store.userScrolled) setStore("userScrolled", false)
return return
@ -208,23 +208,20 @@ export function createAutoScroll(options: AutoScrollOptions) {
// Track `userScrolled` even before `scrollRef` is attached, so we can // Track `userScrolled` even before `scrollRef` is attached, so we can
// update overflow anchoring once the element exists. // update overflow anchoring once the element exists.
store.userScrolled store.userScrolled
const el = scroll const el = store.scrollRef
if (!el) return if (!el) return
updateOverflowAnchor(el) updateOverflowAnchor(el)
}) })
createEventListener(() => store.scrollRef, "wheel", handleWheel, { passive: true })
onCleanup(() => { onCleanup(() => {
if (settleTimer) clearTimeout(settleTimer) if (settleTimer) clearTimeout(settleTimer)
if (autoTimer) clearTimeout(autoTimer) if (autoTimer) clearTimeout(autoTimer)
}) })
return { return {
scrollRef: (el: HTMLElement | undefined) => { scrollRef: (el: HTMLElement | undefined) => setStore("scrollRef", el),
if (!el) return
updateOverflowAnchor(el)
makeEventListener(el, "wheel", handleWheel, { passive: true })
},
contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el), contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el),
handleScroll, handleScroll,
handleInteraction, handleInteraction,