app: unify auto scroll ref handling (#20716)
parent
3faabdadb7
commit
c4b3971548
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue