Merge pull request #7831 from overleaf/ii-scroll-to-position

Add scroll to position custom hook

GitOrigin-RevId: e2202e390ad674e31b61eb0f0ff6bda9346b34b8
This commit is contained in:
ilkin-overleaf 2022-05-11 12:19:27 +03:00 committed by Copybot
parent c807bedb65
commit f7977cdca5
2 changed files with 113 additions and 0 deletions

View file

@ -14,10 +14,12 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
import { UserProvider } from '../../../shared/context/user-context'
import { SSOProvider } from '../context/sso-context'
import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
import useScrollToIdOnLoad from '../../../shared/hooks/use-scroll-to-id-on-load'
import { ExposedSettings } from '../../../../../types/exposed-settings'
function SettingsPageRoot() {
const { isReady } = useWaitForI18n()
useScrollToIdOnLoad()
useEffect(() => {
eventTracking.sendMB('settings-view')

View file

@ -0,0 +1,111 @@
import { useEffect, useState, useRef } from 'react'
const DEFAULT_TIMEOUT = 3000
const hash = window.location.hash.substring(1)
const events = <const>['keydown', 'touchmove', 'wheel']
const isKeyboardEvent = (event: Event): event is KeyboardEvent => {
return event.constructor.name === 'KeyboardEvent'
}
type UseScrollToIdOnLoadProps = {
timeout?: number
}
function UseScrollToIdOnLoad({
timeout = DEFAULT_TIMEOUT,
}: UseScrollToIdOnLoadProps = {}) {
const [offsetTop, setOffsetTop] = useState<number | null>(null)
const requestRef = useRef<number | null>(null)
const targetRef = useRef<HTMLElement | null>(null)
const cancelAnimationFrame = () => {
if (requestRef.current) {
window.cancelAnimationFrame(requestRef.current)
}
}
const cancelEventListeners = () => {
events.forEach(eventType => {
window.removeEventListener(eventType, eventListenersCallbackRef.current)
})
}
const eventListenersCallback = (
event: KeyboardEvent | TouchEvent | WheelEvent
) => {
const keys = new Set(['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown'])
if (!isKeyboardEvent(event) || keys.has(event.key)) {
// Remove scroll checks
cancelAnimationFrame()
// Remove event listeners
cancelEventListeners()
}
}
const eventListenersCallbackRef = useRef(eventListenersCallback)
// Scroll to the target
useEffect(() => {
if (!offsetTop) {
return
}
window.scrollTo({
top: offsetTop,
})
}, [offsetTop])
// Bail out from scrolling automatically in `${timeout}` milliseconds
useEffect(() => {
if (!hash) {
return
}
setTimeout(() => {
cancelAnimationFrame()
cancelEventListeners()
}, timeout)
}, [timeout])
// Scroll to target by recursively looking for the target element
useEffect(() => {
if (!hash) {
return
}
const offsetTop = () => {
if (targetRef.current) {
setOffsetTop(targetRef.current.offsetTop)
} else {
targetRef.current = document.getElementById(hash)
}
requestRef.current = window.requestAnimationFrame(offsetTop)
}
requestRef.current = window.requestAnimationFrame(offsetTop)
return () => {
cancelAnimationFrame()
}
}, [])
// Set up the event listeners that will cancel the target element lookup
useEffect(() => {
if (!hash) {
return
}
events.forEach(eventType => {
window.addEventListener(eventType, eventListenersCallbackRef.current)
})
return () => {
cancelEventListeners()
}
}, [])
}
export default UseScrollToIdOnLoad