@timber-js/app 0.1.23 → 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 +108 -106
- 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 +37 -33
- 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,11 +1,10 @@
|
|
|
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";
|
|
8
|
-
import { flushSync } from "react-dom";
|
|
9
8
|
//#region src/client/link-navigate-interceptor.tsx
|
|
10
9
|
var _jsxFileName$2 = "/Users/dsaewitz/y/timber-js-fresh/packages/timber-app/src/client/link-navigate-interceptor.tsx";
|
|
11
10
|
/** Symbol used to store the onNavigate callback on anchor elements. */
|
|
@@ -380,94 +379,94 @@ var HistoryStack = class {
|
|
|
380
379
|
}
|
|
381
380
|
};
|
|
382
381
|
//#endregion
|
|
383
|
-
//#region src/client/
|
|
382
|
+
//#region src/client/navigation-context.ts
|
|
384
383
|
/**
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* Returns the dynamic route parameters for the current URL.
|
|
388
|
-
* When called with a route pattern argument, TypeScript narrows
|
|
389
|
-
* the return type to the exact params shape for that route.
|
|
390
|
-
*
|
|
391
|
-
* Two layers of type narrowing work together:
|
|
392
|
-
* 1. The generic overload here uses the Routes interface directly —
|
|
393
|
-
* `useParams<R>()` returns `Routes[R]['params']`.
|
|
394
|
-
* 2. Build-time codegen generates per-route string-literal overloads
|
|
395
|
-
* in the .d.ts file for IDE autocomplete (see routing/codegen.ts).
|
|
384
|
+
* NavigationContext — React context for navigation state.
|
|
396
385
|
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
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.
|
|
399
392
|
*
|
|
400
|
-
*
|
|
401
|
-
* (
|
|
402
|
-
*
|
|
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.
|
|
403
398
|
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* params change during client-side navigation. This matches the pattern
|
|
407
|
-
* used by usePathname() and useSearchParams().
|
|
408
|
-
*
|
|
409
|
-
* All mutable state is delegated to client/state.ts for singleton guarantees.
|
|
410
|
-
* 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.
|
|
411
401
|
*
|
|
412
|
-
*
|
|
402
|
+
* See design/19-client-navigation.md §"Navigation Flow"
|
|
413
403
|
*/
|
|
414
404
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
405
|
+
* The context value is null when no provider is mounted (SSR).
|
|
406
|
+
* On the client, NavigationProvider always wraps the tree.
|
|
417
407
|
*/
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
408
|
+
var NavigationContext = createContext(null);
|
|
409
|
+
/**
|
|
410
|
+
* Read the navigation context. Returns null during SSR (no provider).
|
|
411
|
+
* Internal — used by useParams() and usePathname().
|
|
412
|
+
*/
|
|
413
|
+
function useNavigationContext() {
|
|
414
|
+
return useContext(NavigationContext);
|
|
421
415
|
}
|
|
422
416
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
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.
|
|
425
421
|
*/
|
|
426
|
-
function
|
|
427
|
-
return
|
|
422
|
+
function NavigationProvider({ value, children }) {
|
|
423
|
+
return createElement(NavigationContext.Provider, { value }, children);
|
|
428
424
|
}
|
|
429
425
|
/**
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
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).
|
|
433
433
|
*/
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
var _currentNavState = {
|
|
435
|
+
params: {},
|
|
436
|
+
pathname: "/"
|
|
437
|
+
};
|
|
438
|
+
function setNavigationState(state) {
|
|
439
|
+
_currentNavState = state;
|
|
436
440
|
}
|
|
441
|
+
function getNavigationState() {
|
|
442
|
+
return _currentNavState;
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/client/use-params.ts
|
|
437
446
|
/**
|
|
438
|
-
* Set the current route params
|
|
439
|
-
* Called by the router before renderPayload() so that new components
|
|
440
|
-
* in the RSC tree see the updated params via getSnapshot(), but
|
|
441
|
-
* preserved layout components don't re-render prematurely with
|
|
442
|
-
* {old tree, new params}.
|
|
447
|
+
* Set the current route params in the module-level store.
|
|
443
448
|
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
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.
|
|
446
457
|
*
|
|
447
|
-
* On the client, the segment router calls this on each navigation.
|
|
448
458
|
* During SSR, params are also available via getSsrData().params
|
|
449
|
-
* (ALS-backed)
|
|
450
|
-
* module-level fallback path.
|
|
459
|
+
* (ALS-backed).
|
|
451
460
|
*/
|
|
452
461
|
function setCurrentParams(params) {
|
|
453
462
|
_setCurrentParams(params);
|
|
454
463
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Notify all useSyncExternalStore subscribers that params have changed.
|
|
457
|
-
* Called by the router AFTER renderPayload() so that preserved layout
|
|
458
|
-
* components re-render only after the new tree is committed — producing
|
|
459
|
-
* an atomic {new tree, new params} update instead of a stale
|
|
460
|
-
* {old tree, new params} intermediate state.
|
|
461
|
-
*/
|
|
462
|
-
function notifyParamsListeners() {
|
|
463
|
-
for (const listener of paramsListeners) listener();
|
|
464
|
-
}
|
|
465
464
|
function useParams(_route) {
|
|
466
465
|
try {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
466
|
+
const navContext = useNavigationContext();
|
|
467
|
+
if (navContext !== null) return navContext.params;
|
|
468
|
+
} catch {}
|
|
469
|
+
return getSsrData()?.params ?? currentParams;
|
|
471
470
|
}
|
|
472
471
|
//#endregion
|
|
473
472
|
//#region src/client/router.ts
|
|
@@ -639,9 +638,21 @@ function createRouter(deps) {
|
|
|
639
638
|
function renderPayload(payload) {
|
|
640
639
|
if (deps.renderRoot) deps.renderRoot(payload);
|
|
641
640
|
}
|
|
642
|
-
/**
|
|
643
|
-
|
|
644
|
-
|
|
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
|
+
});
|
|
645
656
|
}
|
|
646
657
|
/** Apply head elements (title, meta tags) to the DOM if available. */
|
|
647
658
|
function applyHead(elements) {
|
|
@@ -682,11 +693,8 @@ function createRouter(deps) {
|
|
|
682
693
|
params: result.params
|
|
683
694
|
});
|
|
684
695
|
updateSegmentCache(result.segmentInfo);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
renderPayload(result.payload);
|
|
688
|
-
notifyParamsListeners();
|
|
689
|
-
});
|
|
696
|
+
updateNavigationState(result.params, url);
|
|
697
|
+
renderPayload(result.payload);
|
|
690
698
|
applyHead(result.headElements);
|
|
691
699
|
window.dispatchEvent(new Event("timber:navigation-end"));
|
|
692
700
|
afterPaint(() => {
|
|
@@ -717,11 +725,8 @@ function createRouter(deps) {
|
|
|
717
725
|
params: result.params
|
|
718
726
|
});
|
|
719
727
|
updateSegmentCache(result.segmentInfo);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
renderPayload(result.payload);
|
|
723
|
-
notifyParamsListeners();
|
|
724
|
-
});
|
|
728
|
+
updateNavigationState(result.params, currentUrl);
|
|
729
|
+
renderPayload(result.payload);
|
|
725
730
|
applyHead(result.headElements);
|
|
726
731
|
} finally {
|
|
727
732
|
setPending(false);
|
|
@@ -730,11 +735,8 @@ function createRouter(deps) {
|
|
|
730
735
|
async function handlePopState(url, scrollY = 0) {
|
|
731
736
|
const entry = historyStack.get(url);
|
|
732
737
|
if (entry && entry.payload !== null) {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
renderPayload(entry.payload);
|
|
736
|
-
notifyParamsListeners();
|
|
737
|
-
});
|
|
738
|
+
updateNavigationState(entry.params, url);
|
|
739
|
+
renderPayload(entry.payload);
|
|
738
740
|
applyHead(entry.headElements);
|
|
739
741
|
afterPaint(() => {
|
|
740
742
|
deps.scrollTo(0, scrollY);
|
|
@@ -745,16 +747,13 @@ function createRouter(deps) {
|
|
|
745
747
|
try {
|
|
746
748
|
const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree());
|
|
747
749
|
updateSegmentCache(result.segmentInfo);
|
|
748
|
-
|
|
750
|
+
updateNavigationState(result.params, url);
|
|
749
751
|
historyStack.push(url, {
|
|
750
752
|
payload: result.payload,
|
|
751
753
|
headElements: result.headElements,
|
|
752
754
|
params: result.params
|
|
753
755
|
});
|
|
754
|
-
|
|
755
|
-
renderPayload(result.payload);
|
|
756
|
-
notifyParamsListeners();
|
|
757
|
-
});
|
|
756
|
+
renderPayload(result.payload);
|
|
758
757
|
applyHead(result.headElements);
|
|
759
758
|
afterPaint(() => {
|
|
760
759
|
deps.scrollTo(0, scrollY);
|
|
@@ -924,31 +923,34 @@ function useRouter() {
|
|
|
924
923
|
* Returns the pathname portion of the current URL (e.g. '/dashboard/settings').
|
|
925
924
|
* Updates when client-side navigation changes the URL.
|
|
926
925
|
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
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.
|
|
930
931
|
*
|
|
931
932
|
* During SSR, reads the request pathname from the SSR ALS context
|
|
932
933
|
* (populated by ssr-entry.ts) instead of window.location.
|
|
934
|
+
*
|
|
935
|
+
* Compatible with Next.js's `usePathname()` from `next/navigation`.
|
|
933
936
|
*/
|
|
934
|
-
function getPathname() {
|
|
935
|
-
if (typeof window !== "undefined") return window.location.pathname;
|
|
936
|
-
return getSsrData()?.pathname ?? "/";
|
|
937
|
-
}
|
|
938
|
-
function getServerPathname() {
|
|
939
|
-
return getSsrData()?.pathname ?? "/";
|
|
940
|
-
}
|
|
941
|
-
function subscribe$1(callback) {
|
|
942
|
-
window.addEventListener("popstate", callback);
|
|
943
|
-
return () => window.removeEventListener("popstate", callback);
|
|
944
|
-
}
|
|
945
937
|
/**
|
|
946
938
|
* Read the current URL pathname.
|
|
947
939
|
*
|
|
948
|
-
*
|
|
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).
|
|
949
944
|
*/
|
|
950
945
|
function usePathname() {
|
|
951
|
-
|
|
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 "/";
|
|
952
954
|
}
|
|
953
955
|
//#endregion
|
|
954
956
|
//#region src/client/use-search-params.ts
|
|
@@ -1247,6 +1249,6 @@ function useFormErrors(result) {
|
|
|
1247
1249
|
};
|
|
1248
1250
|
}
|
|
1249
1251
|
//#endregion
|
|
1250
|
-
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 };
|
|
1251
1253
|
|
|
1252
1254
|
//# sourceMappingURL=index.js.map
|