@bquery/bquery 1.6.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.
Files changed (402) hide show
  1. package/README.md +192 -18
  2. package/dist/a11y/announce.d.ts +43 -0
  3. package/dist/a11y/announce.d.ts.map +1 -0
  4. package/dist/a11y/audit.d.ts +42 -0
  5. package/dist/a11y/audit.d.ts.map +1 -0
  6. package/dist/a11y/index.d.ts +53 -0
  7. package/dist/a11y/index.d.ts.map +1 -0
  8. package/dist/a11y/media-preferences.d.ts +77 -0
  9. package/dist/a11y/media-preferences.d.ts.map +1 -0
  10. package/dist/a11y/roving-tab-index.d.ts +38 -0
  11. package/dist/a11y/roving-tab-index.d.ts.map +1 -0
  12. package/dist/a11y/skip-link.d.ts +37 -0
  13. package/dist/a11y/skip-link.d.ts.map +1 -0
  14. package/dist/a11y/trap-focus.d.ts +49 -0
  15. package/dist/a11y/trap-focus.d.ts.map +1 -0
  16. package/dist/a11y/types.d.ts +152 -0
  17. package/dist/a11y/types.d.ts.map +1 -0
  18. package/dist/a11y-DVBCy09c.js +421 -0
  19. package/dist/a11y-DVBCy09c.js.map +1 -0
  20. package/dist/a11y.es.mjs +14 -0
  21. package/dist/component/component.d.ts.map +1 -1
  22. package/dist/component/html.d.ts.map +1 -1
  23. package/dist/component/index.d.ts +2 -1
  24. package/dist/component/index.d.ts.map +1 -1
  25. package/dist/component/library.d.ts.map +1 -1
  26. package/dist/component/scope.d.ts +138 -0
  27. package/dist/component/scope.d.ts.map +1 -0
  28. package/dist/component/types.d.ts +53 -1
  29. package/dist/component/types.d.ts.map +1 -1
  30. package/dist/component-L3-JfOFz.js +684 -0
  31. package/dist/component-L3-JfOFz.js.map +1 -0
  32. package/dist/component.es.mjs +9 -6
  33. package/dist/{config-DRmZZno3.js → config-DhT9auRm.js} +4 -4
  34. package/dist/{config-DRmZZno3.js.map → config-DhT9auRm.js.map} +1 -1
  35. package/dist/constraints-D5RHQLmP.js +100 -0
  36. package/dist/constraints-D5RHQLmP.js.map +1 -0
  37. package/dist/core/collection.d.ts +134 -0
  38. package/dist/core/collection.d.ts.map +1 -1
  39. package/dist/core/element.d.ts +120 -0
  40. package/dist/core/element.d.ts.map +1 -1
  41. package/dist/core/env.d.ts +18 -0
  42. package/dist/core/env.d.ts.map +1 -0
  43. package/dist/core/index.d.ts +1 -0
  44. package/dist/core/index.d.ts.map +1 -1
  45. package/dist/core/shared.d.ts +14 -0
  46. package/dist/core/shared.d.ts.map +1 -1
  47. package/dist/core/utils/index.d.ts +52 -41
  48. package/dist/core/utils/index.d.ts.map +1 -1
  49. package/dist/core-DdtZHzsS.js +168 -0
  50. package/dist/core-DdtZHzsS.js.map +1 -0
  51. package/dist/{core-CCEabVHl.js → core-EMYSLzaT.js} +293 -194
  52. package/dist/core-EMYSLzaT.js.map +1 -0
  53. package/dist/core.es.mjs +48 -46
  54. package/dist/custom-directives-Dr4C5lVV.js +9 -0
  55. package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
  56. package/dist/devtools/devtools.d.ts +212 -0
  57. package/dist/devtools/devtools.d.ts.map +1 -0
  58. package/dist/devtools/index.d.ts +20 -0
  59. package/dist/devtools/index.d.ts.map +1 -0
  60. package/dist/devtools/types.d.ts +69 -0
  61. package/dist/devtools/types.d.ts.map +1 -0
  62. package/dist/devtools-BhB2iDPT.js +122 -0
  63. package/dist/devtools-BhB2iDPT.js.map +1 -0
  64. package/dist/devtools.es.mjs +19 -0
  65. package/dist/dnd/draggable.d.ts +51 -0
  66. package/dist/dnd/draggable.d.ts.map +1 -0
  67. package/dist/dnd/droppable.d.ts +38 -0
  68. package/dist/dnd/droppable.d.ts.map +1 -0
  69. package/dist/dnd/index.d.ts +47 -0
  70. package/dist/dnd/index.d.ts.map +1 -0
  71. package/dist/dnd/sortable.d.ts +43 -0
  72. package/dist/dnd/sortable.d.ts.map +1 -0
  73. package/dist/dnd/types.d.ts +250 -0
  74. package/dist/dnd/types.d.ts.map +1 -0
  75. package/dist/dnd-NwZBYh4l.js +244 -0
  76. package/dist/dnd-NwZBYh4l.js.map +1 -0
  77. package/dist/dnd.es.mjs +6 -0
  78. package/dist/env-CTdvLaH2.js +19 -0
  79. package/dist/env-CTdvLaH2.js.map +1 -0
  80. package/dist/forms/create-form.d.ts +49 -0
  81. package/dist/forms/create-form.d.ts.map +1 -0
  82. package/dist/forms/index.d.ts +40 -0
  83. package/dist/forms/index.d.ts.map +1 -0
  84. package/dist/forms/types.d.ts +185 -0
  85. package/dist/forms/types.d.ts.map +1 -0
  86. package/dist/forms/use-field.d.ts +34 -0
  87. package/dist/forms/use-field.d.ts.map +1 -0
  88. package/dist/forms/validators.d.ts +204 -0
  89. package/dist/forms/validators.d.ts.map +1 -0
  90. package/dist/forms-UcRHsYxC.js +227 -0
  91. package/dist/forms-UcRHsYxC.js.map +1 -0
  92. package/dist/forms.es.mjs +16 -0
  93. package/dist/full.d.ts +30 -11
  94. package/dist/full.d.ts.map +1 -1
  95. package/dist/full.es.mjs +209 -93
  96. package/dist/full.iife.js +47 -31
  97. package/dist/full.iife.js.map +1 -1
  98. package/dist/full.umd.js +47 -31
  99. package/dist/full.umd.js.map +1 -1
  100. package/dist/function-Cybd57JV.js +33 -0
  101. package/dist/function-Cybd57JV.js.map +1 -0
  102. package/dist/i18n/formatting.d.ts +40 -0
  103. package/dist/i18n/formatting.d.ts.map +1 -0
  104. package/dist/i18n/i18n.d.ts +48 -0
  105. package/dist/i18n/i18n.d.ts.map +1 -0
  106. package/dist/i18n/index.d.ts +57 -0
  107. package/dist/i18n/index.d.ts.map +1 -0
  108. package/dist/i18n/translate.d.ts +83 -0
  109. package/dist/i18n/translate.d.ts.map +1 -0
  110. package/dist/i18n/types.d.ts +156 -0
  111. package/dist/i18n/types.d.ts.map +1 -0
  112. package/dist/i18n-kuF6Ekj6.js +89 -0
  113. package/dist/i18n-kuF6Ekj6.js.map +1 -0
  114. package/dist/i18n.es.mjs +6 -0
  115. package/dist/index.d.ts +11 -0
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.es.mjs +257 -143
  118. package/dist/media/battery.d.ts +35 -0
  119. package/dist/media/battery.d.ts.map +1 -0
  120. package/dist/media/breakpoints.d.ts +51 -0
  121. package/dist/media/breakpoints.d.ts.map +1 -0
  122. package/dist/media/clipboard.d.ts +30 -0
  123. package/dist/media/clipboard.d.ts.map +1 -0
  124. package/dist/media/device-sensors.d.ts +54 -0
  125. package/dist/media/device-sensors.d.ts.map +1 -0
  126. package/dist/media/geolocation.d.ts +38 -0
  127. package/dist/media/geolocation.d.ts.map +1 -0
  128. package/dist/media/index.d.ts +42 -0
  129. package/dist/media/index.d.ts.map +1 -0
  130. package/dist/media/media-query.d.ts +36 -0
  131. package/dist/media/media-query.d.ts.map +1 -0
  132. package/dist/media/network.d.ts +35 -0
  133. package/dist/media/network.d.ts.map +1 -0
  134. package/dist/media/types.d.ts +173 -0
  135. package/dist/media/types.d.ts.map +1 -0
  136. package/dist/media/viewport.d.ts +32 -0
  137. package/dist/media/viewport.d.ts.map +1 -0
  138. package/dist/media-i-fB5WxI.js +340 -0
  139. package/dist/media-i-fB5WxI.js.map +1 -0
  140. package/dist/media.es.mjs +12 -0
  141. package/dist/motion/index.d.ts +7 -3
  142. package/dist/motion/index.d.ts.map +1 -1
  143. package/dist/motion/morph.d.ts +27 -0
  144. package/dist/motion/morph.d.ts.map +1 -0
  145. package/dist/motion/parallax.d.ts +30 -0
  146. package/dist/motion/parallax.d.ts.map +1 -0
  147. package/dist/motion/reduced-motion.d.ts +36 -3
  148. package/dist/motion/reduced-motion.d.ts.map +1 -1
  149. package/dist/motion/types.d.ts +58 -0
  150. package/dist/motion/types.d.ts.map +1 -1
  151. package/dist/motion/typewriter.d.ts +31 -0
  152. package/dist/motion/typewriter.d.ts.map +1 -0
  153. package/dist/motion-BJsAuULb.js +530 -0
  154. package/dist/motion-BJsAuULb.js.map +1 -0
  155. package/dist/motion.es.mjs +27 -23
  156. package/dist/{view-C70lA3vf.js → mount-B4Y8bk8Z.js} +166 -160
  157. package/dist/mount-B4Y8bk8Z.js.map +1 -0
  158. package/dist/{object-qGpWr6-J.js → object-BCk-1c8T.js} +5 -4
  159. package/dist/{object-qGpWr6-J.js.map → object-BCk-1c8T.js.map} +1 -1
  160. package/dist/{platform-Dr9b6fsq.js → platform-Dw2gE3zI.js} +21 -22
  161. package/dist/{platform-Dr9b6fsq.js.map → platform-Dw2gE3zI.js.map} +1 -1
  162. package/dist/platform.es.mjs +2 -2
  163. package/dist/plugin/index.d.ts +22 -0
  164. package/dist/plugin/index.d.ts.map +1 -0
  165. package/dist/plugin/registry.d.ts +108 -0
  166. package/dist/plugin/registry.d.ts.map +1 -0
  167. package/dist/plugin/types.d.ts +110 -0
  168. package/dist/plugin/types.d.ts.map +1 -0
  169. package/dist/plugin-C2WuC8SF.js +66 -0
  170. package/dist/plugin-C2WuC8SF.js.map +1 -0
  171. package/dist/plugin.es.mjs +9 -0
  172. package/dist/reactive/async-data.d.ts +28 -3
  173. package/dist/reactive/async-data.d.ts.map +1 -1
  174. package/dist/reactive/computed.d.ts +10 -0
  175. package/dist/reactive/computed.d.ts.map +1 -1
  176. package/dist/reactive/effect.d.ts +3 -0
  177. package/dist/reactive/effect.d.ts.map +1 -1
  178. package/dist/reactive/http.d.ts +194 -0
  179. package/dist/reactive/http.d.ts.map +1 -0
  180. package/dist/reactive/index.d.ts +2 -2
  181. package/dist/reactive/index.d.ts.map +1 -1
  182. package/dist/reactive/pagination.d.ts +126 -0
  183. package/dist/reactive/pagination.d.ts.map +1 -0
  184. package/dist/reactive/polling.d.ts +55 -0
  185. package/dist/reactive/polling.d.ts.map +1 -0
  186. package/dist/reactive/readonly.d.ts +20 -1
  187. package/dist/reactive/readonly.d.ts.map +1 -1
  188. package/dist/reactive/rest.d.ts +293 -0
  189. package/dist/reactive/rest.d.ts.map +1 -0
  190. package/dist/reactive/scope.d.ts +140 -0
  191. package/dist/reactive/scope.d.ts.map +1 -0
  192. package/dist/reactive/signal.d.ts +16 -2
  193. package/dist/reactive/signal.d.ts.map +1 -1
  194. package/dist/reactive/to-value.d.ts +57 -0
  195. package/dist/reactive/to-value.d.ts.map +1 -0
  196. package/dist/reactive/websocket.d.ts +285 -0
  197. package/dist/reactive/websocket.d.ts.map +1 -0
  198. package/dist/reactive-DwkhUJfP.js +1148 -0
  199. package/dist/reactive-DwkhUJfP.js.map +1 -0
  200. package/dist/reactive.es.mjs +38 -20
  201. package/dist/registry-B08iilIh.js +26 -0
  202. package/dist/registry-B08iilIh.js.map +1 -0
  203. package/dist/router/bq-link.d.ts +112 -0
  204. package/dist/router/bq-link.d.ts.map +1 -0
  205. package/dist/router/constraints.d.ts +9 -0
  206. package/dist/router/constraints.d.ts.map +1 -0
  207. package/dist/router/index.d.ts +15 -7
  208. package/dist/router/index.d.ts.map +1 -1
  209. package/dist/router/match.d.ts +0 -1
  210. package/dist/router/match.d.ts.map +1 -1
  211. package/dist/router/path-pattern.d.ts +14 -0
  212. package/dist/router/path-pattern.d.ts.map +1 -0
  213. package/dist/router/query.d.ts.map +1 -1
  214. package/dist/router/router.d.ts +3 -1
  215. package/dist/router/router.d.ts.map +1 -1
  216. package/dist/router/state.d.ts +25 -2
  217. package/dist/router/state.d.ts.map +1 -1
  218. package/dist/router/types.d.ts +48 -4
  219. package/dist/router/types.d.ts.map +1 -1
  220. package/dist/router/use-route.d.ts +50 -0
  221. package/dist/router/use-route.d.ts.map +1 -0
  222. package/dist/router/utils.d.ts +3 -0
  223. package/dist/router/utils.d.ts.map +1 -1
  224. package/dist/router-CQikC9Ed.js +492 -0
  225. package/dist/router-CQikC9Ed.js.map +1 -0
  226. package/dist/router.es.mjs +14 -10
  227. package/dist/{sanitize-Bs2dkMby.js → sanitize-B1V4JswB.js} +2 -1
  228. package/dist/{sanitize-Bs2dkMby.js.map → sanitize-B1V4JswB.js.map} +1 -1
  229. package/dist/security/index.d.ts +2 -2
  230. package/dist/security/index.d.ts.map +1 -1
  231. package/dist/security.es.mjs +1 -1
  232. package/dist/ssr/hydrate.d.ts +65 -0
  233. package/dist/ssr/hydrate.d.ts.map +1 -0
  234. package/dist/ssr/index.d.ts +59 -0
  235. package/dist/ssr/index.d.ts.map +1 -0
  236. package/dist/ssr/render.d.ts +62 -0
  237. package/dist/ssr/render.d.ts.map +1 -0
  238. package/dist/ssr/serialize.d.ts +118 -0
  239. package/dist/ssr/serialize.d.ts.map +1 -0
  240. package/dist/ssr/types.d.ts +70 -0
  241. package/dist/ssr/types.d.ts.map +1 -0
  242. package/dist/ssr-_dAcGdzu.js +248 -0
  243. package/dist/ssr-_dAcGdzu.js.map +1 -0
  244. package/dist/ssr.es.mjs +9 -0
  245. package/dist/store/create-store.d.ts.map +1 -1
  246. package/dist/store/index.d.ts +1 -1
  247. package/dist/store/index.d.ts.map +1 -1
  248. package/dist/store/persisted.d.ts +38 -4
  249. package/dist/store/persisted.d.ts.map +1 -1
  250. package/dist/store/types.d.ts +138 -1
  251. package/dist/store/types.d.ts.map +1 -1
  252. package/dist/store/utils.d.ts +2 -2
  253. package/dist/store/utils.d.ts.map +1 -1
  254. package/dist/store-Cb3gPRve.js +338 -0
  255. package/dist/store-Cb3gPRve.js.map +1 -0
  256. package/dist/store.es.mjs +11 -10
  257. package/dist/storybook/index.d.ts.map +1 -1
  258. package/dist/storybook.es.mjs +1 -1
  259. package/dist/storybook.es.mjs.map +1 -1
  260. package/dist/testing/index.d.ts +23 -0
  261. package/dist/testing/index.d.ts.map +1 -0
  262. package/dist/testing/testing.d.ts +156 -0
  263. package/dist/testing/testing.d.ts.map +1 -0
  264. package/dist/testing/types.d.ts +134 -0
  265. package/dist/testing/types.d.ts.map +1 -0
  266. package/dist/testing-C5Sjfsna.js +224 -0
  267. package/dist/testing-C5Sjfsna.js.map +1 -0
  268. package/dist/testing.es.mjs +9 -0
  269. package/dist/type-guards-BMX2c0LP.js +44 -0
  270. package/dist/type-guards-BMX2c0LP.js.map +1 -0
  271. package/dist/untrack-D0fnO5k2.js +36 -0
  272. package/dist/untrack-D0fnO5k2.js.map +1 -0
  273. package/dist/view/custom-directives.d.ts +20 -0
  274. package/dist/view/custom-directives.d.ts.map +1 -0
  275. package/dist/view/evaluate.d.ts.map +1 -1
  276. package/dist/view/process.d.ts.map +1 -1
  277. package/dist/view.es.mjs +9 -9
  278. package/package.json +47 -11
  279. package/src/a11y/announce.ts +131 -0
  280. package/src/a11y/audit.ts +314 -0
  281. package/src/a11y/index.ts +68 -0
  282. package/src/a11y/media-preferences.ts +255 -0
  283. package/src/a11y/roving-tab-index.ts +164 -0
  284. package/src/a11y/skip-link.ts +255 -0
  285. package/src/a11y/trap-focus.ts +184 -0
  286. package/src/a11y/types.ts +183 -0
  287. package/src/component/component.ts +599 -524
  288. package/src/component/html.ts +153 -153
  289. package/src/component/index.ts +52 -50
  290. package/src/component/library.ts +540 -518
  291. package/src/component/scope.ts +212 -0
  292. package/src/component/types.ts +310 -256
  293. package/src/core/collection.ts +249 -1
  294. package/src/core/element.ts +252 -11
  295. package/src/core/env.ts +60 -0
  296. package/src/core/index.ts +1 -0
  297. package/src/core/shared.ts +64 -0
  298. package/src/core/utils/index.ts +66 -1
  299. package/src/devtools/devtools.ts +410 -0
  300. package/src/devtools/index.ts +48 -0
  301. package/src/devtools/types.ts +104 -0
  302. package/src/dnd/draggable.ts +296 -0
  303. package/src/dnd/droppable.ts +228 -0
  304. package/src/dnd/index.ts +62 -0
  305. package/src/dnd/sortable.ts +307 -0
  306. package/src/dnd/types.ts +293 -0
  307. package/src/forms/create-form.ts +320 -0
  308. package/src/forms/index.ts +70 -0
  309. package/src/forms/types.ts +203 -0
  310. package/src/forms/use-field.ts +231 -0
  311. package/src/forms/validators.ts +294 -0
  312. package/src/full.ts +554 -229
  313. package/src/i18n/formatting.ts +67 -0
  314. package/src/i18n/i18n.ts +200 -0
  315. package/src/i18n/index.ts +67 -0
  316. package/src/i18n/translate.ts +182 -0
  317. package/src/i18n/types.ts +171 -0
  318. package/src/index.ts +72 -0
  319. package/src/media/battery.ts +116 -0
  320. package/src/media/breakpoints.ts +129 -0
  321. package/src/media/clipboard.ts +80 -0
  322. package/src/media/device-sensors.ts +158 -0
  323. package/src/media/geolocation.ts +119 -0
  324. package/src/media/index.ts +76 -0
  325. package/src/media/media-query.ts +92 -0
  326. package/src/media/network.ts +115 -0
  327. package/src/media/types.ts +177 -0
  328. package/src/media/viewport.ts +84 -0
  329. package/src/motion/index.ts +11 -2
  330. package/src/motion/morph.ts +151 -0
  331. package/src/motion/parallax.ts +120 -0
  332. package/src/motion/reduced-motion.ts +52 -3
  333. package/src/motion/types.ts +63 -0
  334. package/src/motion/typewriter.ts +164 -0
  335. package/src/plugin/index.ts +37 -0
  336. package/src/plugin/registry.ts +284 -0
  337. package/src/plugin/types.ts +137 -0
  338. package/src/reactive/async-data.ts +250 -29
  339. package/src/reactive/computed.ts +53 -1
  340. package/src/reactive/effect.ts +29 -6
  341. package/src/reactive/http.ts +790 -0
  342. package/src/reactive/index.ts +60 -0
  343. package/src/reactive/pagination.ts +317 -0
  344. package/src/reactive/polling.ts +179 -0
  345. package/src/reactive/readonly.ts +52 -8
  346. package/src/reactive/rest.ts +859 -0
  347. package/src/reactive/scope.ts +276 -0
  348. package/src/reactive/signal.ts +61 -1
  349. package/src/reactive/to-value.ts +71 -0
  350. package/src/reactive/websocket.ts +849 -0
  351. package/src/router/bq-link.ts +279 -0
  352. package/src/router/constraints.ts +204 -0
  353. package/src/router/index.ts +15 -7
  354. package/src/router/match.ts +255 -49
  355. package/src/router/path-pattern.ts +52 -0
  356. package/src/router/query.ts +3 -0
  357. package/src/router/router.ts +258 -48
  358. package/src/router/state.ts +51 -3
  359. package/src/router/types.ts +50 -4
  360. package/src/router/use-route.ts +68 -0
  361. package/src/router/utils.ts +44 -3
  362. package/src/security/index.ts +12 -17
  363. package/src/security/sanitize.ts +70 -70
  364. package/src/security/trusted-html.ts +71 -71
  365. package/src/ssr/hydrate.ts +84 -0
  366. package/src/ssr/index.ts +70 -0
  367. package/src/ssr/render.ts +508 -0
  368. package/src/ssr/serialize.ts +296 -0
  369. package/src/ssr/types.ts +81 -0
  370. package/src/store/create-store.ts +146 -8
  371. package/src/store/define-store.ts +49 -49
  372. package/src/store/index.ts +5 -0
  373. package/src/store/mapping.ts +74 -74
  374. package/src/store/persisted.ts +245 -62
  375. package/src/store/types.ts +247 -92
  376. package/src/store/utils.ts +4 -10
  377. package/src/store/watch.ts +53 -53
  378. package/src/storybook/index.ts +480 -479
  379. package/src/testing/index.ts +42 -0
  380. package/src/testing/testing.ts +593 -0
  381. package/src/testing/types.ts +170 -0
  382. package/src/view/custom-directives.ts +28 -0
  383. package/src/view/evaluate.ts +2 -0
  384. package/src/view/process.ts +19 -3
  385. package/dist/component-BEQgt5hl.js +0 -600
  386. package/dist/component-BEQgt5hl.js.map +0 -1
  387. package/dist/core-BGQJVw0-.js +0 -35
  388. package/dist/core-BGQJVw0-.js.map +0 -1
  389. package/dist/core-CCEabVHl.js.map +0 -1
  390. package/dist/effect-AFRW_Plg.js +0 -84
  391. package/dist/effect-AFRW_Plg.js.map +0 -1
  392. package/dist/motion-D9TcHxOF.js +0 -415
  393. package/dist/motion-D9TcHxOF.js.map +0 -1
  394. package/dist/reactive-DSkct0dO.js +0 -254
  395. package/dist/reactive-DSkct0dO.js.map +0 -1
  396. package/dist/router-CbDhl8rS.js +0 -188
  397. package/dist/router-CbDhl8rS.js.map +0 -1
  398. package/dist/store-BwDvI45q.js +0 -263
  399. package/dist/store-BwDvI45q.js.map +0 -1
  400. package/dist/untrack-B0rVscTc.js +0 -7
  401. package/dist/untrack-B0rVscTc.js.map +0 -1
  402. package/dist/view-C70lA3vf.js.map +0 -1
@@ -3,8 +3,17 @@
3
3
  * @module bquery/router
4
4
  */
5
5
 
6
+ import { isPrototypePollutionKey } from '../core/utils/object';
6
7
  import { createRoute } from './match';
7
- import { currentRoute, getActiveRouter, routeSignal, setActiveRouter } from './state';
8
+ import {
9
+ beginNavigation,
10
+ currentRoute,
11
+ endNavigation,
12
+ getActiveRouter,
13
+ resetNavigationState,
14
+ routeSignal,
15
+ setActiveRouter,
16
+ } from './state';
8
17
  import type { NavigationGuard, Route, Router, RouterOptions } from './types';
9
18
  import { flattenRoutes } from './utils';
10
19
 
@@ -12,6 +21,19 @@ import { flattenRoutes } from './utils';
12
21
  // Router Creation
13
22
  // ============================================================================
14
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
+
15
37
  /**
16
38
  * Creates and initializes a router instance.
17
39
  *
@@ -26,10 +48,12 @@ import { flattenRoutes } from './utils';
26
48
  * routes: [
27
49
  * { path: '/', component: () => import('./pages/Home') },
28
50
  * { path: '/about', component: () => import('./pages/About') },
29
- * { path: '/user/:id', component: () => import('./pages/User') },
51
+ * { path: '/user/:id(\\d+)', component: () => import('./pages/User') },
52
+ * { path: '/old-page', redirectTo: '/new-page' },
30
53
  * { path: '*', component: () => import('./pages/NotFound') },
31
54
  * ],
32
55
  * base: '/app',
56
+ * scrollRestoration: true,
33
57
  * });
34
58
  *
35
59
  * router.beforeEach((to, from) => {
@@ -46,7 +70,7 @@ export const createRouter = (options: RouterOptions): Router => {
46
70
  existingRouter.destroy();
47
71
  }
48
72
 
49
- const { routes, base = '', hash: useHash = false } = options;
73
+ const { routes, base = '', hash: useHash = false, scrollRestoration = false } = options;
50
74
 
51
75
  // Instance-specific guards and hooks (not shared globally)
52
76
  const beforeGuards: NavigationGuard[] = [];
@@ -55,6 +79,98 @@ export const createRouter = (options: RouterOptions): Router => {
55
79
  // Flatten nested routes (base-relative, not including the base path)
56
80
  const flatRoutes = flattenRoutes(routes);
57
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
+
58
174
  /**
59
175
  * Gets the current path from the URL.
60
176
  */
@@ -99,68 +215,152 @@ export const createRouter = (options: RouterOptions): Router => {
99
215
  */
100
216
  const performNavigation = async (
101
217
  path: string,
102
- method: 'pushState' | 'replaceState'
218
+ method: 'pushState' | 'replaceState',
219
+ visitedPaths: Set<string> = new Set()
103
220
  ): Promise<void> => {
104
- const { pathname, search, hash } = getCurrentPath();
105
- const from = createRoute(pathname, search, hash, flatRoutes);
221
+ beginNavigation();
222
+ try {
223
+ const { pathname, search, hash } = getCurrentPath();
224
+ const from = createRoute(pathname, search, hash, flatRoutes);
106
225
 
107
- // Parse the target path
108
- const url = new URL(path, window.location.origin);
109
- const to = createRoute(url.pathname, url.search, url.hash, flatRoutes);
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);
110
234
 
111
- // Run beforeEach guards
112
- for (const guard of beforeGuards) {
113
- const result = await guard(to, from);
114
- if (result === false) {
115
- return; // Cancel navigation
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;
116
240
  }
117
- }
118
241
 
119
- // Update browser history
120
- const fullPath = useHash ? `#${path}` : `${base}${path}`;
121
- history[method]({}, '', fullPath);
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;
122
273
 
123
- // Update route signal
124
- syncRoute();
274
+ // Update route signal
275
+ syncRoute();
125
276
 
126
- // Run afterEach hooks
127
- for (const hook of afterHooks) {
128
- hook(routeSignal.value, from);
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();
129
288
  }
130
289
  };
131
290
 
132
291
  /**
133
292
  * Handle popstate events (back/forward).
134
293
  */
135
- const handlePopState = async (): Promise<void> => {
136
- const { pathname, search, hash } = getCurrentPath();
137
- const from = routeSignal.value;
138
- const to = createRoute(pathname, search, hash, flatRoutes);
139
-
140
- // Run beforeEach guards (supports async guards)
141
- for (const guard of beforeGuards) {
142
- const result = await guard(to, from);
143
- if (result === false) {
144
- // Restore previous state with full URL (including query/hash)
145
- const queryString = new URLSearchParams(
146
- Object.entries(from.query).flatMap(([key, value]) =>
147
- Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]
148
- )
149
- ).toString();
150
- const search = queryString ? `?${queryString}` : '';
151
- const hash = from.hash ? `#${from.hash}` : '';
152
- const restorePath = useHash
153
- ? `#${from.path}${search}${hash}`
154
- : `${base}${from.path}${search}${hash}`;
155
- history.replaceState({}, '', restorePath);
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');
156
304
  return;
157
305
  }
158
- }
159
306
 
160
- syncRoute();
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
+ }
161
326
 
162
- for (const hook of afterHooks) {
163
- hook(routeSignal.value, from);
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();
164
364
  }
165
365
  };
166
366
 
@@ -202,6 +402,16 @@ export const createRouter = (options: RouterOptions): Router => {
202
402
  window.removeEventListener('popstate', handlePopState);
203
403
  beforeGuards.length = 0;
204
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();
205
415
  setActiveRouter(null);
206
416
  },
207
417
  };
@@ -3,7 +3,7 @@
3
3
  * @module bquery/router
4
4
  */
5
5
 
6
- import { computed, signal, type ReadonlySignal, type Signal } from '../reactive/index';
6
+ import { computed, readonly, signal, type ReadonlySignal, type Signal } from '../reactive/index';
7
7
  import type { Route, Router } from './types';
8
8
 
9
9
  // ============================================================================
@@ -27,8 +27,8 @@ export const routeSignal: Signal<Route> = signal<Route>({
27
27
  *
28
28
  * @example
29
29
  * ```ts
30
- * import { currentRoute } from 'bquery/router';
31
- * import { effect } from 'bquery/reactive';
30
+ * import { currentRoute } from '@bquery/bquery/router';
31
+ * import { effect } from '@bquery/bquery/reactive';
32
32
  *
33
33
  * effect(() => {
34
34
  * document.title = `Page: ${currentRoute.value.path}`;
@@ -37,6 +37,54 @@ export const routeSignal: Signal<Route> = signal<Route>({
37
37
  */
38
38
  export const currentRoute: ReadonlySignal<Route> = computed(() => routeSignal.value);
39
39
 
40
+ /** @internal */
41
+ const navigationCountSignal: Signal<number> = signal(0);
42
+
43
+ /** @internal */
44
+ const isNavigatingSignal: Signal<boolean> = signal(false);
45
+
46
+ /**
47
+ * Reactive signal indicating whether a navigation is currently in progress.
48
+ *
49
+ * This becomes `true` while async guards or redirect resolution are running,
50
+ * then flips back to `false` once navigation finishes or is canceled.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import { isNavigating } from '@bquery/bquery/router';
55
+ * import { effect } from '@bquery/bquery/reactive';
56
+ *
57
+ * effect(() => {
58
+ * document.body.toggleAttribute('data-loading-route', isNavigating.value);
59
+ * });
60
+ * ```
61
+ */
62
+ export const isNavigating: ReadonlySignal<boolean> = readonly(isNavigatingSignal);
63
+
64
+ /** @internal */
65
+ export const beginNavigation = (): void => {
66
+ if (navigationCountSignal.value === 0) {
67
+ isNavigatingSignal.value = true;
68
+ }
69
+ navigationCountSignal.value += 1;
70
+ };
71
+
72
+ /** @internal */
73
+ export const endNavigation = (): void => {
74
+ const nextCount = Math.max(0, navigationCountSignal.value - 1);
75
+ navigationCountSignal.value = nextCount;
76
+
77
+ if (nextCount === 0) {
78
+ isNavigatingSignal.value = false;
79
+ }
80
+ };
81
+
82
+ /** @internal */
83
+ export const resetNavigationState = (): void => {
84
+ navigationCountSignal.value = 0;
85
+ isNavigatingSignal.value = false;
86
+ };
87
+
40
88
  /** @internal */
41
89
  export const getActiveRouter = (): Router | null => activeRouter;
42
90
 
@@ -32,19 +32,59 @@ export type Route = {
32
32
  /**
33
33
  * Route definition for configuration.
34
34
  */
35
- export type RouteDefinition = {
36
- /** Path pattern (e.g., '/user/:id', '/posts/*') */
35
+ type BaseRouteDefinition = {
36
+ /**
37
+ * Path pattern (e.g., '/user/:id', '/posts/*').
38
+ * Supports regex constraints on params: `/user/:id(\\d+)`.
39
+ * Constraint backreferences are not supported.
40
+ */
37
41
  path: string;
38
- /** Component loader (sync or async) */
39
- component: () => unknown | Promise<unknown>;
40
42
  /** Optional route name for programmatic navigation */
41
43
  name?: string;
42
44
  /** Optional metadata */
43
45
  meta?: Record<string, unknown>;
44
46
  /** Nested child routes */
45
47
  children?: RouteDefinition[];
48
+ /**
49
+ * Per-route navigation guard. Called before entering this route.
50
+ * Return `false` to cancel navigation.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * {
55
+ * path: '/admin',
56
+ * component: () => import('./Admin'),
57
+ * beforeEnter: (to, from) => isAuthenticated() || false,
58
+ * }
59
+ * ```
60
+ */
61
+ beforeEnter?: NavigationGuard;
46
62
  };
47
63
 
64
+ type ComponentRouteDefinition = BaseRouteDefinition & {
65
+ /** Component loader (sync or async) */
66
+ component: () => unknown | Promise<unknown>;
67
+ redirectTo?: never;
68
+ };
69
+
70
+ type RedirectRouteDefinition = BaseRouteDefinition & {
71
+ /**
72
+ * Redirect target path. When the route is matched, the router
73
+ * automatically navigates to this path instead.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * { path: '/old-page', redirectTo: '/new-page' }
78
+ * ```
79
+ */
80
+ redirectTo: string;
81
+ component?: never;
82
+ children?: never;
83
+ beforeEnter?: never;
84
+ };
85
+
86
+ export type RouteDefinition = ComponentRouteDefinition | RedirectRouteDefinition;
87
+
48
88
  /**
49
89
  * Router configuration options.
50
90
  */
@@ -55,6 +95,12 @@ export type RouterOptions = {
55
95
  base?: string;
56
96
  /** Use hash-based routing instead of history (default: false) */
57
97
  hash?: boolean;
98
+ /**
99
+ * Restore scroll position on back/forward navigation (default: false).
100
+ * When enabled, the router saves scroll positions for each history entry
101
+ * and restores them on popstate events.
102
+ */
103
+ scrollRestoration?: boolean;
58
104
  };
59
105
 
60
106
  /**
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Reactive route composable.
3
+ * @module bquery/router
4
+ */
5
+
6
+ import { computed, type ReadonlySignal } from '../reactive/index';
7
+ import { routeSignal } from './state';
8
+ import type { Route, RouteDefinition } from './types';
9
+
10
+ // ============================================================================
11
+ // useRoute Composable
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Return type for {@link useRoute}.
16
+ * Provides reactive access to individual route properties.
17
+ */
18
+ export type UseRouteReturn = {
19
+ /** Full reactive route object */
20
+ route: ReadonlySignal<Route>;
21
+ /** Reactive current path */
22
+ path: ReadonlySignal<string>;
23
+ /** Reactive route params */
24
+ params: ReadonlySignal<Record<string, string>>;
25
+ /** Reactive query params */
26
+ query: ReadonlySignal<Record<string, string | string[]>>;
27
+ /** Reactive hash fragment (without #) */
28
+ hash: ReadonlySignal<string>;
29
+ /** Reactive matched route definition */
30
+ matched: ReadonlySignal<RouteDefinition | null>;
31
+ };
32
+
33
+ const route = computed(() => routeSignal.value);
34
+ const path = computed(() => route.value.path);
35
+ const params = computed(() => route.value.params);
36
+ const query = computed(() => route.value.query);
37
+ const hash = computed(() => route.value.hash);
38
+ const matched = computed(() => route.value.matched);
39
+
40
+ const routeHandle: UseRouteReturn = { route, path, params, query, hash, matched };
41
+
42
+ /**
43
+ * Returns reactive access to the current route, params, query, and hash.
44
+ *
45
+ * Each property is a readonly computed signal that updates automatically
46
+ * when the route changes. This is useful for fine-grained reactivity
47
+ * where you only need to subscribe to specific route parts.
48
+ *
49
+ * @returns An object with reactive route properties
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { useRoute } from '@bquery/bquery/router';
54
+ * import { effect } from '@bquery/bquery/reactive';
55
+ *
56
+ * const { path, params, query, hash } = useRoute();
57
+ *
58
+ * effect(() => {
59
+ * console.log('Path:', path.value);
60
+ * console.log('Params:', params.value);
61
+ * console.log('Query:', query.value);
62
+ * console.log('Hash:', hash.value);
63
+ * });
64
+ * ```
65
+ */
66
+ export const useRoute = (): UseRouteReturn => {
67
+ return routeHandle;
68
+ };
@@ -4,6 +4,8 @@
4
4
  */
5
5
 
6
6
  import { computed, type ReadonlySignal } from '../reactive/index';
7
+ import { getRouteConstraintRegex } from './constraints';
8
+ import { isParamChar, isParamStart, readConstraint } from './path-pattern';
7
9
  import { getActiveRouter, routeSignal } from './state';
8
10
  import type { RouteDefinition } from './types';
9
11
 
@@ -41,6 +43,9 @@ export const flattenRoutes = (routes: RouteDefinition[], parentPath = ''): Route
41
43
  * @param name - The route name
42
44
  * @param params - Route params to interpolate
43
45
  * @returns The resolved path
46
+ * @throws {Error} If no router is initialized, the route name is unknown,
47
+ * a required path param is missing from `params`, a param value does not satisfy
48
+ * its route regex constraint, or a route param constraint has invalid syntax
44
49
  *
45
50
  * @example
46
51
  * ```ts
@@ -61,9 +66,45 @@ export const resolve = (name: string, params: Record<string, string> = {}): stri
61
66
  throw new Error(`bQuery router: Route "${name}" not found.`);
62
67
  }
63
68
 
64
- let path = route.path;
65
- for (const [key, value] of Object.entries(params)) {
66
- path = path.replace(`:${key}`, encodeURIComponent(value));
69
+ let path = '';
70
+ for (let i = 0; i < route.path.length; ) {
71
+ if (route.path[i] === ':' && isParamStart(route.path[i + 1])) {
72
+ let nameEnd = i + 2;
73
+ while (nameEnd < route.path.length && isParamChar(route.path[nameEnd])) {
74
+ nameEnd++;
75
+ }
76
+
77
+ let nextIndex = nameEnd;
78
+ let constraint: string | null = null;
79
+ if (route.path[nameEnd] === '(') {
80
+ const parsedConstraint = readConstraint(route.path, nameEnd);
81
+ if (!parsedConstraint) {
82
+ throw new Error(
83
+ `bQuery router: Invalid constraint syntax in path "${route.path}" for route "${name}".`
84
+ );
85
+ }
86
+ constraint = parsedConstraint.constraint;
87
+ nextIndex = parsedConstraint.endIndex;
88
+ }
89
+
90
+ const key = route.path.slice(i + 1, nameEnd);
91
+ const value = params[key];
92
+ if (value === undefined) {
93
+ throw new Error(`bQuery router: Missing required param "${key}" for route "${name}".`);
94
+ }
95
+ if (constraint && !getRouteConstraintRegex(constraint).test(value)) {
96
+ throw new Error(
97
+ `bQuery router: Param "${key}" with value "${value}" does not satisfy the route constraint "${constraint}" for route "${name}".`
98
+ );
99
+ }
100
+
101
+ path += encodeURIComponent(value);
102
+ i = nextIndex;
103
+ continue;
104
+ }
105
+
106
+ path += route.path[i];
107
+ i++;
67
108
  }
68
109
 
69
110
  return path;
@@ -1,17 +1,12 @@
1
- /**
2
- * Security module providing sanitization, CSP compatibility, and Trusted Types.
3
- *
4
- * @module bquery/security
5
- */
6
-
7
- export { generateNonce, hasCSPDirective } from './csp';
8
- export {
9
- escapeHtml,
10
- sanitizeHtml as sanitize,
11
- sanitizeHtml,
12
- stripTags,
13
- } from './sanitize';
14
- export { trusted } from './trusted-html';
15
- export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
16
- export type { SanitizedHtml, TrustedHtml } from './trusted-html';
17
- export type { SanitizeOptions } from './sanitize';
1
+ /**
2
+ * Security module providing sanitization, CSP compatibility, and Trusted Types.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ export { generateNonce, hasCSPDirective } from './csp';
8
+ export { escapeHtml, sanitizeHtml as sanitize, sanitizeHtml, stripTags } from './sanitize';
9
+ export { trusted } from './trusted-html';
10
+ export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
11
+ export type { SanitizedHtml, TrustedHtml } from './trusted-html';
12
+ export type { SanitizeOptions } from './types';