/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/hooks/useScrollAnimation.ts
157 строк
5 KB
dependabot[bot]
Bump the eslint group in /frontend with 7 updates (#13502)
05 янв 2026, 20:23
Не верифицирован
05 янв 2026, 20:23
9c391de
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useCallback, useLayoutEffect, useRef } from "react" /** * Computes next step in a square-root based animation sequence. Step size is * square root of absolute difference between current and target values. Can be * used as a stepper function for other higher-level stepping functions. * * @param {number} current - Current value in animation sequence. * @param {number} to - Target value of animation sequence. * @returns {number} Next value in animation sequence. */ function squareStepper(current: number, to: number): number { const sign = Math.sign(to - current) const currStep = Math.sqrt(Math.abs(to - current)) const next = current + currStep * sign if (sign > 0) { return Math.min(to, next) } return Math.max(to, next) } /** * Computes sequence of steps in animation by repeatedly applying a stepper * function. * * @param {number} from - Initial value in animation sequence. * @param {number} to - Target value of animation sequence. * @param {function} stepper - Function computing next value given current * and target values. * @param {number} index - Number of steps to compute. * @returns {number} Value at given index in animation sequence. */ function step( from: number, to: number, stepper: (x: number, y: number) => number, index: number ): number { let next = from for (let i = 0; i < index; i++) { next = stepper(next, to) } return next } /** * Handles scroll animation for a given target HTMLElement. Uses a square-root * based stepping function to compute scroll animation. Stops animation if * target's scrollTop has reached scrollHeight or if user interacts with target * (mousedown or mousewheel). Can also be cancelled by caller. * * @export * @param {HTMLElement | null} target - HTML element to animate scroll of. If * null, no animation is performed. * @param {() => void} onEnd - Callback when animation ends or is cancelled. * @param {boolean} isAnimating - Boolean to start or stop animation. If false, * no animation is performed. * @returns {void} */ export default function useScrollAnimation( target: HTMLElement | null, onEnd: () => void, isAnimating: boolean, active: boolean ): void { const animatorRef = useRef(0) const animate = useCallback( (from: number, index: number, start = Date.now()) => { cancelAnimationFrame(animatorRef.current) animatorRef.current = requestAnimationFrame(() => { if (target) { // eslint-disable-next-line streamlit-custom/no-force-reflow-access -- Existing usage const toNumber = target.scrollHeight - target.offsetHeight let nextValue = step( from, toNumber, squareStepper, (Date.now() - start) / 5 ) if (Math.abs(toNumber - nextValue) < 1.5) { nextValue = toNumber } target.scrollTop = nextValue if (toNumber === nextValue) { onEnd() } else { // eslint-disable-next-line react-hooks/immutability -- TODO: Update to match React best practices animate(from, index + 1, start) } } }) }, [animatorRef, onEnd, target] ) const handleCancelAnimation = useCallback(() => { cancelAnimationFrame(animatorRef.current) onEnd() }, [onEnd]) useLayoutEffect(() => { if (!target || !isAnimating || !active) { return } // eslint-disable-next-line streamlit-custom/no-force-reflow-access -- Existing usage animate(target.scrollTop, 1) if (target) { target.addEventListener("pointerdown", handleCancelAnimation, { passive: true, }) target.addEventListener("wheel", handleCancelAnimation, { passive: true, }) return () => { target.removeEventListener("pointerdown", handleCancelAnimation) target.removeEventListener("wheel", handleCancelAnimation) cancelAnimationFrame(animatorRef.current) } } return () => cancelAnimationFrame(animatorRef.current) }, [ animate, animatorRef, handleCancelAnimation, target, isAnimating, active, ]) }