From ed2d4af5c7482a14e23ff1fbcf7dc13097931f6d Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Mon, 29 Mar 2021 20:39:58 -0400 Subject: [PATCH 1/4] [preact-iso] Router: reset scroll position when navigating forwards --- packages/preact-iso/router.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/preact-iso/router.js b/packages/preact-iso/router.js index d8707ab69..1d2d6c778 100644 --- a/packages/preact-iso/router.js +++ b/packages/preact-iso/router.js @@ -1,8 +1,9 @@ import { h, createContext, cloneElement } from 'preact'; import { useContext, useMemo, useReducer, useEffect, useLayoutEffect, useRef } from 'preact/hooks'; +let push; const UPDATE = (state, url) => { - let push = true; + push = true; if (url && url.type === 'click') { const link = url.target.closest('a[href]'); if (!link || link.origin != location.origin) return state; @@ -43,12 +44,13 @@ export const exec = (url, route, matches) => { export function LocationProvider(props) { const [url, route] = useReducer(UPDATE, location.pathname + location.search); + const wasPush = push === true; const value = useMemo(() => { const u = new URL(url, location.origin); const path = u.pathname.replace(/(.)\/$/g, '$1'); // @ts-ignore-next - return { url, path, query: Object.fromEntries(u.searchParams), route }; + return { url, path, query: Object.fromEntries(u.searchParams), route, wasPush }; }, [url]); useEffect(() => { @@ -70,7 +72,7 @@ export function Router(props) { const loc = useLocation(); - const { url, path, query } = loc; + const { url, path, query, wasPush } = loc; const cur = useRef(loc); const prev = useRef(); @@ -112,6 +114,7 @@ export function Router(props) { prev.current = prevChildren.current = pending.current = null; if (props.onLoadEnd) props.onLoadEnd(url); update(0); + if (wasPush) scrollTo(0, 0); }; if (p) { From 321dfced389800e661174e1e304fa8ecab593f96 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Mon, 29 Mar 2021 20:44:42 -0400 Subject: [PATCH 2/4] Create rare-seals-burn.md --- .changeset/rare-seals-burn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rare-seals-burn.md diff --git a/.changeset/rare-seals-burn.md b/.changeset/rare-seals-burn.md new file mode 100644 index 000000000..6c6895a21 --- /dev/null +++ b/.changeset/rare-seals-burn.md @@ -0,0 +1,5 @@ +--- +"preact-iso": patch +--- + +[preact-iso] Router: reset page scroll position on forward navigations From 08c4bfdb5a59a6e67fe3585bd17433bd27d9658d Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 30 Mar 2021 18:08:06 -0400 Subject: [PATCH 3/4] Tests & bugfix for scroll reset on programmatic/link navigation --- packages/preact-iso/router.js | 8 +++-- packages/preact-iso/test/router.test.js | 48 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/preact-iso/router.js b/packages/preact-iso/router.js index 1d2d6c778..9957ed517 100644 --- a/packages/preact-iso/router.js +++ b/packages/preact-iso/router.js @@ -3,16 +3,18 @@ import { useContext, useMemo, useReducer, useEffect, useLayoutEffect, useRef } f let push; const UPDATE = (state, url) => { - push = true; + push = undefined; if (url && url.type === 'click') { const link = url.target.closest('a[href]'); if (!link || link.origin != location.origin) return state; + push = true; url.preventDefault(); url = link.href.replace(location.origin, ''); - } else if (typeof url !== 'string') { + } else if (typeof url === 'string') { + push = true; + } else { url = location.pathname + location.search; - push = undefined; } if (push === true) history.pushState(null, '', url); diff --git a/packages/preact-iso/test/router.test.js b/packages/preact-iso/test/router.test.js index a8b7ec446..73036106f 100644 --- a/packages/preact-iso/test/router.test.js +++ b/packages/preact-iso/test/router.test.js @@ -3,6 +3,8 @@ import { h, html, render } from 'htm/preact'; import { LocationProvider, Router, useLocation } from '../router.js'; import lazy, { ErrorBoundary } from '../lazy.js'; +Object.defineProperty(window, 'scrollTo', { value() {} }); + const sleep = ms => new Promise(r => setTimeout(r, ms)); // delayed lazy() @@ -206,4 +208,50 @@ describe('Router', () => { // expect(A).toHaveBeenCalledTimes(1); expect(A).toHaveBeenCalledWith({ path: '/', query: {} }, expect.anything()); }); + + it('should scroll to top when navigating forward', async () => { + const scrollTo = jest.spyOn(window, 'scrollTo'); + + const Route = jest.fn(() => html`
link
`); + let loc; + render( + html` + <${LocationProvider}> + <${Router}> + <${Route} default /> + + <${() => { + loc = useLocation(); + }} /> + + `, + scratch + ); + + await sleep(20); + + expect(scrollTo).not.toHaveBeenCalled(); + expect(Route).toHaveBeenCalledTimes(1); + Route.mockClear(); + + loc.route('/programmatic'); + await sleep(10); + expect(loc).toMatchObject({ url: '/programmatic' }); + expect(scrollTo).toHaveBeenCalledWith(0, 0); + expect(scrollTo).toHaveBeenCalledTimes(1); + expect(Route).toHaveBeenCalledTimes(1); + Route.mockClear(); + scrollTo.mockClear(); + + scratch.querySelector('a').click(); + await sleep(10); + expect(loc).toMatchObject({ url: '/link' }); + expect(scrollTo).toHaveBeenCalledWith(0, 0); + expect(scrollTo).toHaveBeenCalledTimes(1); + expect(Route).toHaveBeenCalledTimes(1); + Route.mockClear(); + + await sleep(10); + scrollTo.mockRestore(); + }); }); From ef7df427dd9a30d95bad9eda31df829de6acf235 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 30 Mar 2021 18:32:26 -0400 Subject: [PATCH 4/4] include duplicate preact fix --- packages/preact-iso/test/router.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/preact-iso/test/router.test.js b/packages/preact-iso/test/router.test.js index 73036106f..89de8b00a 100644 --- a/packages/preact-iso/test/router.test.js +++ b/packages/preact-iso/test/router.test.js @@ -1,5 +1,6 @@ import { jest, describe, it, beforeEach, expect } from '@jest/globals'; -import { h, html, render } from 'htm/preact'; +import { h, render } from 'preact'; +import { html } from 'htm/preact'; import { LocationProvider, Router, useLocation } from '../router.js'; import lazy, { ErrorBoundary } from '../lazy.js';