@bquery/bquery 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +760 -716
- package/dist/{a11y-C5QOVvRn.js → a11y-DVBCy09c.js} +3 -3
- package/dist/a11y-DVBCy09c.js.map +1 -0
- package/dist/a11y.es.mjs +1 -1
- package/dist/component/library.d.ts.map +1 -1
- package/dist/{component-CuuTijA6.js → component-L3-JfOFz.js} +5 -5
- package/dist/component-L3-JfOFz.js.map +1 -0
- package/dist/component.es.mjs +1 -1
- package/dist/{config-BW35FKuA.js → config-DhT9auRm.js} +1 -1
- package/dist/{config-BW35FKuA.js.map → config-DhT9auRm.js.map} +1 -1
- package/dist/{constraints-3lV9yyBw.js → constraints-D5RHQLmP.js} +1 -1
- package/dist/constraints-D5RHQLmP.js.map +1 -0
- package/dist/core/collection.d.ts +86 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +28 -0
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/shared.d.ts +6 -0
- package/dist/core/shared.d.ts.map +1 -1
- package/dist/core-DdtZHzsS.js +168 -0
- package/dist/core-DdtZHzsS.js.map +1 -0
- package/dist/{core-Cjl7GUu8.js → core-EMYSLzaT.js} +289 -259
- package/dist/core-EMYSLzaT.js.map +1 -0
- package/dist/core.es.mjs +48 -47
- package/dist/{custom-directives-7wAShnnd.js → custom-directives-Dr4C5lVV.js} +1 -1
- package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
- package/dist/{devtools-D2fQLhDN.js → devtools-BhB2iDPT.js} +2 -2
- package/dist/devtools-BhB2iDPT.js.map +1 -0
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-B8EgyzaI.js → dnd-NwZBYh4l.js} +1 -1
- package/dist/dnd-NwZBYh4l.js.map +1 -0
- package/dist/dnd.es.mjs +1 -1
- package/dist/{env-NeVmr4Gf.js → env-CTdvLaH2.js} +1 -1
- package/dist/env-CTdvLaH2.js.map +1 -0
- package/dist/forms/create-form.d.ts.map +1 -1
- package/dist/forms/index.d.ts +3 -2
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/types.d.ts +46 -0
- package/dist/forms/types.d.ts.map +1 -1
- package/dist/forms/use-field.d.ts +34 -0
- package/dist/forms/use-field.d.ts.map +1 -0
- package/dist/forms/validators.d.ts +25 -0
- package/dist/forms/validators.d.ts.map +1 -1
- package/dist/forms-UcRHsYxC.js +227 -0
- package/dist/forms-UcRHsYxC.js.map +1 -0
- package/dist/forms.es.mjs +14 -12
- package/dist/full.d.ts +17 -26
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +206 -181
- package/dist/full.iife.js +33 -33
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +33 -33
- package/dist/full.umd.js.map +1 -1
- package/dist/function-Cybd57JV.js +33 -0
- package/dist/function-Cybd57JV.js.map +1 -0
- package/dist/{i18n-BnnhTFOS.js → i18n-kuF6Ekj6.js} +3 -3
- package/dist/i18n-kuF6Ekj6.js.map +1 -0
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.es.mjs +251 -228
- package/dist/media/breakpoints.d.ts.map +1 -1
- package/dist/media/types.d.ts +2 -2
- package/dist/media/types.d.ts.map +1 -1
- package/dist/{media-Di2Ta22s.js → media-i-fB5WxI.js} +3 -3
- package/dist/media-i-fB5WxI.js.map +1 -0
- package/dist/media.es.mjs +1 -1
- package/dist/{motion-qPj_TYGv.js → motion-BJsAuULb.js} +2 -2
- package/dist/motion-BJsAuULb.js.map +1 -0
- package/dist/motion.es.mjs +1 -1
- package/dist/{mount-SM07RUa6.js → mount-B4Y8bk8Z.js} +5 -5
- package/dist/mount-B4Y8bk8Z.js.map +1 -0
- package/dist/{platform-CPbCprb6.js → platform-Dw2gE3zI.js} +3 -3
- package/dist/{platform-CPbCprb6.js.map → platform-Dw2gE3zI.js.map} +1 -1
- package/dist/platform.es.mjs +2 -2
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/{plugin-cPoOHFLY.js → plugin-C2WuC8SF.js} +20 -18
- package/dist/plugin-C2WuC8SF.js.map +1 -0
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive/async-data.d.ts +28 -3
- package/dist/reactive/async-data.d.ts.map +1 -1
- package/dist/reactive/computed.d.ts +3 -0
- package/dist/reactive/computed.d.ts.map +1 -1
- package/dist/reactive/effect.d.ts +3 -0
- package/dist/reactive/effect.d.ts.map +1 -1
- package/dist/reactive/http.d.ts +194 -0
- package/dist/reactive/http.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/pagination.d.ts +126 -0
- package/dist/reactive/pagination.d.ts.map +1 -0
- package/dist/reactive/polling.d.ts +55 -0
- package/dist/reactive/polling.d.ts.map +1 -0
- package/dist/reactive/readonly.d.ts +20 -1
- package/dist/reactive/readonly.d.ts.map +1 -1
- package/dist/reactive/rest.d.ts +293 -0
- package/dist/reactive/rest.d.ts.map +1 -0
- package/dist/reactive/scope.d.ts +140 -0
- package/dist/reactive/scope.d.ts.map +1 -0
- package/dist/reactive/signal.d.ts +16 -2
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/to-value.d.ts +57 -0
- package/dist/reactive/to-value.d.ts.map +1 -0
- package/dist/reactive/websocket.d.ts +285 -0
- package/dist/reactive/websocket.d.ts.map +1 -0
- package/dist/reactive-DwkhUJfP.js +1148 -0
- package/dist/reactive-DwkhUJfP.js.map +1 -0
- package/dist/reactive.es.mjs +38 -19
- package/dist/{registry-CWf368tT.js → registry-B08iilIh.js} +1 -1
- package/dist/{registry-CWf368tT.js.map → registry-B08iilIh.js.map} +1 -1
- package/dist/router/constraints.d.ts.map +1 -1
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/state.d.ts +25 -2
- package/dist/router/state.d.ts.map +1 -1
- package/dist/router-CQikC9Ed.js +492 -0
- package/dist/router-CQikC9Ed.js.map +1 -0
- package/dist/router.es.mjs +9 -8
- package/dist/ssr/hydrate.d.ts.map +1 -1
- package/dist/{ssr-B2qd_WBB.js → ssr-_dAcGdzu.js} +4 -4
- package/dist/ssr-_dAcGdzu.js.map +1 -0
- package/dist/ssr.es.mjs +1 -1
- package/dist/store/persisted.d.ts.map +1 -1
- package/dist/{store-DWpyH6p5.js → store-Cb3gPRve.js} +7 -7
- package/dist/store-Cb3gPRve.js.map +1 -0
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs.map +1 -1
- package/dist/{testing-CsqjNUyy.js → testing-C5Sjfsna.js} +8 -8
- package/dist/testing-C5Sjfsna.js.map +1 -0
- package/dist/testing.es.mjs +1 -1
- package/dist/{type-guards-Do9DWgNp.js → type-guards-BMX2c0LP.js} +1 -1
- package/dist/{type-guards-Do9DWgNp.js.map → type-guards-BMX2c0LP.js.map} +1 -1
- package/dist/untrack-D0fnO5k2.js +36 -0
- package/dist/untrack-D0fnO5k2.js.map +1 -0
- package/dist/view/custom-directives.d.ts.map +1 -1
- package/dist/view.es.mjs +4 -4
- package/package.json +177 -177
- package/src/a11y/announce.ts +131 -131
- package/src/a11y/audit.ts +314 -314
- package/src/a11y/index.ts +68 -68
- package/src/a11y/media-preferences.ts +255 -255
- package/src/a11y/roving-tab-index.ts +164 -164
- package/src/a11y/skip-link.ts +255 -255
- package/src/a11y/trap-focus.ts +184 -184
- package/src/a11y/types.ts +183 -183
- package/src/component/component.ts +599 -599
- package/src/component/html.ts +153 -153
- package/src/component/index.ts +52 -52
- package/src/component/library.ts +540 -542
- package/src/component/scope.ts +212 -212
- package/src/component/types.ts +310 -310
- package/src/core/collection.ts +876 -707
- package/src/core/element.ts +1015 -981
- package/src/core/env.ts +60 -60
- package/src/core/index.ts +49 -49
- package/src/core/shared.ts +77 -62
- package/src/core/utils/index.ts +148 -148
- package/src/devtools/devtools.ts +410 -410
- package/src/devtools/index.ts +48 -48
- package/src/devtools/types.ts +104 -104
- package/src/dnd/draggable.ts +296 -296
- package/src/dnd/droppable.ts +228 -228
- package/src/dnd/index.ts +62 -62
- package/src/dnd/sortable.ts +307 -307
- package/src/dnd/types.ts +293 -293
- package/src/forms/create-form.ts +320 -278
- package/src/forms/index.ts +70 -65
- package/src/forms/types.ts +203 -154
- package/src/forms/use-field.ts +231 -0
- package/src/forms/validators.ts +294 -265
- package/src/full.ts +554 -480
- package/src/i18n/formatting.ts +67 -67
- package/src/i18n/i18n.ts +200 -200
- package/src/i18n/index.ts +67 -67
- package/src/i18n/translate.ts +182 -182
- package/src/i18n/types.ts +171 -171
- package/src/index.ts +108 -108
- package/src/media/battery.ts +116 -116
- package/src/media/breakpoints.ts +129 -131
- package/src/media/clipboard.ts +80 -80
- package/src/media/device-sensors.ts +158 -158
- package/src/media/geolocation.ts +119 -119
- package/src/media/index.ts +76 -76
- package/src/media/media-query.ts +92 -92
- package/src/media/network.ts +115 -115
- package/src/media/types.ts +177 -177
- package/src/media/viewport.ts +84 -84
- package/src/motion/index.ts +57 -57
- package/src/motion/morph.ts +151 -151
- package/src/motion/parallax.ts +120 -120
- package/src/motion/reduced-motion.ts +66 -66
- package/src/motion/types.ts +271 -271
- package/src/motion/typewriter.ts +164 -164
- package/src/plugin/index.ts +37 -37
- package/src/plugin/registry.ts +284 -269
- package/src/plugin/types.ts +137 -137
- package/src/reactive/async-data.ts +250 -29
- package/src/reactive/computed.ts +144 -130
- package/src/reactive/effect.ts +29 -6
- package/src/reactive/http.ts +790 -0
- package/src/reactive/index.ts +60 -0
- package/src/reactive/pagination.ts +317 -0
- package/src/reactive/polling.ts +179 -0
- package/src/reactive/readonly.ts +52 -8
- package/src/reactive/rest.ts +859 -0
- package/src/reactive/scope.ts +276 -0
- package/src/reactive/signal.ts +61 -1
- package/src/reactive/to-value.ts +71 -0
- package/src/reactive/websocket.ts +849 -0
- package/src/router/bq-link.ts +279 -279
- package/src/router/constraints.ts +204 -201
- package/src/router/index.ts +49 -49
- package/src/router/match.ts +312 -312
- package/src/router/path-pattern.ts +52 -52
- package/src/router/query.ts +38 -38
- package/src/router/router.ts +421 -402
- package/src/router/state.ts +51 -3
- package/src/router/types.ts +139 -139
- package/src/router/use-route.ts +68 -68
- package/src/router/utils.ts +157 -157
- package/src/security/index.ts +12 -12
- package/src/ssr/hydrate.ts +84 -82
- package/src/ssr/index.ts +70 -70
- package/src/ssr/render.ts +508 -508
- package/src/ssr/serialize.ts +296 -296
- package/src/ssr/types.ts +81 -81
- package/src/store/create-store.ts +467 -467
- package/src/store/index.ts +27 -27
- package/src/store/persisted.ts +245 -249
- package/src/store/types.ts +247 -247
- package/src/store/utils.ts +135 -135
- package/src/storybook/index.ts +480 -480
- package/src/testing/index.ts +42 -42
- package/src/testing/testing.ts +593 -593
- package/src/testing/types.ts +170 -170
- package/src/view/custom-directives.ts +28 -30
- package/src/view/evaluate.ts +292 -292
- package/src/view/process.ts +108 -108
- package/dist/a11y-C5QOVvRn.js.map +0 -1
- package/dist/component-CuuTijA6.js.map +0 -1
- package/dist/constraints-3lV9yyBw.js.map +0 -1
- package/dist/core-Cjl7GUu8.js.map +0 -1
- package/dist/core-DnlyjbF2.js +0 -112
- package/dist/core-DnlyjbF2.js.map +0 -1
- package/dist/custom-directives-7wAShnnd.js.map +0 -1
- package/dist/devtools-D2fQLhDN.js.map +0 -1
- package/dist/dnd-B8EgyzaI.js.map +0 -1
- package/dist/env-NeVmr4Gf.js.map +0 -1
- package/dist/forms-C3yovgH9.js +0 -141
- package/dist/forms-C3yovgH9.js.map +0 -1
- package/dist/i18n-BnnhTFOS.js.map +0 -1
- package/dist/media-Di2Ta22s.js.map +0 -1
- package/dist/motion-qPj_TYGv.js.map +0 -1
- package/dist/mount-SM07RUa6.js.map +0 -1
- package/dist/plugin-cPoOHFLY.js.map +0 -1
- package/dist/reactive-Cfv0RK6x.js +0 -233
- package/dist/reactive-Cfv0RK6x.js.map +0 -1
- package/dist/router-BrthaP_z.js +0 -473
- package/dist/router-BrthaP_z.js.map +0 -1
- package/dist/ssr-B2qd_WBB.js.map +0 -1
- package/dist/store-DWpyH6p5.js.map +0 -1
- package/dist/testing-CsqjNUyy.js.map +0 -1
- package/dist/untrack-DJVQQ2WM.js +0 -33
- package/dist/untrack-DJVQQ2WM.js.map +0 -1
package/src/router/router.ts
CHANGED
|
@@ -1,402 +1,421 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router creation and lifecycle management.
|
|
3
|
-
* @module bquery/router
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { isPrototypePollutionKey } from '../core/utils/object';
|
|
7
|
-
import { createRoute } from './match';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* }
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
history.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Generates a unique key for
|
|
110
|
-
* @internal
|
|
111
|
-
*/
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
scrollPositions.delete(
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Router creation and lifecycle management.
|
|
3
|
+
* @module bquery/router
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isPrototypePollutionKey } from '../core/utils/object';
|
|
7
|
+
import { createRoute } from './match';
|
|
8
|
+
import {
|
|
9
|
+
beginNavigation,
|
|
10
|
+
currentRoute,
|
|
11
|
+
endNavigation,
|
|
12
|
+
getActiveRouter,
|
|
13
|
+
resetNavigationState,
|
|
14
|
+
routeSignal,
|
|
15
|
+
setActiveRouter,
|
|
16
|
+
} from './state';
|
|
17
|
+
import type { NavigationGuard, Route, Router, RouterOptions } from './types';
|
|
18
|
+
import { flattenRoutes } from './utils';
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Router Creation
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
const MAX_SCROLL_POSITION_ENTRIES = 100;
|
|
25
|
+
|
|
26
|
+
const sanitizeHistoryState = (state: Record<string, unknown>): Record<string, unknown> => {
|
|
27
|
+
const sanitized: Record<string, unknown> = {};
|
|
28
|
+
|
|
29
|
+
for (const [key, value] of Object.entries(state)) {
|
|
30
|
+
if (isPrototypePollutionKey(key)) continue;
|
|
31
|
+
sanitized[key] = value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return sanitized;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates and initializes a router instance.
|
|
39
|
+
*
|
|
40
|
+
* @param options - Router configuration
|
|
41
|
+
* @returns The router instance
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { createRouter } from 'bquery/router';
|
|
46
|
+
*
|
|
47
|
+
* const router = createRouter({
|
|
48
|
+
* routes: [
|
|
49
|
+
* { path: '/', component: () => import('./pages/Home') },
|
|
50
|
+
* { path: '/about', component: () => import('./pages/About') },
|
|
51
|
+
* { path: '/user/:id(\\d+)', component: () => import('./pages/User') },
|
|
52
|
+
* { path: '/old-page', redirectTo: '/new-page' },
|
|
53
|
+
* { path: '*', component: () => import('./pages/NotFound') },
|
|
54
|
+
* ],
|
|
55
|
+
* base: '/app',
|
|
56
|
+
* scrollRestoration: true,
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* router.beforeEach((to, from) => {
|
|
60
|
+
* if (to.path === '/admin' && !isAuthenticated()) {
|
|
61
|
+
* return false; // Cancel navigation
|
|
62
|
+
* }
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export const createRouter = (options: RouterOptions): Router => {
|
|
67
|
+
// Clean up any existing router to prevent guard leakage
|
|
68
|
+
const existingRouter = getActiveRouter();
|
|
69
|
+
if (existingRouter) {
|
|
70
|
+
existingRouter.destroy();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { routes, base = '', hash: useHash = false, scrollRestoration = false } = options;
|
|
74
|
+
|
|
75
|
+
// Instance-specific guards and hooks (not shared globally)
|
|
76
|
+
const beforeGuards: NavigationGuard[] = [];
|
|
77
|
+
const afterHooks: Array<(to: Route, from: Route) => void> = [];
|
|
78
|
+
|
|
79
|
+
// Flatten nested routes (base-relative, not including the base path)
|
|
80
|
+
const flatRoutes = flattenRoutes(routes);
|
|
81
|
+
|
|
82
|
+
// Scroll position storage keyed by history state id
|
|
83
|
+
const scrollPositions = new Map<string, { x: number; y: number }>();
|
|
84
|
+
let currentScrollKey = '0';
|
|
85
|
+
let scrollKeyCounter = 0;
|
|
86
|
+
let previousScrollRestoration: History['scrollRestoration'] | null = null;
|
|
87
|
+
|
|
88
|
+
// Enable manual scroll restoration if scrollRestoration is configured
|
|
89
|
+
if (scrollRestoration && typeof history !== 'undefined' && 'scrollRestoration' in history) {
|
|
90
|
+
previousScrollRestoration = history.scrollRestoration;
|
|
91
|
+
if (history.scrollRestoration !== 'manual') {
|
|
92
|
+
history.scrollRestoration = 'manual';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const state =
|
|
96
|
+
history.state && typeof history.state === 'object'
|
|
97
|
+
? (history.state as Record<string, unknown>)
|
|
98
|
+
: {};
|
|
99
|
+
|
|
100
|
+
if (typeof state.__bqScrollKey !== 'string') {
|
|
101
|
+
const currentUrl = useHash
|
|
102
|
+
? window.location.hash || '#/'
|
|
103
|
+
: `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
104
|
+
history.replaceState({ ...state, __bqScrollKey: currentScrollKey }, '', currentUrl);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generates a unique key for the current history entry.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
const getScrollKey = (): string => {
|
|
113
|
+
return (history.state && history.state.__bqScrollKey) || currentScrollKey;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generates a unique key for a new history entry.
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
const createScrollKey = (): string => `${Date.now()}-${scrollKeyCounter++}`;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Saves current scroll position for the current history entry.
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
const saveScrollPosition = (key = getScrollKey()): void => {
|
|
127
|
+
if (!scrollRestoration) return;
|
|
128
|
+
if (scrollPositions.has(key)) {
|
|
129
|
+
// Refresh the insertion order so pruning behaves like an LRU cache.
|
|
130
|
+
scrollPositions.delete(key);
|
|
131
|
+
}
|
|
132
|
+
scrollPositions.set(key, { x: window.scrollX, y: window.scrollY });
|
|
133
|
+
while (scrollPositions.size > MAX_SCROLL_POSITION_ENTRIES) {
|
|
134
|
+
const oldestKey = scrollPositions.keys().next().value as string | undefined;
|
|
135
|
+
if (oldestKey === undefined) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
scrollPositions.delete(oldestKey);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Restores scroll position for the current history entry.
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
const restoreScrollPosition = (key = getScrollKey()): void => {
|
|
147
|
+
if (!scrollRestoration) return;
|
|
148
|
+
const pos = scrollPositions.get(key);
|
|
149
|
+
if (pos) {
|
|
150
|
+
window.scrollTo(pos.x, pos.y);
|
|
151
|
+
} else {
|
|
152
|
+
window.scrollTo(0, 0);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Builds history state for canceled navigations without dropping
|
|
158
|
+
* the scroll restoration key for the current entry.
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
const getRestoreHistoryState = (): Record<string, unknown> => {
|
|
162
|
+
const state =
|
|
163
|
+
history.state && typeof history.state === 'object'
|
|
164
|
+
? { ...(history.state as Record<string, unknown>) }
|
|
165
|
+
: {};
|
|
166
|
+
|
|
167
|
+
if (scrollRestoration) {
|
|
168
|
+
state.__bqScrollKey = currentScrollKey;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return state;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Gets the current path from the URL.
|
|
176
|
+
*/
|
|
177
|
+
const getCurrentPath = (): { pathname: string; search: string; hash: string } => {
|
|
178
|
+
if (useHash) {
|
|
179
|
+
const hashPath = window.location.hash.slice(1) || '/';
|
|
180
|
+
// In hash routing, URL structure is #/path?query#fragment
|
|
181
|
+
// Extract hash fragment first (after the second #)
|
|
182
|
+
const [pathWithQuery, hashPart = ''] = hashPath.split('#');
|
|
183
|
+
// Then extract query from the path
|
|
184
|
+
const [pathname, search = ''] = pathWithQuery.split('?');
|
|
185
|
+
return {
|
|
186
|
+
pathname,
|
|
187
|
+
search: search ? `?${search}` : '',
|
|
188
|
+
hash: hashPart ? `#${hashPart}` : '',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let pathname = window.location.pathname;
|
|
193
|
+
if (base && (pathname === base || pathname.startsWith(base + '/'))) {
|
|
194
|
+
pathname = pathname.slice(base.length) || '/';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
pathname,
|
|
199
|
+
search: window.location.search,
|
|
200
|
+
hash: window.location.hash,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Updates the route signal with current URL state.
|
|
206
|
+
*/
|
|
207
|
+
const syncRoute = (): void => {
|
|
208
|
+
const { pathname, search, hash } = getCurrentPath();
|
|
209
|
+
const newRoute = createRoute(pathname, search, hash, flatRoutes);
|
|
210
|
+
routeSignal.value = newRoute;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Performs navigation with guards.
|
|
215
|
+
*/
|
|
216
|
+
const performNavigation = async (
|
|
217
|
+
path: string,
|
|
218
|
+
method: 'pushState' | 'replaceState',
|
|
219
|
+
visitedPaths: Set<string> = new Set()
|
|
220
|
+
): Promise<void> => {
|
|
221
|
+
beginNavigation();
|
|
222
|
+
try {
|
|
223
|
+
const { pathname, search, hash } = getCurrentPath();
|
|
224
|
+
const from = createRoute(pathname, search, hash, flatRoutes);
|
|
225
|
+
|
|
226
|
+
// Parse the target path
|
|
227
|
+
const url = new URL(path, window.location.origin);
|
|
228
|
+
const resolvedPath = `${url.pathname}${url.search}${url.hash}`;
|
|
229
|
+
if (visitedPaths.has(resolvedPath)) {
|
|
230
|
+
throw new Error(`bQuery router: redirect loop detected for path "${resolvedPath}"`);
|
|
231
|
+
}
|
|
232
|
+
visitedPaths.add(resolvedPath);
|
|
233
|
+
const to = createRoute(url.pathname, url.search, url.hash, flatRoutes);
|
|
234
|
+
|
|
235
|
+
// Check for redirectTo on the matched route
|
|
236
|
+
if (to.matched?.redirectTo) {
|
|
237
|
+
// Navigate to the redirect target instead
|
|
238
|
+
await performNavigation(to.matched.redirectTo, method, visitedPaths);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Run route-level beforeEnter guard
|
|
243
|
+
if (to.matched?.beforeEnter) {
|
|
244
|
+
const result = await to.matched.beforeEnter(to, from);
|
|
245
|
+
if (result === false) {
|
|
246
|
+
return; // Cancel navigation
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Run beforeEach guards
|
|
251
|
+
for (const guard of beforeGuards) {
|
|
252
|
+
const result = await guard(to, from);
|
|
253
|
+
if (result === false) {
|
|
254
|
+
return; // Cancel navigation
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Save scroll position before navigation
|
|
259
|
+
saveScrollPosition();
|
|
260
|
+
|
|
261
|
+
// Update browser history
|
|
262
|
+
const existingScrollKey = scrollRestoration ? getScrollKey() : undefined;
|
|
263
|
+
const scrollKey =
|
|
264
|
+
method === 'replaceState' && existingScrollKey ? existingScrollKey : createScrollKey();
|
|
265
|
+
const fullPath = useHash ? `#${path}` : `${base}${path}`;
|
|
266
|
+
const baseState =
|
|
267
|
+
scrollRestoration && history.state && typeof history.state === 'object'
|
|
268
|
+
? sanitizeHistoryState(history.state as Record<string, unknown>)
|
|
269
|
+
: {};
|
|
270
|
+
const state = scrollRestoration ? { ...baseState, __bqScrollKey: scrollKey } : {};
|
|
271
|
+
history[method](state, '', fullPath);
|
|
272
|
+
currentScrollKey = scrollKey;
|
|
273
|
+
|
|
274
|
+
// Update route signal
|
|
275
|
+
syncRoute();
|
|
276
|
+
|
|
277
|
+
// Scroll to top on push navigation
|
|
278
|
+
if (scrollRestoration && method === 'pushState') {
|
|
279
|
+
window.scrollTo(0, 0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Run afterEach hooks
|
|
283
|
+
for (const hook of afterHooks) {
|
|
284
|
+
hook(routeSignal.value, from);
|
|
285
|
+
}
|
|
286
|
+
} finally {
|
|
287
|
+
endNavigation();
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Handle popstate events (back/forward).
|
|
293
|
+
*/
|
|
294
|
+
const handlePopState = async (event: PopStateEvent): Promise<void> => {
|
|
295
|
+
beginNavigation();
|
|
296
|
+
try {
|
|
297
|
+
const { pathname, search, hash } = getCurrentPath();
|
|
298
|
+
const from = routeSignal.value;
|
|
299
|
+
const to = createRoute(pathname, search, hash, flatRoutes);
|
|
300
|
+
|
|
301
|
+
// Check for redirectTo on the matched route
|
|
302
|
+
if (to.matched?.redirectTo) {
|
|
303
|
+
await performNavigation(to.matched.redirectTo, 'replaceState');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Run route-level beforeEnter guard
|
|
308
|
+
if (to.matched?.beforeEnter) {
|
|
309
|
+
const result = await to.matched.beforeEnter(to, from);
|
|
310
|
+
if (result === false) {
|
|
311
|
+
// Restore previous state with full URL (including query/hash)
|
|
312
|
+
const queryString = new URLSearchParams(
|
|
313
|
+
Object.entries(from.query).flatMap(([key, value]) =>
|
|
314
|
+
Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]
|
|
315
|
+
)
|
|
316
|
+
).toString();
|
|
317
|
+
const searchStr = queryString ? `?${queryString}` : '';
|
|
318
|
+
const hashStr = from.hash ? `#${from.hash}` : '';
|
|
319
|
+
const restorePath = useHash
|
|
320
|
+
? `#${from.path}${searchStr}${hashStr}`
|
|
321
|
+
: `${base}${from.path}${searchStr}${hashStr}`;
|
|
322
|
+
history.replaceState(getRestoreHistoryState(), '', restorePath);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Run beforeEach guards (supports async guards)
|
|
328
|
+
for (const guard of beforeGuards) {
|
|
329
|
+
const result = await guard(to, from);
|
|
330
|
+
if (result === false) {
|
|
331
|
+
// Restore previous state with full URL (including query/hash)
|
|
332
|
+
const queryString = new URLSearchParams(
|
|
333
|
+
Object.entries(from.query).flatMap(([key, value]) =>
|
|
334
|
+
Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]
|
|
335
|
+
)
|
|
336
|
+
).toString();
|
|
337
|
+
const search = queryString ? `?${queryString}` : '';
|
|
338
|
+
const hash = from.hash ? `#${from.hash}` : '';
|
|
339
|
+
const restorePath = useHash
|
|
340
|
+
? `#${from.path}${search}${hash}`
|
|
341
|
+
: `${base}${from.path}${search}${hash}`;
|
|
342
|
+
history.replaceState(getRestoreHistoryState(), '', restorePath);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Save scroll position of the page we're leaving
|
|
348
|
+
saveScrollPosition(currentScrollKey);
|
|
349
|
+
|
|
350
|
+
// Update scroll key from history state
|
|
351
|
+
currentScrollKey =
|
|
352
|
+
(event.state as { __bqScrollKey?: string } | null)?.__bqScrollKey ?? getScrollKey();
|
|
353
|
+
|
|
354
|
+
syncRoute();
|
|
355
|
+
|
|
356
|
+
// Restore scroll position for the entry we're navigating to
|
|
357
|
+
restoreScrollPosition(currentScrollKey);
|
|
358
|
+
|
|
359
|
+
for (const hook of afterHooks) {
|
|
360
|
+
hook(routeSignal.value, from);
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
endNavigation();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Attach popstate listener
|
|
368
|
+
window.addEventListener('popstate', handlePopState);
|
|
369
|
+
|
|
370
|
+
// Initialize route
|
|
371
|
+
syncRoute();
|
|
372
|
+
|
|
373
|
+
const router: Router = {
|
|
374
|
+
push: (path: string) => performNavigation(path, 'pushState'),
|
|
375
|
+
replace: (path: string) => performNavigation(path, 'replaceState'),
|
|
376
|
+
back: () => history.back(),
|
|
377
|
+
forward: () => history.forward(),
|
|
378
|
+
go: (delta: number) => history.go(delta),
|
|
379
|
+
|
|
380
|
+
beforeEach: (guard: NavigationGuard) => {
|
|
381
|
+
beforeGuards.push(guard);
|
|
382
|
+
return () => {
|
|
383
|
+
const index = beforeGuards.indexOf(guard);
|
|
384
|
+
if (index > -1) beforeGuards.splice(index, 1);
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
afterEach: (hook: (to: Route, from: Route) => void) => {
|
|
389
|
+
afterHooks.push(hook);
|
|
390
|
+
return () => {
|
|
391
|
+
const index = afterHooks.indexOf(hook);
|
|
392
|
+
if (index > -1) afterHooks.splice(index, 1);
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
currentRoute,
|
|
397
|
+
routes: flatRoutes,
|
|
398
|
+
base,
|
|
399
|
+
hash: useHash,
|
|
400
|
+
|
|
401
|
+
destroy: () => {
|
|
402
|
+
window.removeEventListener('popstate', handlePopState);
|
|
403
|
+
beforeGuards.length = 0;
|
|
404
|
+
afterHooks.length = 0;
|
|
405
|
+
scrollPositions.clear();
|
|
406
|
+
// Restore the previous scroll restoration mode on destroy
|
|
407
|
+
if (
|
|
408
|
+
previousScrollRestoration !== null &&
|
|
409
|
+
typeof history !== 'undefined' &&
|
|
410
|
+
'scrollRestoration' in history
|
|
411
|
+
) {
|
|
412
|
+
history.scrollRestoration = previousScrollRestoration;
|
|
413
|
+
}
|
|
414
|
+
resetNavigationState();
|
|
415
|
+
setActiveRouter(null);
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
setActiveRouter(router);
|
|
420
|
+
return router;
|
|
421
|
+
};
|