{"version":3,"sources":["../src/useScrollRestorer.ts"],"sourcesContent":["/* eslint-disable no-console -- console required for debugging. Its dropped with bundler on production. */\nimport {usePathname, useSearchParams} from \"next/navigation\"\nimport {useEffect, useLayoutEffect, useRef,} from \"react\"\nimport {\n    getIsNavigatingHistory, getKey, getPopstateTimestamp,\n    getScrollFromState,\n    getScrollTimestamp,\n    type HistoryState,\n    type ScrollPos,\n    setCurrentScrollHistory\n} from \"./storage\"\nimport { isRecord } from \"./util\"\n\nconst getWindowScroll = (): ScrollPos => [window.scrollX, window.scrollY]\nconst memoizationIntervalLimit = 601//100 times per 30 seconds\nconst safariBugWorkaroundTimeThreshold = 2000 //Safari reset scroll position to 0 0 after popstate for some reason.\n\nconst getState = ():HistoryState => {\n    const state = window.history.state as unknown\n    return isRecord(state)?state:null\n}\nconst restoreScrollFromState = (state: HistoryState):void => {\n    const scroll = getScrollFromState(state)\n    console.log(`Found scroll ${scroll?.toString()}. ${window.location.href}`)\n    if (scroll !== null) {\n        const [x, y] = scroll\n        console.log(`Scroll restored to ${x} ${y}. Document height ${window.document.body.clientHeight}.`)\n        window.scrollTo({\n            behavior: 'instant',\n            left: x,\n            top: y\n        })\n        console.log(`Scroll is ${window.scrollX} ${window.scrollY} after restoring. ${window.innerHeight}`)\n    }\n}\nconst scrollMemoIntervalCountLimit = 2\nconst restoreCurrentScrollPosition = ():void => {\n    console.log(`Restoring current scroll position. ${window.location.href}`)\n    restoreScrollFromState(getState())\n}\nconst defaultMemoInterval = 0\nconst numericTrue = 1\nconst defaultX = 0\nconst defaultY = 0\nconst useScrollRestorer = (): void => {\n    const pathname = usePathname()\n    const searchparams = useSearchParams()\n\n\n    useLayoutEffect(() => {\n        console.log('Restoring based on hooks.')\n        restoreCurrentScrollPosition()\n    }, [pathname, searchparams])\n    const scrollMemoTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined)\n    const scrollMemoCountInInterval = useRef<number>(defaultMemoInterval)//Used to workaround instant scrollTo() calls.It's used to work around immediate scroll in tests and possible real world behaviour.\n    const isSafariWorkaroundAllowedRef = useRef(false)\n    useEffect(() => {\n        window.history.scrollRestoration = 'manual'\n\n        const navigationListener = ({state:eState}: PopStateEvent):void => {\n            console.log('Popstate started.')\n            cancelDelayedScrollMemoization()\n\n            isSafariWorkaroundAllowedRef.current = true\n            const state = (isRecord(eState)?eState:null) ?? {}\n            window.history.replaceState({\n                ...state,\n                [getKey('is_navigating_history')]: numericTrue,\n                [getKey('popstate_timestamp')]: (new Date()).getTime()\n            }, '')\n        }\n\n\n        /**\n         * This is important to run as late as possible after navigation.\n         * We could use something like `setTimeout(restoreCurrentScroll,500)`, but this is not a reactive approach.\n         * useLayoutEffect + usePageHref hook is the latest reactive thing Next.js app can provide to use.\n         * In Safari even with `window.history.scrollRestoration = 'manual'` scroll position is reset.\n         */\n        const workaroundSafariBreaksScrollRestoration = ([x, y]: ScrollPos):boolean => {\n            const state = getState()\n\n\n            // Sometimes Safari scroll to the start because of unique behavior We restore it back.\n            // This case cannot be tested with Playwright, or any other testing library.\n            if ((x === defaultX && y === defaultY) &&  isSafariWorkaroundAllowedRef.current) {\n                const isWorkaroundAllowed = (() => {\n                    const timeNavigated = getPopstateTimestamp(state)\n                    if (timeNavigated === null) {\n                        return false\n                    }\n                    return (((new Date()).getTime() - timeNavigated) < safariBugWorkaroundTimeThreshold)\n                })() //Place here to prevent many computations\n                const isNavHistory = getIsNavigatingHistory(state)\n                console.log(`Check workaround for safari: ${x} ${y} ${isWorkaroundAllowed}. Is popstate ${isNavHistory}. ${window.location.href}`)\n                if (isWorkaroundAllowed && isNavHistory) {\n                    console.log(`Reverting back scroll because browser tried to brake it..`)\n                    restoreCurrentScrollPosition()\n                    isSafariWorkaroundAllowedRef.current = false //Safari bug appears only once\n                    return true\n                }\n            }\n\n            return false\n        }\n        const rememberScrollPosition = (pos: ScrollPos):void => {\n            // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- it's stale index\n            console.log(`Remember history scroll to ${pos[0]} ${pos[1]}. Href ${window.location.href}.`)\n            cancelDelayedScrollMemoization()\n            setCurrentScrollHistory(pos)\n        }\n        const unmountNavigationListener = () :void=> {\n            console.log('Unmount popstate.')\n\n            window.removeEventListener('popstate', navigationListener)\n        }\n        const mountNavigationListener = ():void => {\n            console.log('Mount popstate.')\n\n            window.addEventListener('popstate', navigationListener, {\n                passive: true\n            })\n        }\n\n        const cancelDelayedScrollMemoization = ():void => {\n            if (scrollMemoTimeoutRef.current !== undefined) {\n                console.log(`Cancelled delayed memoization.`)\n                clearTimeout(scrollMemoTimeoutRef.current)\n                scrollMemoTimeoutRef.current = undefined\n            }\n\n        }\n\n        const scrollMemoizationHandler = (pos: ScrollPos):void => {\n            const isScrollMemoAllowedNow = ():boolean => {\n                const timestamp = getScrollTimestamp(getState())\n                if (timestamp === null) {\n                    return true\n                }\n                return (new Date()).getTime() - timestamp > memoizationIntervalLimit\n            }\n\n            const isAllowedNow = isScrollMemoAllowedNow()\n            console.log(`Handle scroll event. Memo allowed: ${isAllowedNow}.`)\n            if (isAllowedNow) {\n                scrollMemoCountInInterval.current = defaultMemoInterval\n            }\n            if (isAllowedNow || scrollMemoCountInInterval.current < scrollMemoIntervalCountLimit) {\n                // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- it's just an increment for each time scroll position is memoized. That number prevents this library from hiting rate limits of `replaceState` APIs \n                scrollMemoCountInInterval.current += 1\n                rememberScrollPosition(pos)\n            } else {\n                console.log(`Scroll memoization is not allowed. ${window.location.href}`)\n                if (scrollMemoTimeoutRef.current === undefined) {\n                    // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- it's just a fixed index\n                    console.log(`Set delayed memoization ${pos[0]} ${pos[1]}`)\n                    scrollMemoTimeoutRef.current = setTimeout(() => {\n                        rememberScrollPosition(pos)\n                        scrollMemoCountInInterval.current = defaultMemoInterval\n                        scrollMemoTimeoutRef.current = undefined\n                    }, memoizationIntervalLimit)\n                }\n\n            }\n        }\n        const scrollListener = ():void => {\n            cancelDelayedScrollMemoization()\n            const scroll = getWindowScroll()\n\n            console.log(`Scroll event ${scroll.toString()}. ${window.location.href}`)\n            workaroundSafariBreaksScrollRestoration(scroll)\n\n            scrollMemoizationHandler(scroll)\n\n\n        }\n        const mountScrollListener = () :void=> {\n            console.log('Scroll listener mounted.')\n            window.addEventListener('scroll', scrollListener, {\n                passive: true\n            })\n        }\n        const unmountScrollListener = ():void => {\n            console.log('Scroll listener unmounted.')\n            window.removeEventListener('scroll', scrollListener)\n\n        }\n        mountNavigationListener()\n        mountScrollListener()\n        return () => {\n            unmountNavigationListener()\n            unmountScrollListener()\n            cancelDelayedScrollMemoization()\n        }\n    }, [])\n}\nexport default useScrollRestorer\n"],"mappings":"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","names":[]}