@timber-js/app 0.1.22 → 0.1.24
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/_chunks/{ssr-data-B2yikEEB.js → ssr-data-DLnbYpj1.js} +2 -4
- package/dist/_chunks/{ssr-data-B2yikEEB.js.map → ssr-data-DLnbYpj1.js.map} +1 -1
- package/dist/_chunks/{use-cookie-D5aS4slY.js → use-cookie-dDbpCTx-.js} +2 -2
- package/dist/_chunks/{use-cookie-D5aS4slY.js.map → use-cookie-dDbpCTx-.js.map} +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +104 -93
- package/dist/client/index.js.map +1 -1
- package/dist/client/navigation-context.d.ts +50 -0
- package/dist/client/navigation-context.d.ts.map +1 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +35 -25
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +11 -4
- package/dist/client/use-pathname.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -2
- package/package.json +1 -1
- package/src/client/browser-entry.ts +50 -10
- package/src/client/index.ts +4 -0
- package/src/client/navigation-context.ts +88 -0
- package/src/client/router.ts +33 -24
- package/src/client/use-params.ts +50 -54
- package/src/client/use-pathname.ts +31 -24
|
@@ -19,8 +19,6 @@ var currentParams = {};
|
|
|
19
19
|
function _setCurrentParams(params) {
|
|
20
20
|
currentParams = params;
|
|
21
21
|
}
|
|
22
|
-
/** Listeners notified when currentParams changes. */
|
|
23
|
-
var paramsListeners = /* @__PURE__ */ new Set();
|
|
24
22
|
/** Cached search string — avoids reparsing when URL hasn't changed. */
|
|
25
23
|
var cachedSearch = "";
|
|
26
24
|
var cachedSearchParams = new URLSearchParams();
|
|
@@ -85,6 +83,6 @@ function getSsrData() {
|
|
|
85
83
|
return currentSsrData;
|
|
86
84
|
}
|
|
87
85
|
//#endregion
|
|
88
|
-
export { _setCurrentParams as a, cachedSearchParams as c,
|
|
86
|
+
export { _setCurrentParams as a, cachedSearchParams as c, _setCachedSearch as i, currentParams as l, getSsrData as n, _setGlobalRouter as o, setSsrData as r, cachedSearch as s, clearSsrData as t, globalRouter as u };
|
|
89
87
|
|
|
90
|
-
//# sourceMappingURL=ssr-data-
|
|
88
|
+
//# sourceMappingURL=ssr-data-DLnbYpj1.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-data-
|
|
1
|
+
{"version":3,"file":"ssr-data-DLnbYpj1.js","names":[],"sources":["../../src/client/state.ts","../../src/client/ssr-data.ts"],"sourcesContent":["/**\n * Centralized client singleton state registry.\n *\n * ALL mutable module-level state that must have singleton semantics across\n * the client bundle lives here. Individual modules (router-ref.ts, ssr-data.ts,\n * use-params.ts, use-search-params.ts, unload-guard.ts) import from this file\n * and re-export thin wrapper functions.\n *\n * Why: In Vite dev, a module is instantiated separately if reached via different\n * import paths (e.g., relative `./foo.js` vs barrel `@timber-js/app/client`).\n * By centralizing all mutable state in a single module that is always reached\n * through the same dependency chain (barrel → wrapper → state.ts), we guarantee\n * a single instance of every piece of shared state.\n *\n * DO NOT import this file from outside client/. Server code must never depend\n * on client state. The barrel (client/index.ts) is the public entry point.\n *\n * See design/18-build-system.md §\"Module Singleton Strategy\" and\n * §\"Singleton State Registry\".\n */\n\nimport type { RouterInstance } from './router.js';\nimport type { SsrData } from './ssr-data.js';\n\n// ─── Router (from router-ref.ts) ──────────────────────────────────────────\n\n/** The global router singleton — set once during bootstrap. */\nexport let globalRouter: RouterInstance | null = null;\n\nexport function _setGlobalRouter(router: RouterInstance | null): void {\n globalRouter = router;\n}\n\n// ─── SSR Data Provider (from ssr-data.ts) ──────────────────────────────────\n\n/**\n * ALS-backed SSR data provider. When registered, getSsrData() reads from\n * this function (ALS store) instead of module-level currentSsrData.\n */\nexport let ssrDataProvider: (() => SsrData | undefined) | undefined;\n\nexport function _setSsrDataProvider(provider: (() => SsrData | undefined) | undefined): void {\n ssrDataProvider = provider;\n}\n\n/** Fallback SSR data for tests and environments without ALS. */\nexport let currentSsrData: SsrData | undefined;\n\nexport function _setCurrentSsrData(data: SsrData | undefined): void {\n currentSsrData = data;\n}\n\n// ─── Route Params (from use-params.ts) ──────────────────────────────────────\n\n/** Current route params snapshot — replaced (not mutated) on each navigation. */\nexport let currentParams: Record<string, string | string[]> = {};\n\nexport function _setCurrentParams(params: Record<string, string | string[]>): void {\n currentParams = params;\n}\n\n/** Listeners notified when currentParams changes. */\nexport const paramsListeners = new Set<() => void>();\n\n// ─── Search Params Cache (from use-search-params.ts) ────────────────────────\n\n/** Cached search string — avoids reparsing when URL hasn't changed. */\nexport let cachedSearch = '';\nexport let cachedSearchParams = new URLSearchParams();\n\nexport function _setCachedSearch(search: string, params: URLSearchParams): void {\n cachedSearch = search;\n cachedSearchParams = params;\n}\n\n// ─── Unload Guard (from unload-guard.ts) ─────────────────────────────────────\n\n/** Whether the page is currently being unloaded. */\nexport let unloading = false;\n\nexport function _setUnloading(value: boolean): void {\n unloading = value;\n}\n","/**\n * SSR Data — per-request state for client hooks during server-side rendering.\n *\n * RSC and SSR are separate Vite module graphs (see design/18-build-system.md),\n * so the RSC environment's request-context ALS is not visible to SSR modules.\n * This module provides getter/setter functions that ssr-entry.ts uses to\n * populate per-request data for React's render.\n *\n * Request isolation: On the server, ssr-entry.ts registers an ALS-backed\n * provider via registerSsrDataProvider(). getSsrData() reads from the ALS\n * store, ensuring correct per-request data even when Suspense boundaries\n * resolve asynchronously across concurrent requests. The module-level\n * setSsrData/clearSsrData functions are kept as a fallback for tests\n * and environments without ALS.\n *\n * IMPORTANT: This module must NOT import node:async_hooks or any Node.js-only\n * APIs, as it's imported by 'use client' hooks that are bundled for the browser.\n * The ALS instance lives in ssr-entry.ts (server-only); this module only holds\n * a reference to the provider function.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n */\n\nimport {\n ssrDataProvider,\n currentSsrData,\n _setSsrDataProvider,\n _setCurrentSsrData,\n} from './state.js';\n\n// ─── Types ────────────────────────────────────────────────────────\n\nexport interface SsrData {\n /** The request's URL pathname (e.g. '/dashboard/settings') */\n pathname: string;\n /** The request's search params as a plain record */\n searchParams: Record<string, string>;\n /** The request's cookies as name→value pairs */\n cookies: Map<string, string>;\n /** The request's route params (e.g. { id: '123' }) */\n params: Record<string, string | string[]>;\n /**\n * Mutable reference to NavContext for error boundary → RSC communication.\n * When TimberErrorBoundary catches a DenySignal, it sets\n * `_navContext._denyHandledByBoundary = true` to prevent the RSC entry\n * from promoting the denial to page-level. See LOCAL-298.\n */\n _navContext?: { _denyHandledByBoundary?: boolean };\n}\n\n// ─── ALS-Backed Provider ─────────────────────────────────────────\n//\n// Server-side code (ssr-entry.ts) registers a provider that reads\n// from AsyncLocalStorage. This avoids importing node:async_hooks\n// in this browser-bundled module.\n//\n// Module singleton guarantee: In Vite's SSR environment, both\n// ssr-entry.ts (via #/client/ssr-data.js) and client component hooks\n// (via @timber-js/app/client) must resolve to the SAME module instance\n// of this file. The timber-shims plugin ensures this by remapping\n// @timber-js/app/client → src/client/index.ts in the SSR environment.\n// Without this remap, @timber-js/app/client resolves to dist/ (via\n// package.json exports), creating a split where registerSsrDataProvider\n// writes to one instance but getSsrData reads from another.\n// See timber-shims plugin resolveId for details.\n\n/**\n * Register an ALS-backed SSR data provider. Called once at module load\n * by ssr-entry.ts to wire up per-request data via AsyncLocalStorage.\n *\n * When registered, getSsrData() reads from the provider (ALS store)\n * instead of module-level state, ensuring correct isolation for\n * concurrent requests with streaming Suspense.\n */\nexport function registerSsrDataProvider(provider: () => SsrData | undefined): void {\n _setSsrDataProvider(provider);\n}\n\n// ─── Module-Level Fallback ────────────────────────────────────────\n//\n// Used by tests and as a fallback when no ALS provider is registered.\n\n/**\n * Set the SSR data for the current request via module-level state.\n *\n * In production, ssr-entry.ts uses ALS (runWithSsrData) instead.\n * This function is retained for tests and as a fallback.\n */\nexport function setSsrData(data: SsrData): void {\n _setCurrentSsrData(data);\n}\n\n/**\n * Clear the SSR data after rendering completes.\n *\n * In production, ALS scope handles cleanup automatically.\n * This function is retained for tests and as a fallback.\n */\nexport function clearSsrData(): void {\n _setCurrentSsrData(undefined);\n}\n\n/**\n * Read the current request's SSR data. Returns undefined when called\n * outside an SSR render (i.e. on the client after hydration).\n *\n * Prefers the ALS-backed provider when registered (server-side),\n * falling back to module-level state (tests, legacy).\n *\n * Used by client hooks' server snapshot functions.\n */\nexport function getSsrData(): SsrData | undefined {\n if (ssrDataProvider) {\n return ssrDataProvider();\n }\n return currentSsrData;\n}\n"],"mappings":";;AA2BA,IAAW,eAAsC;AAEjD,SAAgB,iBAAiB,QAAqC;AACpE,gBAAe;;;;;;AASjB,IAAW;;AAOX,IAAW;AAEX,SAAgB,mBAAmB,MAAiC;AAClE,kBAAiB;;;AAMnB,IAAW,gBAAmD,EAAE;AAEhE,SAAgB,kBAAkB,QAAiD;AACjF,iBAAgB;;;AASlB,IAAW,eAAe;AAC1B,IAAW,qBAAqB,IAAI,iBAAiB;AAErD,SAAgB,iBAAiB,QAAgB,QAA+B;AAC9E,gBAAe;AACf,sBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACiBvB,SAAgB,WAAW,MAAqB;AAC9C,oBAAmB,KAAK;;;;;;;;AAS1B,SAAgB,eAAqB;AACnC,oBAAmB,KAAA,EAAU;;;;;;;;;;;AAY/B,SAAgB,aAAkC;AAChD,KAAI,gBACF,QAAO,iBAAiB;AAE1B,QAAO"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as getSsrData } from "./ssr-data-
|
|
1
|
+
import { n as getSsrData } from "./ssr-data-DLnbYpj1.js";
|
|
2
2
|
import { useSyncExternalStore } from "react";
|
|
3
3
|
//#region src/client/use-cookie.ts
|
|
4
4
|
/**
|
|
@@ -88,4 +88,4 @@ function useCookie(name, defaultOptions) {
|
|
|
88
88
|
//#endregion
|
|
89
89
|
export { useCookie as t };
|
|
90
90
|
|
|
91
|
-
//# sourceMappingURL=use-cookie-
|
|
91
|
+
//# sourceMappingURL=use-cookie-dDbpCTx-.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cookie-
|
|
1
|
+
{"version":3,"file":"use-cookie-dDbpCTx-.js","names":[],"sources":["../../src/client/use-cookie.ts"],"sourcesContent":["/**\n * useCookie — reactive client-side cookie hook.\n *\n * Uses useSyncExternalStore for SSR-safe, reactive cookie access.\n * All components reading the same cookie name re-render on change.\n * No cross-tab sync (intentional — see design/29-cookies.md).\n *\n * See design/29-cookies.md §\"useCookie(name) Hook\"\n */\n\nimport { useSyncExternalStore } from 'react';\nimport { getSsrData } from './ssr-data.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface ClientCookieOptions {\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Domain scope. Default: omitted (current domain). */\n domain?: string;\n /** Max age in seconds. */\n maxAge?: number;\n /** Expiration date. */\n expires?: Date;\n /** Cross-site policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Only send over HTTPS. Default: true in production. */\n secure?: boolean;\n}\n\nexport type CookieSetter = (value: string, options?: ClientCookieOptions) => void;\n\n// ─── Module-Level Cookie Store ────────────────────────────────────────────\n\ntype Listener = () => void;\n\n/** Per-name subscriber sets. */\nconst listeners = new Map<string, Set<Listener>>();\n\n/** Parse a cookie name from document.cookie. */\nfunction getCookieValue(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie.match(\n new RegExp('(?:^|;\\\\s*)' + name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\s*=\\\\s*([^;]*)')\n );\n return match ? decodeURIComponent(match[1]) : undefined;\n}\n\n/** Serialize options into a cookie string suffix. */\nfunction serializeOptions(options?: ClientCookieOptions): string {\n if (!options) return '; Path=/; SameSite=Lax';\n const parts: string[] = [];\n parts.push(`Path=${options.path ?? '/'}`);\n if (options.domain) parts.push(`Domain=${options.domain}`);\n if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n const sameSite = options.sameSite ?? 'lax';\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n if (options.secure) parts.push('Secure');\n return '; ' + parts.join('; ');\n}\n\n/** Notify all subscribers for a given cookie name. */\nfunction notify(name: string): void {\n const subs = listeners.get(name);\n if (subs) {\n for (const fn of subs) fn();\n }\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────────────\n\n/**\n * Reactive hook for reading/writing a client-side cookie.\n *\n * Returns `[value, setCookie, deleteCookie]`:\n * - `value`: current cookie value (string | undefined)\n * - `setCookie`: sets the cookie and triggers re-renders\n * - `deleteCookie`: deletes the cookie and triggers re-renders\n *\n * @param name - Cookie name.\n * @param defaultOptions - Default options for setCookie calls.\n */\nexport function useCookie(\n name: string,\n defaultOptions?: ClientCookieOptions\n): [value: string | undefined, setCookie: CookieSetter, deleteCookie: () => void] {\n const subscribe = (callback: Listener): (() => void) => {\n let subs = listeners.get(name);\n if (!subs) {\n subs = new Set();\n listeners.set(name, subs);\n }\n subs.add(callback);\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) listeners.delete(name);\n };\n };\n\n const getSnapshot = (): string | undefined => getCookieValue(name);\n const getServerSnapshot = (): string | undefined => getSsrData()?.cookies.get(name);\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n const setCookie: CookieSetter = (newValue: string, options?: ClientCookieOptions) => {\n const merged = { ...defaultOptions, ...options };\n document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;\n notify(name);\n };\n\n const deleteCookie = (): void => {\n const path = defaultOptions?.path ?? '/';\n const domain = defaultOptions?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notify(name);\n };\n\n return [value, setCookie, deleteCookie];\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,IAAM,4BAAY,IAAI,KAA4B;;AAGlD,SAAS,eAAe,MAAkC;AACxD,KAAI,OAAO,aAAa,YAAa,QAAO,KAAA;CAC5C,MAAM,QAAQ,SAAS,OAAO,MAC5B,IAAI,OAAO,gBAAgB,KAAK,QAAQ,uBAAuB,OAAO,GAAG,mBAAmB,CAC7F;AACD,QAAO,QAAQ,mBAAmB,MAAM,GAAG,GAAG,KAAA;;;AAIhD,SAAS,iBAAiB,SAAuC;AAC/D,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,QAAkB,EAAE;AAC1B,OAAM,KAAK,QAAQ,QAAQ,QAAQ,MAAM;AACzC,KAAI,QAAQ,OAAQ,OAAM,KAAK,UAAU,QAAQ,SAAS;AAC1D,KAAI,QAAQ,WAAW,KAAA,EAAW,OAAM,KAAK,WAAW,QAAQ,SAAS;AACzE,KAAI,QAAQ,QAAS,OAAM,KAAK,WAAW,QAAQ,QAAQ,aAAa,GAAG;CAC3E,MAAM,WAAW,QAAQ,YAAY;AACrC,OAAM,KAAK,YAAY,SAAS,OAAO,EAAE,CAAC,aAAa,GAAG,SAAS,MAAM,EAAE,GAAG;AAC9E,KAAI,QAAQ,OAAQ,OAAM,KAAK,SAAS;AACxC,QAAO,OAAO,MAAM,KAAK,KAAK;;;AAIhC,SAAS,OAAO,MAAoB;CAClC,MAAM,OAAO,UAAU,IAAI,KAAK;AAChC,KAAI,KACF,MAAK,MAAM,MAAM,KAAM,KAAI;;;;;;;;;;;;;AAiB/B,SAAgB,UACd,MACA,gBACgF;CAChF,MAAM,aAAa,aAAqC;EACtD,IAAI,OAAO,UAAU,IAAI,KAAK;AAC9B,MAAI,CAAC,MAAM;AACT,0BAAO,IAAI,KAAK;AAChB,aAAU,IAAI,MAAM,KAAK;;AAE3B,OAAK,IAAI,SAAS;AAClB,eAAa;AACX,QAAM,OAAO,SAAS;AACtB,OAAI,KAAM,SAAS,EAAG,WAAU,OAAO,KAAK;;;CAIhD,MAAM,oBAAwC,eAAe,KAAK;CAClE,MAAM,0BAA8C,YAAY,EAAE,QAAQ,IAAI,KAAK;CAEnF,MAAM,QAAQ,qBAAqB,WAAW,aAAa,kBAAkB;CAE7E,MAAM,aAA2B,UAAkB,YAAkC;EACnF,MAAM,SAAS;GAAE,GAAG;GAAgB,GAAG;GAAS;AAChD,WAAS,SAAS,GAAG,KAAK,GAAG,mBAAmB,SAAS,GAAG,iBAAiB,OAAO;AACpF,SAAO,KAAK;;CAGd,MAAM,qBAA2B;EAC/B,MAAM,OAAO,gBAAgB,QAAQ;EACrC,MAAM,SAAS,gBAAgB;EAC/B,IAAI,YAAY,GAAG,KAAK,4DAA4D;AACpF,MAAI,OAAQ,cAAa,YAAY;AACrC,WAAS,SAAS;AAClB,SAAO,KAAK;;AAGd,QAAO;EAAC;EAAO;EAAW;EAAa"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export type { HistoryEntry } from './history';
|
|
|
22
22
|
export { useActionState, useFormAction, useFormErrors } from './form';
|
|
23
23
|
export type { UseActionStateFn, UseActionStateReturn, FormErrorsResult } from './form';
|
|
24
24
|
export { useParams, setCurrentParams } from './use-params';
|
|
25
|
+
export { NavigationProvider, NavigationContext, getNavigationState, setNavigationState } from './navigation-context';
|
|
26
|
+
export type { NavigationState } from './navigation-context';
|
|
25
27
|
export { useQueryStates, bindUseQueryStates } from './use-query-states';
|
|
26
28
|
export { useCookie } from './use-cookie';
|
|
27
29
|
export type { ClientCookieOptions, CookieSetter } from './use-cookie';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGnE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAChG,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAChF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAGpG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACvE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAGvF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGnE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAChG,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAChF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAGpG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACvE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAGvF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrH,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { a as _setCurrentParams, c as cachedSearchParams,
|
|
2
|
+
import { a as _setCurrentParams, c as cachedSearchParams, i as _setCachedSearch, l as currentParams, n as getSsrData, o as _setGlobalRouter, r as setSsrData, s as cachedSearch, t as clearSsrData, u as globalRouter } from "../_chunks/ssr-data-DLnbYpj1.js";
|
|
3
3
|
import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-DAhgj8Gx.js";
|
|
4
|
-
import { t as useCookie } from "../_chunks/use-cookie-
|
|
4
|
+
import { t as useCookie } from "../_chunks/use-cookie-dDbpCTx-.js";
|
|
5
5
|
import { TimberErrorBoundary } from "./error-boundary.js";
|
|
6
6
|
import { createContext, createElement, startTransition, useActionState as useActionState$1, useContext, useEffect, useMemo, useRef, useSyncExternalStore, useTransition } from "react";
|
|
7
7
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
@@ -379,94 +379,94 @@ var HistoryStack = class {
|
|
|
379
379
|
}
|
|
380
380
|
};
|
|
381
381
|
//#endregion
|
|
382
|
-
//#region src/client/
|
|
382
|
+
//#region src/client/navigation-context.ts
|
|
383
383
|
/**
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* Returns the dynamic route parameters for the current URL.
|
|
387
|
-
* When called with a route pattern argument, TypeScript narrows
|
|
388
|
-
* the return type to the exact params shape for that route.
|
|
389
|
-
*
|
|
390
|
-
* Two layers of type narrowing work together:
|
|
391
|
-
* 1. The generic overload here uses the Routes interface directly —
|
|
392
|
-
* `useParams<R>()` returns `Routes[R]['params']`.
|
|
393
|
-
* 2. Build-time codegen generates per-route string-literal overloads
|
|
394
|
-
* in the .d.ts file for IDE autocomplete (see routing/codegen.ts).
|
|
384
|
+
* NavigationContext — React context for navigation state.
|
|
395
385
|
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
386
|
+
* Holds the current route params and pathname, updated atomically
|
|
387
|
+
* with the RSC tree on each navigation. This replaces the previous
|
|
388
|
+
* useSyncExternalStore approach for useParams() and usePathname(),
|
|
389
|
+
* which suffered from a timing gap: the new tree could commit before
|
|
390
|
+
* the external store re-renders fired, causing a frame where both
|
|
391
|
+
* old and new active states were visible simultaneously.
|
|
398
392
|
*
|
|
399
|
-
*
|
|
400
|
-
* (
|
|
401
|
-
*
|
|
393
|
+
* By wrapping the RSC payload element in NavigationProvider inside
|
|
394
|
+
* renderRoot(), the context value and the element tree are passed to
|
|
395
|
+
* reactRoot.render() in the same call — atomic by construction.
|
|
396
|
+
* All consumers (useParams, usePathname) see the new values in the
|
|
397
|
+
* same render pass as the new tree.
|
|
402
398
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* params change during client-side navigation. This matches the pattern
|
|
406
|
-
* used by usePathname() and useSearchParams().
|
|
407
|
-
*
|
|
408
|
-
* All mutable state is delegated to client/state.ts for singleton guarantees.
|
|
409
|
-
* See design/18-build-system.md §"Singleton State Registry"
|
|
399
|
+
* During SSR, no NavigationProvider is mounted. Hooks fall back to
|
|
400
|
+
* the ALS-backed getSsrData() for per-request isolation.
|
|
410
401
|
*
|
|
411
|
-
*
|
|
402
|
+
* See design/19-client-navigation.md §"Navigation Flow"
|
|
403
|
+
*/
|
|
404
|
+
/**
|
|
405
|
+
* The context value is null when no provider is mounted (SSR).
|
|
406
|
+
* On the client, NavigationProvider always wraps the tree.
|
|
412
407
|
*/
|
|
408
|
+
var NavigationContext = createContext(null);
|
|
413
409
|
/**
|
|
414
|
-
*
|
|
415
|
-
*
|
|
410
|
+
* Read the navigation context. Returns null during SSR (no provider).
|
|
411
|
+
* Internal — used by useParams() and usePathname().
|
|
416
412
|
*/
|
|
417
|
-
function
|
|
418
|
-
|
|
419
|
-
return () => paramsListeners.delete(callback);
|
|
413
|
+
function useNavigationContext() {
|
|
414
|
+
return useContext(NavigationContext);
|
|
420
415
|
}
|
|
421
416
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
417
|
+
* Wraps children with NavigationContext.Provider.
|
|
418
|
+
*
|
|
419
|
+
* Used in browser-entry.ts renderRoot to wrap the RSC payload element
|
|
420
|
+
* so that navigation state updates atomically with the tree render.
|
|
424
421
|
*/
|
|
425
|
-
function
|
|
426
|
-
return
|
|
422
|
+
function NavigationProvider({ value, children }) {
|
|
423
|
+
return createElement(NavigationContext.Provider, { value }, children);
|
|
427
424
|
}
|
|
428
425
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
426
|
+
* Module-level navigation state. Updated by the router before calling
|
|
427
|
+
* renderRoot(). The renderRoot callback reads this to create the
|
|
428
|
+
* NavigationProvider with the correct values.
|
|
429
|
+
*
|
|
430
|
+
* This is NOT used by hooks directly — hooks read from React context.
|
|
431
|
+
* This exists only as a communication channel between the router
|
|
432
|
+
* (which knows the new nav state) and renderRoot (which wraps the element).
|
|
432
433
|
*/
|
|
433
|
-
|
|
434
|
-
|
|
434
|
+
var _currentNavState = {
|
|
435
|
+
params: {},
|
|
436
|
+
pathname: "/"
|
|
437
|
+
};
|
|
438
|
+
function setNavigationState(state) {
|
|
439
|
+
_currentNavState = state;
|
|
440
|
+
}
|
|
441
|
+
function getNavigationState() {
|
|
442
|
+
return _currentNavState;
|
|
435
443
|
}
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/client/use-params.ts
|
|
436
446
|
/**
|
|
437
|
-
* Set the current route params
|
|
438
|
-
* Called by the router before renderPayload() so that new components
|
|
439
|
-
* in the RSC tree see the updated params via getSnapshot(), but
|
|
440
|
-
* preserved layout components don't re-render prematurely with
|
|
441
|
-
* {old tree, new params}.
|
|
447
|
+
* Set the current route params in the module-level store.
|
|
442
448
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
449
|
+
* Called by the router on each navigation. This updates the fallback
|
|
450
|
+
* snapshot used by tests and by the hook when called outside a React
|
|
451
|
+
* component (no NavigationContext available).
|
|
452
|
+
*
|
|
453
|
+
* On the client, the primary reactivity path is NavigationContext —
|
|
454
|
+
* the router calls setNavigationState() then renderRoot() which wraps
|
|
455
|
+
* the element in NavigationProvider. setCurrentParams is still called
|
|
456
|
+
* for the module-level fallback.
|
|
445
457
|
*
|
|
446
|
-
* On the client, the segment router calls this on each navigation.
|
|
447
458
|
* During SSR, params are also available via getSsrData().params
|
|
448
|
-
* (ALS-backed)
|
|
449
|
-
* module-level fallback path.
|
|
459
|
+
* (ALS-backed).
|
|
450
460
|
*/
|
|
451
461
|
function setCurrentParams(params) {
|
|
452
462
|
_setCurrentParams(params);
|
|
453
463
|
}
|
|
454
|
-
/**
|
|
455
|
-
* Notify all useSyncExternalStore subscribers that params have changed.
|
|
456
|
-
* Called by the router AFTER renderPayload() so that preserved layout
|
|
457
|
-
* components re-render only after the new tree is committed — producing
|
|
458
|
-
* an atomic {new tree, new params} update instead of a stale
|
|
459
|
-
* {old tree, new params} intermediate state.
|
|
460
|
-
*/
|
|
461
|
-
function notifyParamsListeners() {
|
|
462
|
-
for (const listener of paramsListeners) listener();
|
|
463
|
-
}
|
|
464
464
|
function useParams(_route) {
|
|
465
465
|
try {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
const navContext = useNavigationContext();
|
|
467
|
+
if (navContext !== null) return navContext.params;
|
|
468
|
+
} catch {}
|
|
469
|
+
return getSsrData()?.params ?? currentParams;
|
|
470
470
|
}
|
|
471
471
|
//#endregion
|
|
472
472
|
//#region src/client/router.ts
|
|
@@ -638,9 +638,21 @@ function createRouter(deps) {
|
|
|
638
638
|
function renderPayload(payload) {
|
|
639
639
|
if (deps.renderRoot) deps.renderRoot(payload);
|
|
640
640
|
}
|
|
641
|
-
/**
|
|
642
|
-
|
|
643
|
-
|
|
641
|
+
/**
|
|
642
|
+
* Update navigation state (params + pathname) for the next render.
|
|
643
|
+
*
|
|
644
|
+
* Sets both the module-level fallback (for tests and SSR) and the
|
|
645
|
+
* navigation context state (read by renderRoot to wrap the element
|
|
646
|
+
* in NavigationProvider). The context update is atomic with the tree
|
|
647
|
+
* render — both are passed to reactRoot.render() in the same call.
|
|
648
|
+
*/
|
|
649
|
+
function updateNavigationState(params, url) {
|
|
650
|
+
const resolvedParams = params ?? {};
|
|
651
|
+
setCurrentParams(resolvedParams);
|
|
652
|
+
setNavigationState({
|
|
653
|
+
params: resolvedParams,
|
|
654
|
+
pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
|
|
655
|
+
});
|
|
644
656
|
}
|
|
645
657
|
/** Apply head elements (title, meta tags) to the DOM if available. */
|
|
646
658
|
function applyHead(elements) {
|
|
@@ -681,9 +693,8 @@ function createRouter(deps) {
|
|
|
681
693
|
params: result.params
|
|
682
694
|
});
|
|
683
695
|
updateSegmentCache(result.segmentInfo);
|
|
684
|
-
|
|
696
|
+
updateNavigationState(result.params, url);
|
|
685
697
|
renderPayload(result.payload);
|
|
686
|
-
notifyParamsListeners();
|
|
687
698
|
applyHead(result.headElements);
|
|
688
699
|
window.dispatchEvent(new Event("timber:navigation-end"));
|
|
689
700
|
afterPaint(() => {
|
|
@@ -714,9 +725,8 @@ function createRouter(deps) {
|
|
|
714
725
|
params: result.params
|
|
715
726
|
});
|
|
716
727
|
updateSegmentCache(result.segmentInfo);
|
|
717
|
-
|
|
728
|
+
updateNavigationState(result.params, currentUrl);
|
|
718
729
|
renderPayload(result.payload);
|
|
719
|
-
notifyParamsListeners();
|
|
720
730
|
applyHead(result.headElements);
|
|
721
731
|
} finally {
|
|
722
732
|
setPending(false);
|
|
@@ -725,9 +735,8 @@ function createRouter(deps) {
|
|
|
725
735
|
async function handlePopState(url, scrollY = 0) {
|
|
726
736
|
const entry = historyStack.get(url);
|
|
727
737
|
if (entry && entry.payload !== null) {
|
|
728
|
-
|
|
738
|
+
updateNavigationState(entry.params, url);
|
|
729
739
|
renderPayload(entry.payload);
|
|
730
|
-
notifyParamsListeners();
|
|
731
740
|
applyHead(entry.headElements);
|
|
732
741
|
afterPaint(() => {
|
|
733
742
|
deps.scrollTo(0, scrollY);
|
|
@@ -738,14 +747,13 @@ function createRouter(deps) {
|
|
|
738
747
|
try {
|
|
739
748
|
const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree());
|
|
740
749
|
updateSegmentCache(result.segmentInfo);
|
|
741
|
-
|
|
750
|
+
updateNavigationState(result.params, url);
|
|
742
751
|
historyStack.push(url, {
|
|
743
752
|
payload: result.payload,
|
|
744
753
|
headElements: result.headElements,
|
|
745
754
|
params: result.params
|
|
746
755
|
});
|
|
747
756
|
renderPayload(result.payload);
|
|
748
|
-
notifyParamsListeners();
|
|
749
757
|
applyHead(result.headElements);
|
|
750
758
|
afterPaint(() => {
|
|
751
759
|
deps.scrollTo(0, scrollY);
|
|
@@ -915,31 +923,34 @@ function useRouter() {
|
|
|
915
923
|
* Returns the pathname portion of the current URL (e.g. '/dashboard/settings').
|
|
916
924
|
* Updates when client-side navigation changes the URL.
|
|
917
925
|
*
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
*
|
|
926
|
+
* On the client, reads from NavigationContext which is updated atomically
|
|
927
|
+
* with the RSC tree render. This replaces the previous useSyncExternalStore
|
|
928
|
+
* approach which only subscribed to popstate events — meaning usePathname()
|
|
929
|
+
* did NOT re-render on forward navigation (pushState). The context approach
|
|
930
|
+
* fixes this: pathname updates in the same render pass as the new tree.
|
|
921
931
|
*
|
|
922
932
|
* During SSR, reads the request pathname from the SSR ALS context
|
|
923
933
|
* (populated by ssr-entry.ts) instead of window.location.
|
|
934
|
+
*
|
|
935
|
+
* Compatible with Next.js's `usePathname()` from `next/navigation`.
|
|
924
936
|
*/
|
|
925
|
-
function getPathname() {
|
|
926
|
-
if (typeof window !== "undefined") return window.location.pathname;
|
|
927
|
-
return getSsrData()?.pathname ?? "/";
|
|
928
|
-
}
|
|
929
|
-
function getServerPathname() {
|
|
930
|
-
return getSsrData()?.pathname ?? "/";
|
|
931
|
-
}
|
|
932
|
-
function subscribe$1(callback) {
|
|
933
|
-
window.addEventListener("popstate", callback);
|
|
934
|
-
return () => window.removeEventListener("popstate", callback);
|
|
935
|
-
}
|
|
936
937
|
/**
|
|
937
938
|
* Read the current URL pathname.
|
|
938
939
|
*
|
|
939
|
-
*
|
|
940
|
+
* On the client, reads from NavigationContext (provided by
|
|
941
|
+
* NavigationProvider in renderRoot). During SSR, reads from the
|
|
942
|
+
* ALS-backed SSR data context. Falls back to window.location.pathname
|
|
943
|
+
* when called outside a React component (e.g., in tests).
|
|
940
944
|
*/
|
|
941
945
|
function usePathname() {
|
|
942
|
-
|
|
946
|
+
try {
|
|
947
|
+
const navContext = useNavigationContext();
|
|
948
|
+
if (navContext !== null) return navContext.pathname;
|
|
949
|
+
} catch {}
|
|
950
|
+
const ssrData = getSsrData();
|
|
951
|
+
if (ssrData) return ssrData.pathname ?? "/";
|
|
952
|
+
if (typeof window !== "undefined") return window.location.pathname;
|
|
953
|
+
return "/";
|
|
943
954
|
}
|
|
944
955
|
//#endregion
|
|
945
956
|
//#region src/client/use-search-params.ts
|
|
@@ -1238,6 +1249,6 @@ function useFormErrors(result) {
|
|
|
1238
1249
|
};
|
|
1239
1250
|
}
|
|
1240
1251
|
//#endregion
|
|
1241
|
-
export { HistoryStack, Link, LinkStatusContext, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, buildLinkProps, clearSsrData, createRouter, getRouter, getSsrData, interpolateParams, resolveHref, setCurrentParams, setGlobalRouter, setSsrData, useActionState, useCookie, useFormAction, useFormErrors, useLinkStatus, useNavigationPending, useParams, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
|
|
1252
|
+
export { HistoryStack, Link, LinkStatusContext, NavigationContext, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, buildLinkProps, clearSsrData, createRouter, getNavigationState, getRouter, getSsrData, interpolateParams, resolveHref, setCurrentParams, setGlobalRouter, setNavigationState, setSsrData, useActionState, useCookie, useFormAction, useFormErrors, useLinkStatus, useNavigationPending, useParams, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
|
|
1242
1253
|
|
|
1243
1254
|
//# sourceMappingURL=index.js.map
|