@timber-js/app 0.2.0-alpha.53 → 0.2.0-alpha.55
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/LICENSE +8 -0
- package/dist/client/index.js +201 -162
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +78 -0
- package/dist/client/link-pending-store.d.ts.map +1 -0
- package/dist/client/link.d.ts +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/package.json +6 -7
- package/src/cli.ts +0 -0
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +59 -3
- package/src/client/navigation-context.ts +4 -3
- package/src/client/transition-root.tsx +13 -1
- package/src/server/html-injectors.ts +16 -0
- package/src/server/node-stream-transforms.ts +18 -13
- package/src/server/rsc-entry/ssr-renderer.ts +8 -3
- package/dist/client/link-status-provider.d.ts +0 -11
- package/dist/client/link-status-provider.d.ts.map +0 -1
- package/src/client/link-status-provider.tsx +0 -30
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
DONTFUCKINGUSE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Saewitz
|
|
4
|
+
|
|
5
|
+
This software may not be used, copied, modified, merged, published,
|
|
6
|
+
distributed, sublicensed, or sold by anyone other than the copyright holder.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/dist/client/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-que
|
|
|
4
4
|
import { n as useSegmentContext, r as mergePreservedSearchParams, t as SegmentProvider } from "../_chunks/segment-context-Bmugn-ao.js";
|
|
5
5
|
import { a as _setCachedSearch, c as cachedSearch, d as globalRouter, i as setSsrData, l as cachedSearchParams, n as clearSsrData, o as _setCurrentParams, r as getSsrData, s as _setGlobalRouter, t as TimberErrorBoundary, u as currentParams } from "../_chunks/error-boundary-B9vT_YK_.js";
|
|
6
6
|
import { t as _registerUseCookieModule } from "../_chunks/define-cookie-k9btcEfI.js";
|
|
7
|
-
import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useSyncExternalStore, useTransition } from "react";
|
|
7
|
+
import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useEffect, useRef, useState, useSyncExternalStore, useTransition } from "react";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
9
|
//#region src/client/use-link-status.ts
|
|
10
10
|
/**
|
|
@@ -41,165 +41,6 @@ function useLinkStatus() {
|
|
|
41
41
|
return useContext(LinkStatusContext);
|
|
42
42
|
}
|
|
43
43
|
//#endregion
|
|
44
|
-
//#region src/client/navigation-context.ts
|
|
45
|
-
/**
|
|
46
|
-
* NavigationContext — React context for navigation state.
|
|
47
|
-
*
|
|
48
|
-
* Holds the current route params and pathname, updated atomically
|
|
49
|
-
* with the RSC tree on each navigation. This replaces the previous
|
|
50
|
-
* useSyncExternalStore approach for useSegmentParams() and usePathname(),
|
|
51
|
-
* which suffered from a timing gap: the new tree could commit before
|
|
52
|
-
* the external store re-renders fired, causing a frame where both
|
|
53
|
-
* old and new active states were visible simultaneously.
|
|
54
|
-
*
|
|
55
|
-
* By wrapping the RSC payload element in NavigationProvider inside
|
|
56
|
-
* renderRoot(), the context value and the element tree are passed to
|
|
57
|
-
* reactRoot.render() in the same call — atomic by construction.
|
|
58
|
-
* All consumers (useParams, usePathname) see the new values in the
|
|
59
|
-
* same render pass as the new tree.
|
|
60
|
-
*
|
|
61
|
-
* During SSR, no NavigationProvider is mounted. Hooks fall back to
|
|
62
|
-
* the ALS-backed getSsrData() for per-request isolation.
|
|
63
|
-
*
|
|
64
|
-
* IMPORTANT: createContext and useContext are NOT available in the RSC
|
|
65
|
-
* environment (React Server Components use a stripped-down React).
|
|
66
|
-
* The context is lazily initialized on first access, and all functions
|
|
67
|
-
* that depend on these APIs are safe to call from any environment —
|
|
68
|
-
* they return null or no-op when the APIs aren't available.
|
|
69
|
-
*
|
|
70
|
-
* SINGLETON GUARANTEE: All shared mutable state uses globalThis via
|
|
71
|
-
* Symbol.for keys. The RSC client bundler can duplicate this module
|
|
72
|
-
* across chunks (browser-entry graph + client-reference graph). With
|
|
73
|
-
* ESM output, each chunk gets its own module scope — module-level
|
|
74
|
-
* variables would create separate singleton instances per chunk.
|
|
75
|
-
* globalThis guarantees a single instance regardless of duplication.
|
|
76
|
-
*
|
|
77
|
-
* This workaround will be removed when Rolldown ships `format: 'app'`
|
|
78
|
-
* (module registry format that deduplicates like webpack/Turbopack).
|
|
79
|
-
* See design/27-chunking-strategy.md.
|
|
80
|
-
*
|
|
81
|
-
* See design/19-client-navigation.md §"NavigationContext"
|
|
82
|
-
*/
|
|
83
|
-
/**
|
|
84
|
-
* The context is created lazily to avoid calling createContext at module
|
|
85
|
-
* level. In the RSC environment, React.createContext doesn't exist —
|
|
86
|
-
* calling it at import time would crash the server.
|
|
87
|
-
*
|
|
88
|
-
* Context instances are stored on globalThis (NOT in module-level
|
|
89
|
-
* variables) because the ESM bundler can duplicate this module across
|
|
90
|
-
* chunks. Module-level variables would create separate instances per
|
|
91
|
-
* chunk — the provider in TransitionRoot (index chunk) would use
|
|
92
|
-
* context A while the consumer in LinkStatusProvider (shared chunk)
|
|
93
|
-
* reads from context B. globalThis guarantees a single instance.
|
|
94
|
-
*
|
|
95
|
-
* See design/27-chunking-strategy.md §"Singleton Safety"
|
|
96
|
-
*/
|
|
97
|
-
var NAV_CTX_KEY = Symbol.for("__timber_nav_ctx");
|
|
98
|
-
var PENDING_CTX_KEY = Symbol.for("__timber_pending_nav_ctx");
|
|
99
|
-
function getOrCreateContext() {
|
|
100
|
-
const existing = globalThis[NAV_CTX_KEY];
|
|
101
|
-
if (existing !== void 0) return existing;
|
|
102
|
-
if (typeof React.createContext === "function") {
|
|
103
|
-
const ctx = React.createContext(null);
|
|
104
|
-
globalThis[NAV_CTX_KEY] = ctx;
|
|
105
|
-
return ctx;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Read the navigation context. Returns null during SSR (no provider)
|
|
110
|
-
* or in the RSC environment (no context available).
|
|
111
|
-
* Internal — used by useSegmentParams() and usePathname().
|
|
112
|
-
*/
|
|
113
|
-
function useNavigationContext() {
|
|
114
|
-
const ctx = getOrCreateContext();
|
|
115
|
-
if (!ctx) return null;
|
|
116
|
-
if (typeof React.useContext !== "function") return null;
|
|
117
|
-
return React.useContext(ctx);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Wraps children with NavigationContext.Provider.
|
|
121
|
-
*
|
|
122
|
-
* Used in browser-entry.ts renderRoot to wrap the RSC payload element
|
|
123
|
-
* so that navigation state updates atomically with the tree render.
|
|
124
|
-
*/
|
|
125
|
-
function NavigationProvider({ value, children }) {
|
|
126
|
-
const ctx = getOrCreateContext();
|
|
127
|
-
if (!ctx) return children;
|
|
128
|
-
return createElement(ctx.Provider, { value }, children);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Navigation state communicated between the router and renderRoot.
|
|
132
|
-
*
|
|
133
|
-
* The router calls setNavigationState() before renderRoot(). The
|
|
134
|
-
* renderRoot callback reads via getNavigationState() to create the
|
|
135
|
-
* NavigationProvider with the correct params/pathname.
|
|
136
|
-
*
|
|
137
|
-
* This is NOT used by hooks directly — hooks read from React context.
|
|
138
|
-
*
|
|
139
|
-
* Stored on globalThis (like the context instances above) because the
|
|
140
|
-
* router lives in one chunk while renderRoot lives in another. Module-
|
|
141
|
-
* level variables would be separate per chunk.
|
|
142
|
-
*/
|
|
143
|
-
var NAV_STATE_KEY = Symbol.for("__timber_nav_state");
|
|
144
|
-
function _getNavStateStore() {
|
|
145
|
-
const g = globalThis;
|
|
146
|
-
if (!g[NAV_STATE_KEY]) g[NAV_STATE_KEY] = { current: {
|
|
147
|
-
params: {},
|
|
148
|
-
pathname: "/"
|
|
149
|
-
} };
|
|
150
|
-
return g[NAV_STATE_KEY];
|
|
151
|
-
}
|
|
152
|
-
function setNavigationState(state) {
|
|
153
|
-
_getNavStateStore().current = state;
|
|
154
|
-
}
|
|
155
|
-
function getNavigationState() {
|
|
156
|
-
return _getNavStateStore().current;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Separate context for the in-flight navigation URL. Provided by
|
|
160
|
-
* TransitionRoot (urgent useState), consumed by LinkStatusProvider
|
|
161
|
-
* and useNavigationPending.
|
|
162
|
-
*
|
|
163
|
-
* Uses globalThis via Symbol.for for the same reason as NavigationContext
|
|
164
|
-
* above — the bundler may duplicate this module across chunks, and module-
|
|
165
|
-
* level variables would create separate context instances.
|
|
166
|
-
*/
|
|
167
|
-
function getOrCreatePendingContext() {
|
|
168
|
-
const existing = globalThis[PENDING_CTX_KEY];
|
|
169
|
-
if (existing !== void 0) return existing;
|
|
170
|
-
if (typeof React.createContext === "function") {
|
|
171
|
-
const ctx = React.createContext(null);
|
|
172
|
-
globalThis[PENDING_CTX_KEY] = ctx;
|
|
173
|
-
return ctx;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Read the pending navigation URL from context.
|
|
178
|
-
* Returns null during SSR (no provider) or in the RSC environment.
|
|
179
|
-
*/
|
|
180
|
-
function usePendingNavigationUrl() {
|
|
181
|
-
const ctx = getOrCreatePendingContext();
|
|
182
|
-
if (!ctx) return null;
|
|
183
|
-
if (typeof React.useContext !== "function") return null;
|
|
184
|
-
return React.useContext(ctx);
|
|
185
|
-
}
|
|
186
|
-
//#endregion
|
|
187
|
-
//#region src/client/link-status-provider.tsx
|
|
188
|
-
var NOT_PENDING = { pending: false };
|
|
189
|
-
var IS_PENDING = { pending: true };
|
|
190
|
-
/**
|
|
191
|
-
* Client component that reads the pending URL from PendingNavigationContext
|
|
192
|
-
* and provides a scoped LinkStatusContext to children. Renders no extra DOM —
|
|
193
|
-
* just a context provider around children.
|
|
194
|
-
*/
|
|
195
|
-
function LinkStatusProvider({ href, children }) {
|
|
196
|
-
const status = usePendingNavigationUrl() === href ? IS_PENDING : NOT_PENDING;
|
|
197
|
-
return /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
|
|
198
|
-
value: status,
|
|
199
|
-
children
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
//#endregion
|
|
203
44
|
//#region src/client/router-ref.ts
|
|
204
45
|
/**
|
|
205
46
|
* Set the global router instance. Called once during bootstrap.
|
|
@@ -224,6 +65,48 @@ function getRouterOrNull() {
|
|
|
224
65
|
return globalRouter;
|
|
225
66
|
}
|
|
226
67
|
//#endregion
|
|
68
|
+
//#region src/client/link-pending-store.ts
|
|
69
|
+
var LINK_PENDING_KEY = Symbol.for("__timber_link_pending");
|
|
70
|
+
/** Status object indicating link is pending — shared reference */
|
|
71
|
+
var PENDING_LINK_STATUS = { pending: true };
|
|
72
|
+
/** Status object indicating link is idle — shared reference */
|
|
73
|
+
var IDLE_LINK_STATUS = { pending: false };
|
|
74
|
+
function getStore() {
|
|
75
|
+
const g = globalThis;
|
|
76
|
+
if (!g[LINK_PENDING_KEY]) g[LINK_PENDING_KEY] = {
|
|
77
|
+
current: null,
|
|
78
|
+
navId: 0
|
|
79
|
+
};
|
|
80
|
+
return g[LINK_PENDING_KEY];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Register the link instance that initiated the current navigation.
|
|
84
|
+
*
|
|
85
|
+
* Called from <Link>'s click handler before router.navigate().
|
|
86
|
+
* - Resets the previous pending link to IDLE (urgent update, immediate)
|
|
87
|
+
* - Does NOT set the new link to PENDING here — the Link's click handler
|
|
88
|
+
* calls setLinkStatus(PENDING) directly for the eager show
|
|
89
|
+
* - Increments the navId counter for stale-clear protection
|
|
90
|
+
*
|
|
91
|
+
* Pass `null` to clear (e.g., for programmatic navigations).
|
|
92
|
+
*/
|
|
93
|
+
function setLinkForCurrentNavigation(link) {
|
|
94
|
+
const store = getStore();
|
|
95
|
+
const prev = store.current;
|
|
96
|
+
if (prev && prev !== link) prev.setLinkStatus(IDLE_LINK_STATUS);
|
|
97
|
+
store.current = link;
|
|
98
|
+
store.navId++;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Unmount a link instance from navigation tracking. Called when a Link
|
|
102
|
+
* component unmounts while it is the current navigation link. Prevents
|
|
103
|
+
* calling setState on an unmounted component.
|
|
104
|
+
*/
|
|
105
|
+
function unmountLinkForCurrentNavigation(link) {
|
|
106
|
+
const store = getStore();
|
|
107
|
+
if (store.current === link) store.current = null;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
227
110
|
//#region src/client/link.tsx
|
|
228
111
|
/**
|
|
229
112
|
* Read the current URL's search string without requiring a React hook.
|
|
@@ -346,6 +229,16 @@ function Link({ href, prefetch, scroll, segmentParams, searchParams, preserveSea
|
|
|
346
229
|
params: segmentParams,
|
|
347
230
|
searchParams
|
|
348
231
|
});
|
|
232
|
+
const [linkStatus, setLinkStatus] = useState(IDLE_LINK_STATUS);
|
|
233
|
+
const linkInstanceRef = useRef(null);
|
|
234
|
+
if (!linkInstanceRef.current) linkInstanceRef.current = { setLinkStatus };
|
|
235
|
+
else linkInstanceRef.current.setLinkStatus = setLinkStatus;
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const instance = linkInstanceRef.current;
|
|
238
|
+
return () => {
|
|
239
|
+
if (instance) unmountLinkForCurrentNavigation(instance);
|
|
240
|
+
};
|
|
241
|
+
}, []);
|
|
349
242
|
const resolvedHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : baseHref;
|
|
350
243
|
const internal = isInternalHref(resolvedHref);
|
|
351
244
|
const handleClick = internal ? (event) => {
|
|
@@ -366,6 +259,8 @@ function Link({ href, prefetch, scroll, segmentParams, searchParams, preserveSea
|
|
|
366
259
|
event.preventDefault();
|
|
367
260
|
const shouldScroll = scroll !== false;
|
|
368
261
|
const navHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : resolvedHref;
|
|
262
|
+
setLinkStatus(PENDING_LINK_STATUS);
|
|
263
|
+
setLinkForCurrentNavigation(linkInstanceRef.current);
|
|
369
264
|
router.navigate(navHref, { scroll: shouldScroll });
|
|
370
265
|
} : userOnClick;
|
|
371
266
|
const handleMouseEnter = internal && prefetch ? (event) => {
|
|
@@ -381,8 +276,8 @@ function Link({ href, prefetch, scroll, segmentParams, searchParams, preserveSea
|
|
|
381
276
|
href: resolvedHref,
|
|
382
277
|
onClick: handleClick,
|
|
383
278
|
onMouseEnter: handleMouseEnter,
|
|
384
|
-
children: /* @__PURE__ */ jsx(
|
|
385
|
-
|
|
279
|
+
children: /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
|
|
280
|
+
value: linkStatus,
|
|
386
281
|
children
|
|
387
282
|
})
|
|
388
283
|
});
|
|
@@ -519,6 +414,150 @@ var HistoryStack = class {
|
|
|
519
414
|
}
|
|
520
415
|
};
|
|
521
416
|
//#endregion
|
|
417
|
+
//#region src/client/navigation-context.ts
|
|
418
|
+
/**
|
|
419
|
+
* NavigationContext — React context for navigation state.
|
|
420
|
+
*
|
|
421
|
+
* Holds the current route params and pathname, updated atomically
|
|
422
|
+
* with the RSC tree on each navigation. This replaces the previous
|
|
423
|
+
* useSyncExternalStore approach for useSegmentParams() and usePathname(),
|
|
424
|
+
* which suffered from a timing gap: the new tree could commit before
|
|
425
|
+
* the external store re-renders fired, causing a frame where both
|
|
426
|
+
* old and new active states were visible simultaneously.
|
|
427
|
+
*
|
|
428
|
+
* By wrapping the RSC payload element in NavigationProvider inside
|
|
429
|
+
* renderRoot(), the context value and the element tree are passed to
|
|
430
|
+
* reactRoot.render() in the same call — atomic by construction.
|
|
431
|
+
* All consumers (useParams, usePathname) see the new values in the
|
|
432
|
+
* same render pass as the new tree.
|
|
433
|
+
*
|
|
434
|
+
* During SSR, no NavigationProvider is mounted. Hooks fall back to
|
|
435
|
+
* the ALS-backed getSsrData() for per-request isolation.
|
|
436
|
+
*
|
|
437
|
+
* IMPORTANT: createContext and useContext are NOT available in the RSC
|
|
438
|
+
* environment (React Server Components use a stripped-down React).
|
|
439
|
+
* The context is lazily initialized on first access, and all functions
|
|
440
|
+
* that depend on these APIs are safe to call from any environment —
|
|
441
|
+
* they return null or no-op when the APIs aren't available.
|
|
442
|
+
*
|
|
443
|
+
* SINGLETON GUARANTEE: All shared mutable state uses globalThis via
|
|
444
|
+
* Symbol.for keys. The RSC client bundler can duplicate this module
|
|
445
|
+
* across chunks (browser-entry graph + client-reference graph). With
|
|
446
|
+
* ESM output, each chunk gets its own module scope — module-level
|
|
447
|
+
* variables would create separate singleton instances per chunk.
|
|
448
|
+
* globalThis guarantees a single instance regardless of duplication.
|
|
449
|
+
*
|
|
450
|
+
* This workaround will be removed when Rolldown ships `format: 'app'`
|
|
451
|
+
* (module registry format that deduplicates like webpack/Turbopack).
|
|
452
|
+
* See design/27-chunking-strategy.md.
|
|
453
|
+
*
|
|
454
|
+
* See design/19-client-navigation.md §"NavigationContext"
|
|
455
|
+
*/
|
|
456
|
+
/**
|
|
457
|
+
* The context is created lazily to avoid calling createContext at module
|
|
458
|
+
* level. In the RSC environment, React.createContext doesn't exist —
|
|
459
|
+
* calling it at import time would crash the server.
|
|
460
|
+
*
|
|
461
|
+
* Context instances are stored on globalThis (NOT in module-level
|
|
462
|
+
* variables) because the ESM bundler can duplicate this module across
|
|
463
|
+
* chunks. Module-level variables would create separate instances per
|
|
464
|
+
* chunk — the provider in TransitionRoot (index chunk) would use
|
|
465
|
+
* context A while the consumer in useNavigationPending (shared chunk)
|
|
466
|
+
* reads from context B. globalThis guarantees a single instance.
|
|
467
|
+
*
|
|
468
|
+
* See design/27-chunking-strategy.md §"Singleton Safety"
|
|
469
|
+
*/
|
|
470
|
+
var NAV_CTX_KEY = Symbol.for("__timber_nav_ctx");
|
|
471
|
+
var PENDING_CTX_KEY = Symbol.for("__timber_pending_nav_ctx");
|
|
472
|
+
function getOrCreateContext() {
|
|
473
|
+
const existing = globalThis[NAV_CTX_KEY];
|
|
474
|
+
if (existing !== void 0) return existing;
|
|
475
|
+
if (typeof React.createContext === "function") {
|
|
476
|
+
const ctx = React.createContext(null);
|
|
477
|
+
globalThis[NAV_CTX_KEY] = ctx;
|
|
478
|
+
return ctx;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Read the navigation context. Returns null during SSR (no provider)
|
|
483
|
+
* or in the RSC environment (no context available).
|
|
484
|
+
* Internal — used by useSegmentParams() and usePathname().
|
|
485
|
+
*/
|
|
486
|
+
function useNavigationContext() {
|
|
487
|
+
const ctx = getOrCreateContext();
|
|
488
|
+
if (!ctx) return null;
|
|
489
|
+
if (typeof React.useContext !== "function") return null;
|
|
490
|
+
return React.useContext(ctx);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Wraps children with NavigationContext.Provider.
|
|
494
|
+
*
|
|
495
|
+
* Used in browser-entry.ts renderRoot to wrap the RSC payload element
|
|
496
|
+
* so that navigation state updates atomically with the tree render.
|
|
497
|
+
*/
|
|
498
|
+
function NavigationProvider({ value, children }) {
|
|
499
|
+
const ctx = getOrCreateContext();
|
|
500
|
+
if (!ctx) return children;
|
|
501
|
+
return createElement(ctx.Provider, { value }, children);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Navigation state communicated between the router and renderRoot.
|
|
505
|
+
*
|
|
506
|
+
* The router calls setNavigationState() before renderRoot(). The
|
|
507
|
+
* renderRoot callback reads via getNavigationState() to create the
|
|
508
|
+
* NavigationProvider with the correct params/pathname.
|
|
509
|
+
*
|
|
510
|
+
* This is NOT used by hooks directly — hooks read from React context.
|
|
511
|
+
*
|
|
512
|
+
* Stored on globalThis (like the context instances above) because the
|
|
513
|
+
* router lives in one chunk while renderRoot lives in another. Module-
|
|
514
|
+
* level variables would be separate per chunk.
|
|
515
|
+
*/
|
|
516
|
+
var NAV_STATE_KEY = Symbol.for("__timber_nav_state");
|
|
517
|
+
function _getNavStateStore() {
|
|
518
|
+
const g = globalThis;
|
|
519
|
+
if (!g[NAV_STATE_KEY]) g[NAV_STATE_KEY] = { current: {
|
|
520
|
+
params: {},
|
|
521
|
+
pathname: "/"
|
|
522
|
+
} };
|
|
523
|
+
return g[NAV_STATE_KEY];
|
|
524
|
+
}
|
|
525
|
+
function setNavigationState(state) {
|
|
526
|
+
_getNavStateStore().current = state;
|
|
527
|
+
}
|
|
528
|
+
function getNavigationState() {
|
|
529
|
+
return _getNavStateStore().current;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Separate context for the in-flight navigation URL. Provided by
|
|
533
|
+
* TransitionRoot (urgent useState), consumed by useNavigationPending
|
|
534
|
+
* and TopLoader. Per-link pending state uses useOptimistic instead
|
|
535
|
+
* (see link-pending-store.ts).
|
|
536
|
+
*
|
|
537
|
+
* Uses globalThis via Symbol.for for the same reason as NavigationContext
|
|
538
|
+
* above — the bundler may duplicate this module across chunks, and module-
|
|
539
|
+
* level variables would create separate context instances.
|
|
540
|
+
*/
|
|
541
|
+
function getOrCreatePendingContext() {
|
|
542
|
+
const existing = globalThis[PENDING_CTX_KEY];
|
|
543
|
+
if (existing !== void 0) return existing;
|
|
544
|
+
if (typeof React.createContext === "function") {
|
|
545
|
+
const ctx = React.createContext(null);
|
|
546
|
+
globalThis[PENDING_CTX_KEY] = ctx;
|
|
547
|
+
return ctx;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Read the pending navigation URL from context.
|
|
552
|
+
* Returns null during SSR (no provider) or in the RSC environment.
|
|
553
|
+
*/
|
|
554
|
+
function usePendingNavigationUrl() {
|
|
555
|
+
const ctx = getOrCreatePendingContext();
|
|
556
|
+
if (!ctx) return null;
|
|
557
|
+
if (typeof React.useContext !== "function") return null;
|
|
558
|
+
return React.useContext(ctx);
|
|
559
|
+
}
|
|
560
|
+
//#endregion
|
|
522
561
|
//#region src/client/use-params.ts
|
|
523
562
|
/**
|
|
524
563
|
* Set the current route params in the module-level store.
|