@timber-js/app 0.1.30 → 0.1.31
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/dist/client/index.js +189 -141
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-status-provider.d.ts +2 -8
- package/dist/client/link-status-provider.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +0 -8
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/pending-navigation-context.d.ts +32 -0
- package/dist/client/pending-navigation-context.d.ts.map +1 -0
- package/dist/client/router.d.ts +12 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +33 -13
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/client/use-navigation-pending.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/browser-entry.ts +26 -19
- package/src/client/link-status-provider.tsx +11 -15
- package/src/client/navigation-context.ts +1 -9
- package/src/client/pending-navigation-context.ts +66 -0
- package/src/client/router.ts +131 -98
- package/src/client/transition-root.tsx +84 -20
- package/src/client/use-navigation-pending.ts +7 -9
package/dist/client/index.js
CHANGED
|
@@ -67,104 +67,55 @@ function useLinkStatus() {
|
|
|
67
67
|
return useContext(LinkStatusContext);
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
-
//#region src/client/navigation-context.ts
|
|
70
|
+
//#region src/client/pending-navigation-context.ts
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* PendingNavigationContext — React context for the in-flight navigation URL.
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* the external store re-renders fired, causing a frame where both
|
|
79
|
-
* old and new active states were visible simultaneously.
|
|
80
|
-
*
|
|
81
|
-
* By wrapping the RSC payload element in NavigationProvider inside
|
|
82
|
-
* renderRoot(), the context value and the element tree are passed to
|
|
83
|
-
* reactRoot.render() in the same call — atomic by construction.
|
|
84
|
-
* All consumers (useParams, usePathname) see the new values in the
|
|
85
|
-
* same render pass as the new tree.
|
|
74
|
+
* Provided by TransitionRoot. The value is the URL being navigated to,
|
|
75
|
+
* or null when idle. Used by:
|
|
76
|
+
* - LinkStatusProvider to show per-link pending spinners
|
|
77
|
+
* - useNavigationPending to return a global pending boolean
|
|
86
78
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
79
|
+
* The pending URL is set as an URGENT update (shows immediately) and
|
|
80
|
+
* cleared inside startTransition (commits atomically with the new tree).
|
|
81
|
+
* This ensures pending state appears instantly on navigation start and
|
|
82
|
+
* disappears in the same React commit as the new params/tree.
|
|
89
83
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* they return null or no-op when the APIs aren't available.
|
|
84
|
+
* Separate from NavigationContext (which holds params + pathname) because
|
|
85
|
+
* the pending URL is managed as React state in TransitionRoot, while
|
|
86
|
+
* params/pathname are set via module-level state read by renderRoot.
|
|
87
|
+
* Both contexts commit together in the same transition.
|
|
95
88
|
*
|
|
96
89
|
* See design/19-client-navigation.md §"NavigationContext"
|
|
97
90
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
var _context;
|
|
104
|
-
function getOrCreateContext() {
|
|
105
|
-
if (_context !== void 0) return _context;
|
|
106
|
-
if (typeof React.createContext === "function") _context = React.createContext(null);
|
|
107
|
-
return _context;
|
|
91
|
+
var _context$1;
|
|
92
|
+
function getOrCreateContext$1() {
|
|
93
|
+
if (_context$1 !== void 0) return _context$1;
|
|
94
|
+
if (typeof React.createContext === "function") _context$1 = React.createContext(null);
|
|
95
|
+
return _context$1;
|
|
108
96
|
}
|
|
109
97
|
/**
|
|
110
|
-
* Read the navigation context.
|
|
111
|
-
* or in the RSC environment
|
|
112
|
-
* Internal — used by
|
|
98
|
+
* Read the pending navigation URL from context.
|
|
99
|
+
* Returns null during SSR (no provider) or in the RSC environment.
|
|
100
|
+
* Internal — used by LinkStatusProvider and useNavigationPending.
|
|
113
101
|
*/
|
|
114
|
-
function
|
|
115
|
-
const ctx = getOrCreateContext();
|
|
102
|
+
function usePendingNavigationUrl() {
|
|
103
|
+
const ctx = getOrCreateContext$1();
|
|
116
104
|
if (!ctx) return null;
|
|
117
105
|
if (typeof React.useContext !== "function") return null;
|
|
118
106
|
return React.useContext(ctx);
|
|
119
107
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Wraps children with NavigationContext.Provider.
|
|
122
|
-
*
|
|
123
|
-
* Used in browser-entry.ts renderRoot to wrap the RSC payload element
|
|
124
|
-
* so that navigation state updates atomically with the tree render.
|
|
125
|
-
*/
|
|
126
|
-
function NavigationProvider({ value, children }) {
|
|
127
|
-
const ctx = getOrCreateContext();
|
|
128
|
-
if (!ctx) return children;
|
|
129
|
-
return createElement(ctx.Provider, { value }, children);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Module-level navigation state. Updated by the router before calling
|
|
133
|
-
* renderRoot(). The renderRoot callback reads this to create the
|
|
134
|
-
* NavigationProvider with the correct values.
|
|
135
|
-
*
|
|
136
|
-
* This is NOT used by hooks directly — hooks read from React context.
|
|
137
|
-
* This exists only as a communication channel between the router
|
|
138
|
-
* (which knows the new nav state) and renderRoot (which wraps the element).
|
|
139
|
-
*/
|
|
140
|
-
var _currentNavState = {
|
|
141
|
-
params: {},
|
|
142
|
-
pathname: "/",
|
|
143
|
-
pendingUrl: null
|
|
144
|
-
};
|
|
145
|
-
function setNavigationState(state) {
|
|
146
|
-
_currentNavState = state;
|
|
147
|
-
}
|
|
148
|
-
function getNavigationState() {
|
|
149
|
-
return _currentNavState;
|
|
150
|
-
}
|
|
151
108
|
//#endregion
|
|
152
109
|
//#region src/client/link-status-provider.tsx
|
|
153
110
|
var NOT_PENDING = { pending: false };
|
|
154
111
|
var IS_PENDING = { pending: true };
|
|
155
112
|
/**
|
|
156
|
-
* Client component that reads the pending URL from
|
|
157
|
-
* provides a scoped LinkStatusContext to children. Renders no extra DOM —
|
|
113
|
+
* Client component that reads the pending URL from PendingNavigationContext
|
|
114
|
+
* and provides a scoped LinkStatusContext to children. Renders no extra DOM —
|
|
158
115
|
* just a context provider around children.
|
|
159
|
-
*
|
|
160
|
-
* Because pendingUrl lives in NavigationContext alongside params and pathname,
|
|
161
|
-
* all three update in the same React commit via renderRoot(). This eliminates
|
|
162
|
-
* the two-commit timing gap that existed when pendingUrl was read via
|
|
163
|
-
* useSyncExternalStore (external module-level state) while params came from
|
|
164
|
-
* NavigationContext (React context).
|
|
165
116
|
*/
|
|
166
117
|
function LinkStatusProvider({ href, children }) {
|
|
167
|
-
const status =
|
|
118
|
+
const status = usePendingNavigationUrl() === href ? IS_PENDING : NOT_PENDING;
|
|
168
119
|
return /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
|
|
169
120
|
value: status,
|
|
170
121
|
children
|
|
@@ -407,6 +358,87 @@ var HistoryStack = class {
|
|
|
407
358
|
}
|
|
408
359
|
};
|
|
409
360
|
//#endregion
|
|
361
|
+
//#region src/client/navigation-context.ts
|
|
362
|
+
/**
|
|
363
|
+
* NavigationContext — React context for navigation state.
|
|
364
|
+
*
|
|
365
|
+
* Holds the current route params and pathname, updated atomically
|
|
366
|
+
* with the RSC tree on each navigation. This replaces the previous
|
|
367
|
+
* useSyncExternalStore approach for useParams() and usePathname(),
|
|
368
|
+
* which suffered from a timing gap: the new tree could commit before
|
|
369
|
+
* the external store re-renders fired, causing a frame where both
|
|
370
|
+
* old and new active states were visible simultaneously.
|
|
371
|
+
*
|
|
372
|
+
* By wrapping the RSC payload element in NavigationProvider inside
|
|
373
|
+
* renderRoot(), the context value and the element tree are passed to
|
|
374
|
+
* reactRoot.render() in the same call — atomic by construction.
|
|
375
|
+
* All consumers (useParams, usePathname) see the new values in the
|
|
376
|
+
* same render pass as the new tree.
|
|
377
|
+
*
|
|
378
|
+
* During SSR, no NavigationProvider is mounted. Hooks fall back to
|
|
379
|
+
* the ALS-backed getSsrData() for per-request isolation.
|
|
380
|
+
*
|
|
381
|
+
* IMPORTANT: createContext and useContext are NOT available in the RSC
|
|
382
|
+
* environment (React Server Components use a stripped-down React).
|
|
383
|
+
* The context is lazily initialized on first access, and all functions
|
|
384
|
+
* that depend on these APIs are safe to call from any environment —
|
|
385
|
+
* they return null or no-op when the APIs aren't available.
|
|
386
|
+
*
|
|
387
|
+
* See design/19-client-navigation.md §"NavigationContext"
|
|
388
|
+
*/
|
|
389
|
+
/**
|
|
390
|
+
* The context is created lazily to avoid calling createContext at module
|
|
391
|
+
* level. In the RSC environment, React.createContext doesn't exist —
|
|
392
|
+
* calling it at import time would crash the server.
|
|
393
|
+
*/
|
|
394
|
+
var _context;
|
|
395
|
+
function getOrCreateContext() {
|
|
396
|
+
if (_context !== void 0) return _context;
|
|
397
|
+
if (typeof React.createContext === "function") _context = React.createContext(null);
|
|
398
|
+
return _context;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Read the navigation context. Returns null during SSR (no provider)
|
|
402
|
+
* or in the RSC environment (no context available).
|
|
403
|
+
* Internal — used by useParams() and usePathname().
|
|
404
|
+
*/
|
|
405
|
+
function useNavigationContext() {
|
|
406
|
+
const ctx = getOrCreateContext();
|
|
407
|
+
if (!ctx) return null;
|
|
408
|
+
if (typeof React.useContext !== "function") return null;
|
|
409
|
+
return React.useContext(ctx);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Wraps children with NavigationContext.Provider.
|
|
413
|
+
*
|
|
414
|
+
* Used in browser-entry.ts renderRoot to wrap the RSC payload element
|
|
415
|
+
* so that navigation state updates atomically with the tree render.
|
|
416
|
+
*/
|
|
417
|
+
function NavigationProvider({ value, children }) {
|
|
418
|
+
const ctx = getOrCreateContext();
|
|
419
|
+
if (!ctx) return children;
|
|
420
|
+
return createElement(ctx.Provider, { value }, children);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Module-level navigation state. Updated by the router before calling
|
|
424
|
+
* renderRoot(). The renderRoot callback reads this to create the
|
|
425
|
+
* NavigationProvider with the correct values.
|
|
426
|
+
*
|
|
427
|
+
* This is NOT used by hooks directly — hooks read from React context.
|
|
428
|
+
* This exists only as a communication channel between the router
|
|
429
|
+
* (which knows the new nav state) and renderRoot (which wraps the element).
|
|
430
|
+
*/
|
|
431
|
+
var _currentNavState = {
|
|
432
|
+
params: {},
|
|
433
|
+
pathname: "/"
|
|
434
|
+
};
|
|
435
|
+
function setNavigationState(state) {
|
|
436
|
+
_currentNavState = state;
|
|
437
|
+
}
|
|
438
|
+
function getNavigationState() {
|
|
439
|
+
return _currentNavState;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
410
442
|
//#region src/client/use-params.ts
|
|
411
443
|
/**
|
|
412
444
|
* Set the current route params in the module-level store.
|
|
@@ -586,21 +618,12 @@ function createRouter(deps) {
|
|
|
586
618
|
let pending = false;
|
|
587
619
|
let pendingUrl = null;
|
|
588
620
|
const pendingListeners = /* @__PURE__ */ new Set();
|
|
589
|
-
/** Last rendered payload — used to re-render at navigation start with pendingUrl set. */
|
|
590
|
-
let lastRenderedPayload = null;
|
|
591
621
|
function setPending(value, url) {
|
|
592
622
|
const newPendingUrl = value && url ? url : null;
|
|
593
623
|
if (pending === value && pendingUrl === newPendingUrl) return;
|
|
594
624
|
pending = value;
|
|
595
625
|
pendingUrl = newPendingUrl;
|
|
596
626
|
for (const listener of pendingListeners) listener(value);
|
|
597
|
-
if (value && lastRenderedPayload !== null) {
|
|
598
|
-
setNavigationState({
|
|
599
|
-
...getNavigationState(),
|
|
600
|
-
pendingUrl: newPendingUrl
|
|
601
|
-
});
|
|
602
|
-
renderPayload(lastRenderedPayload);
|
|
603
|
-
}
|
|
604
627
|
}
|
|
605
628
|
/** Update the segment cache from server-provided segment metadata. */
|
|
606
629
|
function updateSegmentCache(segmentInfo) {
|
|
@@ -610,31 +633,44 @@ function createRouter(deps) {
|
|
|
610
633
|
}
|
|
611
634
|
/** Render a decoded RSC payload into the DOM if a renderer is available. */
|
|
612
635
|
function renderPayload(payload) {
|
|
613
|
-
lastRenderedPayload = payload;
|
|
614
636
|
if (deps.renderRoot) deps.renderRoot(payload);
|
|
615
637
|
}
|
|
616
638
|
/**
|
|
617
|
-
* Update navigation state (params + pathname
|
|
639
|
+
* Update navigation state (params + pathname) for the next render.
|
|
618
640
|
*
|
|
619
641
|
* Sets both the module-level fallback (for tests and SSR) and the
|
|
620
642
|
* navigation context state (read by renderRoot to wrap the element
|
|
621
643
|
* in NavigationProvider). The context update is atomic with the tree
|
|
622
644
|
* render — both are passed to reactRoot.render() in the same call.
|
|
623
|
-
*
|
|
624
|
-
* pendingUrl is included so that LinkStatusProvider (which reads from
|
|
625
|
-
* NavigationContext) sees the pending state change in the same React
|
|
626
|
-
* commit as params/pathname — preventing the gap where the spinner
|
|
627
|
-
* disappears before the active state updates.
|
|
628
645
|
*/
|
|
629
|
-
function updateNavigationState(params, url
|
|
646
|
+
function updateNavigationState(params, url) {
|
|
630
647
|
const resolvedParams = params ?? {};
|
|
631
648
|
setCurrentParams(resolvedParams);
|
|
632
649
|
setNavigationState({
|
|
633
650
|
params: resolvedParams,
|
|
634
|
-
pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
|
|
635
|
-
pendingUrl: navPendingUrl
|
|
651
|
+
pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
|
|
636
652
|
});
|
|
637
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Render a payload via navigateTransition (production) or renderRoot (tests).
|
|
656
|
+
* The perform callback should fetch data, update state, and return the payload.
|
|
657
|
+
* In production, the entire callback runs inside a React transition with
|
|
658
|
+
* useOptimistic for the pending URL. In tests, the payload is rendered directly.
|
|
659
|
+
*/
|
|
660
|
+
async function renderViaTransition(pendingUrl, perform) {
|
|
661
|
+
if (deps.navigateTransition) {
|
|
662
|
+
let headElements = null;
|
|
663
|
+
await deps.navigateTransition(pendingUrl, async (wrapPayload) => {
|
|
664
|
+
const result = await perform();
|
|
665
|
+
headElements = result.headElements;
|
|
666
|
+
return wrapPayload(result.payload);
|
|
667
|
+
});
|
|
668
|
+
return headElements;
|
|
669
|
+
}
|
|
670
|
+
const result = await perform();
|
|
671
|
+
renderPayload(result.payload);
|
|
672
|
+
return result.headElements;
|
|
673
|
+
}
|
|
638
674
|
/** Apply head elements (title, meta tags) to the DOM if available. */
|
|
639
675
|
function applyHead(elements) {
|
|
640
676
|
if (elements && deps.applyHead) deps.applyHead(elements);
|
|
@@ -644,6 +680,40 @@ function createRouter(deps) {
|
|
|
644
680
|
if (deps.afterPaint) deps.afterPaint(callback);
|
|
645
681
|
else callback();
|
|
646
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Core navigation logic shared between the transition and fallback paths.
|
|
685
|
+
* Fetches the RSC payload, updates all state, and returns the result.
|
|
686
|
+
*/
|
|
687
|
+
async function performNavigationFetch(url, options) {
|
|
688
|
+
const prefetched = prefetchCache.consume(url);
|
|
689
|
+
let result = prefetched ? {
|
|
690
|
+
payload: prefetched.payload,
|
|
691
|
+
headElements: prefetched.headElements,
|
|
692
|
+
segmentInfo: prefetched.segmentInfo ?? null,
|
|
693
|
+
params: prefetched.params ?? null
|
|
694
|
+
} : void 0;
|
|
695
|
+
if (result === void 0) {
|
|
696
|
+
const stateTree = segmentCache.serializeStateTree();
|
|
697
|
+
const rawCurrentUrl = deps.getCurrentUrl();
|
|
698
|
+
result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname);
|
|
699
|
+
}
|
|
700
|
+
if (options.replace) deps.replaceState({
|
|
701
|
+
timber: true,
|
|
702
|
+
scrollY: 0
|
|
703
|
+
}, "", url);
|
|
704
|
+
else deps.pushState({
|
|
705
|
+
timber: true,
|
|
706
|
+
scrollY: 0
|
|
707
|
+
}, "", url);
|
|
708
|
+
historyStack.push(url, {
|
|
709
|
+
payload: result.payload,
|
|
710
|
+
headElements: result.headElements,
|
|
711
|
+
params: result.params
|
|
712
|
+
});
|
|
713
|
+
updateSegmentCache(result.segmentInfo);
|
|
714
|
+
updateNavigationState(result.params, url);
|
|
715
|
+
return result;
|
|
716
|
+
}
|
|
647
717
|
async function navigate(url, options = {}) {
|
|
648
718
|
const scroll = options.scroll !== false;
|
|
649
719
|
const replace = options.replace === true;
|
|
@@ -654,29 +724,7 @@ function createRouter(deps) {
|
|
|
654
724
|
}, "", deps.getCurrentUrl());
|
|
655
725
|
setPending(true, url);
|
|
656
726
|
try {
|
|
657
|
-
|
|
658
|
-
if (result === void 0) {
|
|
659
|
-
const stateTree = segmentCache.serializeStateTree();
|
|
660
|
-
const rawCurrentUrl = deps.getCurrentUrl();
|
|
661
|
-
result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname);
|
|
662
|
-
}
|
|
663
|
-
if (replace) deps.replaceState({
|
|
664
|
-
timber: true,
|
|
665
|
-
scrollY: 0
|
|
666
|
-
}, "", url);
|
|
667
|
-
else deps.pushState({
|
|
668
|
-
timber: true,
|
|
669
|
-
scrollY: 0
|
|
670
|
-
}, "", url);
|
|
671
|
-
historyStack.push(url, {
|
|
672
|
-
payload: result.payload,
|
|
673
|
-
headElements: result.headElements,
|
|
674
|
-
params: result.params
|
|
675
|
-
});
|
|
676
|
-
updateSegmentCache(result.segmentInfo);
|
|
677
|
-
updateNavigationState(result.params, url);
|
|
678
|
-
renderPayload(result.payload);
|
|
679
|
-
applyHead(result.headElements);
|
|
727
|
+
applyHead(await renderViaTransition(url, () => performNavigationFetch(url, { replace })));
|
|
680
728
|
window.dispatchEvent(new Event("timber:navigation-end"));
|
|
681
729
|
afterPaint(() => {
|
|
682
730
|
if (scroll) deps.scrollTo(0, 0);
|
|
@@ -699,16 +747,17 @@ function createRouter(deps) {
|
|
|
699
747
|
const currentUrl = deps.getCurrentUrl();
|
|
700
748
|
setPending(true, currentUrl);
|
|
701
749
|
try {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
750
|
+
applyHead(await renderViaTransition(currentUrl, async () => {
|
|
751
|
+
const result = await fetchRscPayload(currentUrl, deps);
|
|
752
|
+
historyStack.push(currentUrl, {
|
|
753
|
+
payload: result.payload,
|
|
754
|
+
headElements: result.headElements,
|
|
755
|
+
params: result.params
|
|
756
|
+
});
|
|
757
|
+
updateSegmentCache(result.segmentInfo);
|
|
758
|
+
updateNavigationState(result.params, currentUrl);
|
|
759
|
+
return result;
|
|
760
|
+
}));
|
|
712
761
|
} finally {
|
|
713
762
|
setPending(false);
|
|
714
763
|
}
|
|
@@ -726,16 +775,17 @@ function createRouter(deps) {
|
|
|
726
775
|
} else {
|
|
727
776
|
setPending(true, url);
|
|
728
777
|
try {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
778
|
+
applyHead(await renderViaTransition(url, async () => {
|
|
779
|
+
const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree());
|
|
780
|
+
updateSegmentCache(result.segmentInfo);
|
|
781
|
+
updateNavigationState(result.params, url);
|
|
782
|
+
historyStack.push(url, {
|
|
783
|
+
payload: result.payload,
|
|
784
|
+
headElements: result.headElements,
|
|
785
|
+
params: result.params
|
|
786
|
+
});
|
|
787
|
+
return result;
|
|
788
|
+
}));
|
|
739
789
|
afterPaint(() => {
|
|
740
790
|
deps.scrollTo(0, scrollY);
|
|
741
791
|
window.dispatchEvent(new Event("timber:scroll-restored"));
|
|
@@ -809,9 +859,7 @@ function createRouter(deps) {
|
|
|
809
859
|
* ```
|
|
810
860
|
*/
|
|
811
861
|
function useNavigationPending() {
|
|
812
|
-
|
|
813
|
-
if (!navState) return false;
|
|
814
|
-
return navState.pendingUrl !== null;
|
|
862
|
+
return usePendingNavigationUrl() !== null;
|
|
815
863
|
}
|
|
816
864
|
//#endregion
|
|
817
865
|
//#region src/client/router-ref.ts
|