11import { h , createContext , cloneElement , toChildArray } from 'preact' ;
22import { useContext , useMemo , useReducer , useLayoutEffect , useRef } from 'preact/hooks' ;
33
4+
5+ let scope ;
6+
7+ /**
8+ * @param {string } state
9+ * @param {NavigateEvent } e
10+ */
11+ function handleNav ( state , e ) {
12+ if ( ! e . canIntercept ) return state ;
13+ if ( e . hashChange || e . downloadRequest !== null ) return state ;
14+
15+ const url = new URL ( e . destination . url ) ;
16+ if (
17+ scope && ( typeof scope == 'string'
18+ ? ! url . pathname . startsWith ( scope )
19+ : ! scope . test ( url . pathname )
20+ )
21+ ) {
22+ return state ;
23+ }
24+
25+ e . intercept ( ) ;
26+ return url . href . replace ( url . origin , '' ) ;
27+ }
28+
429/**
530 * @template T
631 * @typedef {import('preact').RefObject<T> } RefObject
732 * @typedef {import('./internal.d.ts').VNode } VNode
833 */
934
10- let push , scope ;
11- const UPDATE = ( state , url ) => {
12- push = undefined ;
13- if ( url && url . type === 'click' ) {
14- // ignore events the browser takes care of already:
15- if ( url . ctrlKey || url . metaKey || url . altKey || url . shiftKey || url . button !== 0 ) {
16- return state ;
17- }
18-
19- const link = url . target . closest ( 'a[href]' ) ,
20- href = link && link . getAttribute ( 'href' ) ;
21- if (
22- ! link ||
23- link . origin != location . origin ||
24- / ^ # / . test ( href ) ||
25- ! / ^ ( _ ? s e l f ) ? $ / i. test ( link . target ) ||
26- scope && ( typeof scope == 'string'
27- ? ! href . startsWith ( scope )
28- : ! scope . test ( href )
29- )
30- ) {
31- return state ;
32- }
33-
34- push = true ;
35- url . preventDefault ( ) ;
36- url = link . href . replace ( location . origin , '' ) ;
37- } else if ( typeof url === 'string' ) {
38- push = true ;
39- } else if ( url && url . url ) {
40- push = ! url . replace ;
41- url = url . url ;
42- } else {
43- url = location . pathname + location . search ;
44- }
45-
46- if ( push === true ) history . pushState ( null , '' , url ) ;
47- else if ( push === false ) history . replaceState ( null , '' , url ) ;
48- return url ;
49- } ;
5035
5136export const exec = ( url , route , matches = { } ) => {
5237 url = url . split ( '/' ) . filter ( Boolean ) ;
@@ -80,9 +65,8 @@ export const exec = (url, route, matches = {}) => {
8065 * @type {import('./router.d.ts').LocationProvider }
8166 */
8267export function LocationProvider ( props ) {
83- const [ url , route ] = useReducer ( UPDATE , location . pathname + location . search ) ;
68+ const [ url , route ] = useReducer ( handleNav , location . pathname + location . search ) ;
8469 if ( props . scope ) scope = props . scope ;
85- const wasPush = push === true ;
8670
8771 const value = useMemo ( ( ) => {
8872 const u = new URL ( url , location . origin ) ;
@@ -93,18 +77,14 @@ export function LocationProvider(props) {
9377 path,
9478 pathParams : { } ,
9579 searchParams : Object . fromEntries ( u . searchParams ) ,
96- route : ( url , replace ) => route ( { url, replace } ) ,
97- wasPush
9880 } ;
9981 } , [ url ] ) ;
10082
10183 useLayoutEffect ( ( ) => {
102- addEventListener ( 'click' , route ) ;
103- addEventListener ( 'popstate' , route ) ;
84+ navigation . addEventListener ( 'navigate' , route ) ;
10485
10586 return ( ) => {
106- removeEventListener ( 'click' , route ) ;
107- removeEventListener ( 'popstate' , route ) ;
87+ navigation . removeEventListener ( 'navigate' , route ) ;
10888 } ;
10989 } , [ ] ) ;
11090
@@ -116,7 +96,7 @@ const RESOLVED = Promise.resolve();
11696export function Router ( props ) {
11797 const [ c , update ] = useReducer ( c => c + 1 , 0 ) ;
11898
119- const { url, path, pathParams, searchParams, wasPush } = useLocation ( ) ;
99+ const { url, path, pathParams, searchParams } = useLocation ( ) ;
120100
121101 const isLoading = useRef ( false ) ;
122102 const prevRoute = useRef ( path ) ;
@@ -236,15 +216,15 @@ export function Router(props) {
236216
237217 // The route is loaded and rendered.
238218 if ( prevRoute . current !== path ) {
239- if ( wasPush ) scrollTo ( 0 , 0 ) ;
219+ scrollTo ( 0 , 0 ) ;
240220 if ( props . onRouteChange ) props . onRouteChange ( url ) ;
241221
242222 prevRoute . current = path ;
243223 }
244224
245225 if ( props . onLoadEnd && isLoading . current ) props . onLoadEnd ( url ) ;
246226 isLoading . current = false ;
247- } , [ path , wasPush , c ] ) ;
227+ } , [ path , c ] ) ;
248228
249229 // Note: cur MUST render first in order to set didSuspend & prev.
250230 return routeChanged
@@ -261,7 +241,7 @@ const RenderRef = ({ r }) => r.current;
261241Router . Provider = LocationProvider ;
262242
263243LocationProvider . ctx = createContext (
264- /** @type {import('./router.d.ts').LocationHook & { wasPush: boolean } } */ ( { } )
244+ /** @type {import('./router.d.ts').LocationHook }} */ ( { } )
265245) ;
266246
267247export const Route = props => h ( props . component , props ) ;
0 commit comments