1
- import { h , createContext , cloneElement , toChildArray } from 'preact' ;
1
+ import { h , Fragment , createContext , cloneElement , toChildArray } from 'preact' ;
2
2
import { useContext , useMemo , useReducer , useLayoutEffect , useRef } from 'preact/hooks' ;
3
3
4
4
/**
@@ -47,10 +47,10 @@ export const exec = (url, route, matches) => {
47
47
url = url . split ( '/' ) . filter ( Boolean ) ;
48
48
route = ( route || '' ) . split ( '/' ) . filter ( Boolean ) ;
49
49
for ( let i = 0 , val , rest ; i < Math . max ( url . length , route . length ) ; i ++ ) {
50
- let [ , m , param , flag ] = ( route [ i ] || '' ) . match ( / ^ ( : ? ) ( .* ?) ( [ + * ? ] ? ) $ / ) ;
50
+ let [ , m , pathParam , flag ] = ( route [ i ] || '' ) . match ( / ^ ( : ? ) ( .* ?) ( [ + * ? ] ? ) $ / ) ;
51
51
val = url [ i ] ;
52
52
// segment match:
53
- if ( ! m && param == val ) continue ;
53
+ if ( ! m && pathParam == val ) continue ;
54
54
// /foo/* match
55
55
if ( ! m && val && flag == '*' ) {
56
56
matches . rest = '/' + url . slice ( i ) . map ( decodeURIComponent ) . join ( '/' ) ;
@@ -63,8 +63,8 @@ export const exec = (url, route, matches) => {
63
63
if ( rest ) val = url . slice ( i ) . map ( decodeURIComponent ) . join ( '/' ) ;
64
64
// normal/optional field:
65
65
else if ( val ) val = decodeURIComponent ( val ) ;
66
- matches . params [ param ] = val ;
67
- if ( ! ( param in matches ) ) matches [ param ] = val ;
66
+ matches . pathParams [ pathParam ] = val ;
67
+ if ( ! ( pathParam in matches ) ) matches [ pathParam ] = val ;
68
68
if ( rest ) break ;
69
69
}
70
70
return matches ;
@@ -74,19 +74,22 @@ export function LocationProvider(props) {
74
74
const [ url , route ] = useReducer ( UPDATE , location . pathname + location . search ) ;
75
75
const wasPush = push === true ;
76
76
77
+ /** @type {import('./router.d.ts').LocationHook } */
77
78
const value = useMemo ( ( ) => {
78
79
const u = new URL ( url , location . origin ) ;
79
80
const path = u . pathname . replace ( / \/ + $ / g, '' ) || '/' ;
80
- // @ts -ignore-next
81
+
81
82
return {
82
83
url,
83
84
path,
84
- query : Object . fromEntries ( u . searchParams ) ,
85
+ pathParams : { } ,
86
+ searchParams : Object . fromEntries ( u . searchParams ) ,
85
87
route : ( url , replace ) => route ( { url, replace } ) ,
86
88
wasPush
87
89
} ;
88
90
} , [ url ] ) ;
89
91
92
+
90
93
useLayoutEffect ( ( ) => {
91
94
addEventListener ( 'click' , route ) ;
92
95
addEventListener ( 'popstate' , route ) ;
@@ -97,7 +100,6 @@ export function LocationProvider(props) {
97
100
} ;
98
101
} , [ ] ) ;
99
102
100
- // @ts -ignore
101
103
return h ( LocationProvider . ctx . Provider , { value } , props . children ) ;
102
104
}
103
105
@@ -106,8 +108,7 @@ const RESOLVED = Promise.resolve();
106
108
export function Router ( props ) {
107
109
const [ c , update ] = useReducer ( c => c + 1 , 0 ) ;
108
110
109
- const { url, query, wasPush, path } = useLocation ( ) ;
110
- const { rest = path , params = { } } = useContext ( RouteContext ) ;
111
+ const { url, path, pathParams, searchParams, wasPush } = useLocation ( ) ;
111
112
112
113
const isLoading = useRef ( false ) ;
113
114
const prevRoute = useRef ( path ) ;
@@ -129,7 +130,7 @@ export function Router(props) {
129
130
130
131
let pathRoute , defaultRoute , matchProps ;
131
132
toChildArray ( props . children ) . some ( ( /** @type {VNode<any> } */ vnode ) => {
132
- const matches = exec ( rest , vnode . props . path , ( matchProps = { ...vnode . props , path : rest , query , params , rest : '' } ) ) ;
133
+ const matches = exec ( path , vnode . props . path , ( matchProps = { ...vnode . props , path, pathParams , searchParams , rest : '' } ) ) ;
133
134
if ( matches ) return ( pathRoute = cloneElement ( vnode , matchProps ) ) ;
134
135
if ( vnode . props . default ) defaultRoute = cloneElement ( vnode , matchProps ) ;
135
136
} ) ;
@@ -140,7 +141,7 @@ export function Router(props) {
140
141
prev . current = cur . current ;
141
142
142
143
// Only mark as an update if the route component changed.
143
- const outgoing = prev . current && prev . current . props . children ;
144
+ const outgoing = prev . current ;
144
145
if ( ! outgoing || ! incoming || incoming . type !== outgoing . type || incoming . props . component !== outgoing . props . component ) {
145
146
// This hack prevents Preact from diffing when we swap `cur` to `prev`:
146
147
if ( this . __v && this . __v . __k ) this . __v . __k . reverse ( ) ;
@@ -152,7 +153,8 @@ export function Router(props) {
152
153
const isHydratingSuspense = cur . current && cur . current . __u & MODE_HYDRATE && cur . current . __u & MODE_SUSPENDED ;
153
154
const isHydratingBool = cur . current && cur . current . __h ;
154
155
// @ts -ignore
155
- cur . current = /** @type {VNode<any> } */ ( h ( RouteContext . Provider , { value : matchProps } , incoming ) ) ;
156
+ // TODO: Figure out how to set `.__h` properly so that it's preserved for the next render.
157
+ cur . current = h ( Fragment , { } , incoming ) ;
156
158
if ( isHydratingSuspense ) {
157
159
cur . current . __u |= MODE_HYDRATE ;
158
160
cur . current . __u |= MODE_SUSPENDED ;
@@ -254,11 +256,7 @@ Router.Provider = LocationProvider;
254
256
LocationProvider . ctx = createContext (
255
257
/** @type {import('./router.d.ts').LocationHook & { wasPush: boolean } } */ ( { } )
256
258
) ;
257
- const RouteContext = createContext (
258
- /** @type {import('./router.d.ts').RouteHook & { rest: string } } */ ( { } )
259
- ) ;
260
259
261
260
export const Route = props => h ( props . component , props ) ;
262
261
263
262
export const useLocation = ( ) => useContext ( LocationProvider . ctx ) ;
264
- export const useRoute = ( ) => useContext ( RouteContext ) ;
0 commit comments