1
1
import { h , createContext , cloneElement , toChildArray } from 'preact' ;
2
2
import { useContext , useMemo , useReducer , useLayoutEffect , useRef } from 'preact/hooks' ;
3
3
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
+
4
29
/**
5
30
* @template T
6
31
* @typedef {import('preact').RefObject<T> } RefObject
7
32
* @typedef {import('./internal.d.ts').VNode } VNode
8
33
*/
9
34
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
- } ;
50
35
51
36
export const exec = ( url , route , matches = { } ) => {
52
37
url = url . split ( '/' ) . filter ( Boolean ) ;
@@ -80,9 +65,8 @@ export const exec = (url, route, matches = {}) => {
80
65
* @type {import('./router.d.ts').LocationProvider }
81
66
*/
82
67
export function LocationProvider ( props ) {
83
- const [ url , route ] = useReducer ( UPDATE , location . pathname + location . search ) ;
68
+ const [ url , route ] = useReducer ( handleNav , location . pathname + location . search ) ;
84
69
if ( props . scope ) scope = props . scope ;
85
- const wasPush = push === true ;
86
70
87
71
const value = useMemo ( ( ) => {
88
72
const u = new URL ( url , location . origin ) ;
@@ -93,18 +77,14 @@ export function LocationProvider(props) {
93
77
path,
94
78
pathParams : { } ,
95
79
searchParams : Object . fromEntries ( u . searchParams ) ,
96
- route : ( url , replace ) => route ( { url, replace } ) ,
97
- wasPush
98
80
} ;
99
81
} , [ url ] ) ;
100
82
101
83
useLayoutEffect ( ( ) => {
102
- addEventListener ( 'click' , route ) ;
103
- addEventListener ( 'popstate' , route ) ;
84
+ navigation . addEventListener ( 'navigate' , route ) ;
104
85
105
86
return ( ) => {
106
- removeEventListener ( 'click' , route ) ;
107
- removeEventListener ( 'popstate' , route ) ;
87
+ navigation . removeEventListener ( 'navigate' , route ) ;
108
88
} ;
109
89
} , [ ] ) ;
110
90
@@ -116,7 +96,7 @@ const RESOLVED = Promise.resolve();
116
96
export function Router ( props ) {
117
97
const [ c , update ] = useReducer ( c => c + 1 , 0 ) ;
118
98
119
- const { url, path, pathParams, searchParams, wasPush } = useLocation ( ) ;
99
+ const { url, path, pathParams, searchParams } = useLocation ( ) ;
120
100
121
101
const isLoading = useRef ( false ) ;
122
102
const prevRoute = useRef ( path ) ;
@@ -236,15 +216,15 @@ export function Router(props) {
236
216
237
217
// The route is loaded and rendered.
238
218
if ( prevRoute . current !== path ) {
239
- if ( wasPush ) scrollTo ( 0 , 0 ) ;
219
+ scrollTo ( 0 , 0 ) ;
240
220
if ( props . onRouteChange ) props . onRouteChange ( url ) ;
241
221
242
222
prevRoute . current = path ;
243
223
}
244
224
245
225
if ( props . onLoadEnd && isLoading . current ) props . onLoadEnd ( url ) ;
246
226
isLoading . current = false ;
247
- } , [ path , wasPush , c ] ) ;
227
+ } , [ path , c ] ) ;
248
228
249
229
// Note: cur MUST render first in order to set didSuspend & prev.
250
230
return routeChanged
@@ -261,7 +241,7 @@ const RenderRef = ({ r }) => r.current;
261
241
Router . Provider = LocationProvider ;
262
242
263
243
LocationProvider . ctx = createContext (
264
- /** @type {import('./router.d.ts').LocationHook & { wasPush: boolean } } */ ( { } )
244
+ /** @type {import('./router.d.ts').LocationHook }} */ ( { } )
265
245
) ;
266
246
267
247
export const Route = props => h ( props . component , props ) ;
0 commit comments