@buzola/router 0.0.17 → 0.0.19

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 (59) hide show
  1. package/dist/define/create-page.js +1 -1
  2. package/dist/engine/loader-cache.d.ts +1 -1
  3. package/dist/engine/loader-cache.js +1 -1
  4. package/dist/engine/loader-state.d.ts +5 -5
  5. package/dist/engine/loader-state.d.ts.map +1 -1
  6. package/dist/engine/loader-state.js +8 -8
  7. package/dist/engine/loader-state.js.map +1 -1
  8. package/dist/engine/matcher.js +4 -4
  9. package/dist/engine/matcher.js.map +1 -1
  10. package/dist/engine/navigation-adapter.js +6 -6
  11. package/dist/engine/navigation-adapter.js.map +1 -1
  12. package/dist/engine/route-tree.js +2 -2
  13. package/dist/engine/route-tree.js.map +1 -1
  14. package/dist/engine/router.d.ts +31 -5
  15. package/dist/engine/router.d.ts.map +1 -1
  16. package/dist/engine/router.js +47 -10
  17. package/dist/engine/router.js.map +1 -1
  18. package/dist/engine/types.d.ts +28 -8
  19. package/dist/engine/types.d.ts.map +1 -1
  20. package/dist/engine/utils.d.ts +8 -3
  21. package/dist/engine/utils.d.ts.map +1 -1
  22. package/dist/engine/utils.js +16 -3
  23. package/dist/engine/utils.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/react/hooks.d.ts +2 -2
  28. package/dist/react/hooks.js +1 -1
  29. package/dist/react/link.d.ts +4 -4
  30. package/dist/react/link.js +1 -1
  31. package/dist/react/outlet.js +2 -2
  32. package/dist/react/outlet.js.map +1 -1
  33. package/dist/react/provider.d.ts +4 -1
  34. package/dist/react/provider.d.ts.map +1 -1
  35. package/dist/react/provider.js +6 -0
  36. package/dist/react/provider.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/__tests__/create-page.test.ts +1 -1
  39. package/src/__tests__/loader-cache.test.ts +1 -1
  40. package/src/__tests__/loader-state.test.ts +12 -12
  41. package/src/__tests__/navigation-adapter.test.ts +2 -2
  42. package/src/__tests__/react.test.tsx +70 -4
  43. package/src/__tests__/router.test.ts +223 -4
  44. package/src/__tests__/setup-dom.ts +1 -1
  45. package/src/__tests__/type-safety.test.tsx +22 -22
  46. package/src/define/create-page.tsx +1 -1
  47. package/src/engine/loader-cache.ts +1 -1
  48. package/src/engine/loader-state.ts +9 -9
  49. package/src/engine/matcher.ts +4 -4
  50. package/src/engine/navigation-adapter.ts +6 -6
  51. package/src/engine/route-tree.ts +3 -3
  52. package/src/engine/router.ts +59 -11
  53. package/src/engine/types.ts +30 -8
  54. package/src/engine/utils.ts +16 -3
  55. package/src/index.ts +2 -0
  56. package/src/react/hooks.ts +2 -2
  57. package/src/react/link.tsx +5 -5
  58. package/src/react/outlet.tsx +2 -2
  59. package/src/react/provider.tsx +10 -2
@@ -1,7 +1,7 @@
1
1
  import { LoaderCache } from './loader-cache.js'
2
2
  import { matchRoutes } from './matcher.js'
3
- import type { BlockerFn, BuzolaNavigateEvent, NavigateOptions, NavigationAdapter, RouterState, RouteTree } from './types.js'
4
- import { extractParamNames, isSamePath, parseParamSegment } from './utils.js'
3
+ import type { BlockerFn, BuzolaNavigateEvent, NavigateOptions, NavigationAdapter, RouterState, RouteTree, ShouldInterceptFn } from './types.js'
4
+ import { extractParamNames, isFragmentNavigation, isSamePath, parseParamSegment } from './utils.js'
5
5
 
6
6
  /**
7
7
  * Thrown when a route guard prevents navigation.
@@ -45,6 +45,16 @@ export interface RouterOptions {
45
45
  * Set to 0 to always re-fetch on URL change (default).
46
46
  */
47
47
  loaderCacheSize?: number
48
+ /**
49
+ * Decides, per navigation, whether the router handles it in the current document.
50
+ * Return `false` to have the browser load the destination as a new document instead,
51
+ * e.g. to pick up a newly deployed build on the next in-app navigation.
52
+ *
53
+ * Offered every push, replace and traverse the router would otherwise intercept, except
54
+ * fragment navigations. Defaults to intercepting everything. See the README for how
55
+ * traversals and blockers are handled.
56
+ */
57
+ shouldIntercept?: ShouldInterceptFn
48
58
  }
49
59
 
50
60
  /**
@@ -58,6 +68,7 @@ export class Router {
58
68
  private readonly _basePath: string
59
69
  private readonly persistentParamsFn?: () => Record<string, string>
60
70
  private readonly pageRegistry: Record<string, string>
71
+ private readonly shouldIntercept: ShouldInterceptFn | undefined
61
72
  private subscribers = new Set<RouterSubscriber>()
62
73
  private invalidationListeners = new Set<() => void>()
63
74
  private state: RouterState
@@ -77,6 +88,7 @@ export class Router {
77
88
  this._basePath = options.basePath ? options.basePath.replace(/\/$/, '') : ''
78
89
  this.persistentParamsFn = options.persistentParams
79
90
  this.pageRegistry = options.pageRegistry ?? {}
91
+ this.shouldIntercept = options.shouldIntercept
80
92
  this._loaderCache = options.loaderCacheSize ? new LoaderCache(options.loaderCacheSize) : undefined
81
93
 
82
94
  // Initialize state from current URL
@@ -134,7 +146,7 @@ export class Router {
134
146
  */
135
147
  start(): () => void {
136
148
  const handler = (event: BuzolaNavigateEvent) => {
137
- // Consume the pending options before any early return — they belong to the
149
+ // Consume the pending options before any early return: they belong to the
138
150
  // navigation that armed them and must not leak into the next one.
139
151
  const scrollBehavior = this.pendingScrollBehavior
140
152
  const explicitFocusReset = this.pendingFocusReset
@@ -163,9 +175,24 @@ export class Router {
163
175
  return
164
176
  }
165
177
 
178
+ // The browser decides a fragment navigation against the committed entry, which runs ahead of the
179
+ // page on screen while a navigation is pending (e.g. a blocker dialog is open).
180
+ if (this.shouldIntercept && !isFragmentNavigation(this.adapter.getCurrentURL(), url)) {
181
+ const intercept = this.shouldIntercept({
182
+ destination: url,
183
+ from: this.state.location,
184
+ navigationType: event.navigationType,
185
+ userInitiated: event.userInitiated,
186
+ })
187
+ if (!intercept) {
188
+ this.handleDeclined(event)
189
+ return
190
+ }
191
+ }
192
+
166
193
  const currentNavId = ++this.navigationId
167
194
  const useViewTransition = event.viewTransition || this.viewTransitions
168
- // Query-only navigations stay on the same page — keep focus where the user left it
195
+ // Query-only navigations stay on the same page, so keep focus where the user left it
169
196
  // (search boxes, filters, pagination). Path changes keep the browser's focus reset,
170
197
  // and so do fragment changes, which must move focus to the targeted section.
171
198
  const focusReset = explicitFocusReset
@@ -191,7 +218,7 @@ export class Router {
191
218
  pendingLocation: url,
192
219
  })
193
220
 
194
- // A newer navigation has started — abandon this one
221
+ // A newer navigation has started, so abandon this one
195
222
  if (this.navigationId !== currentNavId) return
196
223
 
197
224
  // Perform the DOM update
@@ -204,7 +231,7 @@ export class Router {
204
231
  pendingLocation: undefined,
205
232
  navigationState: this.adapter.getState(),
206
233
  })
207
- // Going to the URL we are already on is still a navigation — a browser
234
+ // Going to the URL we are already on is still a navigation: a browser
208
235
  // re-requests the document for it. Loader data is keyed by href, so it
209
236
  // would silently keep serving stale data without an explicit invalidation.
210
237
  if (previousHref === url.href) {
@@ -240,7 +267,28 @@ export class Router {
240
267
  */
241
268
  private handleReload(event: BuzolaNavigateEvent): void {
242
269
  if (this.blockers.size === 0) return
270
+ this.reloadAfterBlockers(event)
271
+ }
243
272
 
273
+ /**
274
+ * `shouldIntercept` declined this navigation, so the destination must load as a new document.
275
+ *
276
+ * A push or replace is simply not intercepted, and the browser performs the document navigation itself.
277
+ * That does not work for a traversal, which the browser completes within the document, nor while
278
+ * blockers are registered, since they are async. Those are intercepted, which commits the destination
279
+ * entry, and that entry is reloaded once the blockers allow it.
280
+ */
281
+ private handleDeclined(event: BuzolaNavigateEvent): void {
282
+ if (event.navigationType !== 'traverse' && this.blockers.size === 0) return
283
+ this.reloadAfterBlockers(event)
284
+ }
285
+
286
+ /**
287
+ * Hold the navigation while the blockers run and, if they allow it, reload the current entry,
288
+ * which is already the navigation's destination, because an intercepted navigation commits before
289
+ * its handler runs.
290
+ */
291
+ private reloadAfterBlockers(event: BuzolaNavigateEvent): void {
244
292
  const currentNavId = ++this.navigationId
245
293
  event.intercept({
246
294
  scroll: 'manual',
@@ -261,7 +309,7 @@ export class Router {
261
309
  }
262
310
 
263
311
  /**
264
- * Hand one navigation to the browser instead of intercepting it, for a URL the SERVER owns —
312
+ * Hand one navigation to the browser instead of intercepting it, for a URL the SERVER owns:
265
313
  * an auth handoff, a file download, a server-rendered page that happens to share this origin.
266
314
  *
267
315
  * The Navigation API offers no way for a caller to mark a navigation un-interceptable:
@@ -271,7 +319,7 @@ export class Router {
271
319
  *
272
320
  * The release is recorded BEFORE the navigation is requested and is keyed to the resolved URL, so
273
321
  * it cannot race the event or release some other navigation. Unlike `navigate`, the URL is passed
274
- * to the browser as given — no base path is prepended, because the target is outside the app.
322
+ * to the browser as given: no base path is prepended, because the target is outside the app.
275
323
  */
276
324
  leaveApp(url: string): void {
277
325
  this.releasedURL = new URL(url, this.adapter.getCurrentURL()).href
@@ -280,7 +328,7 @@ export class Router {
280
328
 
281
329
  /**
282
330
  * Navigate programmatically.
283
- * The `to` path should not include the base path — it is prepended automatically.
331
+ * The `to` path should not include the base path. It is prepended automatically.
284
332
  */
285
333
  navigate(to: string, options?: NavigateOptions): void {
286
334
  if (options?.resetScroll === false) {
@@ -361,7 +409,7 @@ export class Router {
361
409
  missing.push(paramName)
362
410
  } else {
363
411
  if (isCatchAll) {
364
- // Catch-all values may contain slashes — insert raw
412
+ // Catch-all values may contain slashes: insert raw
365
413
  result.push(value)
366
414
  } else {
367
415
  result.push(encodeURIComponent(value))
@@ -485,7 +533,7 @@ export class Router {
485
533
  }
486
534
 
487
535
  /**
488
- * Dispose the router — clear all subscribers and reset state.
536
+ * Dispose the router: clear all subscribers and reset state.
489
537
  */
490
538
  dispose(): void {
491
539
  this.stopFn?.()
@@ -29,7 +29,7 @@ export type RegisteredPage = keyof BuzolaPageMap & string
29
29
  export type PageParams<P extends keyof BuzolaPageMap> = BuzolaPageMap[P]
30
30
 
31
31
  /**
32
- * Effective params for a page — persistent params become optional.
32
+ * Effective params for a page: persistent params become optional.
33
33
  * When BuzolaPersistentParams is empty (default), collapses to BuzolaPageMap[P].
34
34
  */
35
35
  export type EffectivePageParams<P extends keyof BuzolaPageMap> =
@@ -50,7 +50,7 @@ export interface StandardSchema<T = unknown> {
50
50
  }
51
51
  }
52
52
 
53
- /** Route component — either a regular component or a lazy-loaded one. */
53
+ /** Route component: either a regular component or a lazy-loaded one. */
54
54
  export type RouteComponent = ComponentType<Record<string, never>> | LazyExoticComponent<ComponentType<Record<string, never>>>
55
55
 
56
56
  /** Configuration for a single route. */
@@ -63,7 +63,7 @@ export interface RouteConfig {
63
63
  component?: RouteComponent
64
64
  /** Schema for validating search params (Standard Schema compatible). */
65
65
  searchSchema?: StandardSchema
66
- /** Preload function — triggers lazy component import ahead of navigation. */
66
+ /** Preload function: triggers lazy component import ahead of navigation. */
67
67
  preload?: () => void
68
68
  /** Child routes. */
69
69
  children?: RouteConfig[]
@@ -71,7 +71,7 @@ export interface RouteConfig {
71
71
  isLayout?: boolean
72
72
  /** Whether this is an index route (renders at parent's exact path). */
73
73
  isIndex?: boolean
74
- /** Whether this is a pathless group (e.g., (auth) — no URL segment). */
74
+ /** Whether this is a pathless group (e.g., (auth), with no URL segment). */
75
75
  isPathlessGroup?: boolean
76
76
  }
77
77
 
@@ -101,7 +101,7 @@ export interface RouteNode {
101
101
  component?: RouteComponent
102
102
  /** Search params schema. */
103
103
  searchSchema?: StandardSchema
104
- /** Preload function — triggers lazy component import ahead of navigation. */
104
+ /** Preload function: triggers lazy component import ahead of navigation. */
105
105
  preload?: () => void
106
106
  /** Whether this is a layout node (renders children via Outlet). */
107
107
  isLayout: boolean
@@ -113,7 +113,7 @@ export interface RouteNode {
113
113
 
114
114
  /** A node in the segment trie used for URL matching. */
115
115
  export interface TrieNode {
116
- /** Static children — exact segment match via Map lookup (O(1)). */
116
+ /** Static children: exact segment match via Map lookup (O(1)). */
117
117
  staticChildren: Map<string, TrieNode>
118
118
  /**
119
119
  * Dynamic child: matches any single segment. It carries no param name, because routes sharing this
@@ -174,8 +174,8 @@ export interface NavigateOptions {
174
174
  resetScroll?: boolean
175
175
  /**
176
176
  * Whether the browser moves focus after navigation.
177
- * Defaults to `manual` when only the search string changes — so search boxes, filters and
178
- * pagination controls keep the caret — and to `after-transition` when the path or the hash
177
+ * Defaults to `manual` when only the search string changes (so search boxes, filters and
178
+ * pagination controls keep the caret), and to `after-transition` when the path or the hash
179
179
  * changes, so fragment links move focus to the section they target.
180
180
  */
181
181
  focusReset?: 'after-transition' | 'manual'
@@ -188,6 +188,28 @@ export interface NavigateOptions {
188
188
  */
189
189
  export type BlockerFn = () => Promise<boolean>
190
190
 
191
+ /** A navigation the router is about to intercept, as offered to `shouldIntercept`. */
192
+ export interface InterceptInfo {
193
+ /** Where the navigation goes. */
194
+ destination: URL
195
+ /**
196
+ * The location the router currently shows. While a navigation is pending, this can differ from
197
+ * `navigation.currentEntry`, which has already moved to that navigation's destination.
198
+ */
199
+ from: URL
200
+ /** How the navigation moves through history. Reloads always go to the browser and are never offered. */
201
+ navigationType: 'push' | 'replace' | 'traverse'
202
+ /** Whether the user initiated the navigation (e.g., clicking a link). */
203
+ userInitiated: boolean
204
+ }
205
+
206
+ /**
207
+ * Decides whether the router handles a navigation in the current document.
208
+ * Return `false` to have the browser load the destination as a new document instead.
209
+ * Called synchronously from the `navigate` event, so it cannot await anything.
210
+ */
211
+ export type ShouldInterceptFn = (info: InterceptInfo) => boolean
212
+
191
213
  /** Navigate event from the Navigation API (simplified). */
192
214
  export interface BuzolaNavigateEvent {
193
215
  /** The destination URL. */
@@ -24,15 +24,28 @@ export function extractParamNames(pattern: string): string[] {
24
24
  }
25
25
 
26
26
  /**
27
- * Whether two URLs address the same page — i.e. only search/hash differ.
27
+ * Whether two URLs address the same page, i.e. only search/hash differ.
28
28
  * Same-path navigations (filters, sorting, pagination, search) keep the page
29
29
  * on screen and keep its loader data as stale; path changes do not.
30
30
  *
31
- * Note this ignores the hash. Callers that care about fragment changes — focus
32
- * handling, for one — have to compare `hash` themselves.
31
+ * Note this ignores the hash. Callers that care about fragment changes (focus
32
+ * handling, for one) have to compare `hash` themselves.
33
33
  */
34
34
  export function isSamePath(a: string | URL, b: string | URL): boolean {
35
35
  const urlA = typeof a === 'string' ? new URL(a) : a
36
36
  const urlB = typeof b === 'string' ? new URL(b) : b
37
37
  return urlA.origin === urlB.origin && urlA.pathname === urlB.pathname
38
38
  }
39
+
40
+ /**
41
+ * Whether going from `from` to `to` is a fragment navigation: the URLs differ at most in the hash and `to`
42
+ * has one. Browsers always perform those within the current document, whether intercepted or not.
43
+ */
44
+ export function isFragmentNavigation(from: URL, to: URL): boolean {
45
+ if (to.hash === '') return false
46
+ const fromWithoutHash = new URL(from)
47
+ fromWithoutHash.hash = ''
48
+ const toWithoutHash = new URL(to)
49
+ toWithoutHash.hash = ''
50
+ return fromWithoutHash.href === toWithoutHash.href
51
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export type {
19
19
  BuzolaPageMap,
20
20
  BuzolaPersistentParams,
21
21
  EffectivePageParams,
22
+ InterceptInfo,
22
23
  NavigateOptions,
23
24
  NavigationAdapter,
24
25
  PageParams,
@@ -29,6 +30,7 @@ export type {
29
30
  RouteNode,
30
31
  RouterState,
31
32
  RouteTree,
33
+ ShouldInterceptFn,
32
34
  StandardSchema,
33
35
  } from './engine/types.js'
34
36
 
@@ -29,7 +29,7 @@ function useRouteContext() {
29
29
  /**
30
30
  * Type-safe navigation function.
31
31
  * Uses page IDs instead of URL patterns.
32
- * Persistent params are optional — resolved from current URL or fallback.
32
+ * Persistent params are optional: resolved from current URL or fallback.
33
33
  */
34
34
  type NavigateFn = <P extends RegisteredPage>(
35
35
  to: P,
@@ -41,7 +41,7 @@ type NavigateFn = <P extends RegisteredPage>(
41
41
 
42
42
  /**
43
43
  * Hook to get a type-safe navigate function.
44
- * Navigates using page IDs — the router looks up the route pattern from the page registry.
44
+ * Navigates using page IDs: the router looks up the route pattern from the page registry.
45
45
  */
46
46
  export function useNavigate(): NavigateFn {
47
47
  const router = useRouter()
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'
2
2
  import type { EffectivePageParams, NavigateOptions, RegisteredPage } from '../engine/types.js'
3
3
  import { useRouter, useRouterState } from './hooks.js'
4
4
 
5
- // ─── Link props type — always type-safe via page ID ──────────────────────────
5
+ // ─── Link props type: always type-safe via page ID ───────────────────────────
6
6
 
7
7
  type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {
8
8
  /** Replace the current history entry instead of pushing. */
@@ -29,9 +29,9 @@ type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
29
29
  asChild?: boolean
30
30
  /**
31
31
  * Prefetch strategy for lazy-loaded route code.
32
- * - `'intent'` — preload on mouseEnter/focus (default)
33
- * - `'render'` — preload as soon as the Link renders
34
- * - `'none'` — disable prefetching
32
+ * - `'intent'`: preload on mouseEnter/focus (default)
33
+ * - `'render'`: preload as soon as the Link renders
34
+ * - `'none'`: disable prefetching
35
35
  */
36
36
  prefetch?: 'intent' | 'render' | 'none'
37
37
  }
@@ -44,7 +44,7 @@ export type LinkProps<P extends RegisteredPage = RegisteredPage> = [keyof Effect
44
44
 
45
45
  /**
46
46
  * Type-safe link component.
47
- * Uses page IDs for navigation — the router looks up the route pattern from the page registry.
47
+ * Uses page IDs for navigation: the router looks up the route pattern from the page registry.
48
48
  * For simple navigations, renders a native <a> and lets the Navigation API handle it.
49
49
  * Uses programmatic navigation when state, replace, resetScroll, or viewTransition is needed.
50
50
  *
@@ -12,7 +12,7 @@ function DefaultNotFound(): React.ReactElement {
12
12
  },
13
13
  isDev
14
14
  ? [
15
- React.createElement('h1', { key: 'h' }, '404 — Page Not Found'),
15
+ React.createElement('h1', { key: 'h' }, '404: Page Not Found'),
16
16
  React.createElement('p', { key: 'p1' }, `No route matches "${typeof window !== 'undefined' ? window.location.pathname : ''}".`),
17
17
  React.createElement('p', {
18
18
  key: 'p2',
@@ -74,7 +74,7 @@ export function Outlet({ fallback, errorFallback, notFound }: OutletProps): Reac
74
74
  }, [state, depth, effectiveDepth, matches, match, routeContext.params])
75
75
 
76
76
  if (!match) {
77
- // At root depth with no matches — show 404
77
+ // At root depth with no matches: show 404
78
78
  if (depth === 0 && matches.length === 0) {
79
79
  if (notFound === null) return null
80
80
  return (notFound ?? React.createElement(DefaultNotFound, null)) as React.ReactElement
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react'
2
2
  import { createBrowserNavigationAdapter } from '../engine/navigation-adapter.js'
3
3
  import { Router } from '../engine/router.js'
4
- import type { RouteMatch, RouteTree } from '../engine/types.js'
4
+ import type { RouteMatch, RouteTree, ShouldInterceptFn } from '../engine/types.js'
5
5
  import { RouteContext, type RouteContextValue, RouterContext } from './context.js'
6
6
  import { Outlet } from './outlet.js'
7
7
 
@@ -25,12 +25,14 @@ export type BuzolaProviderProps =
25
25
  middleware?: MiddlewareComponent
26
26
  }
27
27
  & (
28
- | { router: Router; routes?: never; persistentParams?: never; pageRegistry?: never }
28
+ | { router: Router; routes?: never; persistentParams?: never; pageRegistry?: never; shouldIntercept?: never }
29
29
  | {
30
30
  router?: never
31
31
  routes: RouteTree
32
32
  persistentParams?: () => Record<string, string>
33
33
  pageRegistry?: Record<string, string>
34
+ /** Decides per navigation whether the router handles it; see `RouterOptions.shouldIntercept`. */
35
+ shouldIntercept?: ShouldInterceptFn
34
36
  }
35
37
  )
36
38
 
@@ -39,6 +41,11 @@ export type BuzolaProviderProps =
39
41
  * Subscribes to router state and provides context to the React tree.
40
42
  */
41
43
  export function BuzolaProvider({ routes, children, middleware: Middleware, ...props }: BuzolaProviderProps): React.ReactElement {
44
+ // The router is created once, so it reads the latest prop through a ref rather than the first render's closure.
45
+ const shouldInterceptRef = useRef(props.shouldIntercept)
46
+ useLayoutEffect(() => {
47
+ shouldInterceptRef.current = props.shouldIntercept
48
+ })
42
49
  const routerRef = useRef<Router | undefined>(props.router)
43
50
  if (!routerRef.current) {
44
51
  routerRef.current = new Router({
@@ -46,6 +53,7 @@ export function BuzolaProvider({ routes, children, middleware: Middleware, ...pr
46
53
  adapter: createBrowserNavigationAdapter(),
47
54
  persistentParams: props.persistentParams,
48
55
  pageRegistry: props.pageRegistry,
56
+ shouldIntercept: info => shouldInterceptRef.current?.(info) ?? true,
49
57
  })
50
58
  }
51
59
  const router = routerRef.current