sibujs 4.13.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +15 -2
  3. package/dist/browser.js +9 -7
  4. package/dist/build.cjs +880 -281
  5. package/dist/build.d.cts +68 -3
  6. package/dist/build.d.ts +68 -3
  7. package/dist/build.js +88 -30
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +13 -13
  10. package/dist/cdn.full.global.js +12 -12
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-5RIBLY2N.js → chunk-2TTRW2TE.js} +6 -6
  13. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  14. package/dist/chunk-3HZOSMPX.js +90 -0
  15. package/dist/{chunk-QKFVMIAV.js → chunk-4FJSAUKB.js} +11 -3
  16. package/dist/{chunk-OIKD7YDY.js → chunk-4TQDTWWN.js} +6 -5
  17. package/dist/{chunk-5XUGJM3J.js → chunk-7ILEHTVX.js} +1 -1
  18. package/dist/{chunk-EU7T7EB3.js → chunk-A454RAEK.js} +4 -4
  19. package/dist/{chunk-QC3NP47A.js → chunk-AE32RGWX.js} +10 -6
  20. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  21. package/dist/{chunk-QZ47OZ25.js → chunk-BKZ5N6NK.js} +8 -6
  22. package/dist/{chunk-2O6JJCUR.js → chunk-BNXRWZZV.js} +49 -8
  23. package/dist/chunk-BOYKGGVD.js +2015 -0
  24. package/dist/{chunk-22OPSWMF.js → chunk-CCKUNHVU.js} +2 -2
  25. package/dist/{chunk-CPQKWCJ2.js → chunk-CJ7KS3DF.js} +16 -10
  26. package/dist/chunk-CNPT6NKV.js +1859 -0
  27. package/dist/{chunk-2NC56RGY.js → chunk-F67F77IA.js} +2 -2
  28. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  29. package/dist/chunk-GVAMIU6K.js +195 -0
  30. package/dist/{chunk-K4LNUT5L.js → chunk-HQIAL7F2.js} +8 -6
  31. package/dist/{chunk-C73VBPEF.js → chunk-JTFQ7IL4.js} +8 -8
  32. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  33. package/dist/{chunk-4UTGDVSA.js → chunk-MIRKTVH4.js} +7 -14
  34. package/dist/{chunk-YFTBCCVH.js → chunk-N7A6FKWW.js} +5 -3
  35. package/dist/chunk-NT3ZYB5S.js +167 -0
  36. package/dist/{chunk-VFPLWUT7.js → chunk-QS6GMWDD.js} +151 -86
  37. package/dist/chunk-R5U7XKVJ.js +16 -0
  38. package/dist/{chunk-ADZHPN2B.js → chunk-RVR5NPFL.js} +3 -3
  39. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  40. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  41. package/dist/{chunk-G3Z2FEBR.js → chunk-SVULV7O4.js} +119 -26
  42. package/dist/{chunk-PBJYCE2Z.js → chunk-TLS4NVIB.js} +5 -5
  43. package/dist/{chunk-KC62VURU.js → chunk-UUIKJJIK.js} +10 -8
  44. package/dist/chunk-UYTYNCMZ.js +199 -0
  45. package/dist/chunk-VM2U5LEL.js +2596 -0
  46. package/dist/{chunk-J4X7ICH4.js → chunk-Y4LUM2XS.js} +8 -6
  47. package/dist/data.cjs +265 -35
  48. package/dist/data.d.cts +31 -1
  49. package/dist/data.d.ts +31 -1
  50. package/dist/data.js +16 -10
  51. package/dist/devtools.cjs +15 -2
  52. package/dist/devtools.js +9 -7
  53. package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
  54. package/dist/ecosystem.cjs +62 -23
  55. package/dist/ecosystem.js +14 -12
  56. package/dist/extras.cjs +3520 -270
  57. package/dist/extras.d.cts +3 -3
  58. package/dist/extras.d.ts +3 -3
  59. package/dist/extras.js +39 -29
  60. package/dist/index.cjs +442 -253
  61. package/dist/index.d.cts +93 -283
  62. package/dist/index.d.ts +93 -283
  63. package/dist/index.js +26 -16
  64. package/dist/motion.cjs +12 -1
  65. package/dist/motion.js +6 -4
  66. package/dist/patterns.cjs +15 -2
  67. package/dist/patterns.js +10 -8
  68. package/dist/performance.cjs +36 -2
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +14 -10
  72. package/dist/plugins.cjs +5225 -1807
  73. package/dist/plugins.d.cts +8 -834
  74. package/dist/plugins.d.ts +8 -834
  75. package/dist/plugins.js +64 -2329
  76. package/dist/prerenderServer-MXJEVWA3.js +350 -0
  77. package/dist/renderRoute-DP20XwMS.d.cts +86 -0
  78. package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
  79. package/dist/routeHead-CE9SV-cH.d.cts +995 -0
  80. package/dist/routeHead-DzVxPviY.d.ts +995 -0
  81. package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
  82. package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
  83. package/dist/ssr-JpGpHnpC.d.cts +841 -0
  84. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  85. package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
  86. package/dist/ssr-dom.cjs +1862 -0
  87. package/dist/ssr-dom.d.cts +341 -0
  88. package/dist/ssr-dom.d.ts +341 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7630 -2064
  91. package/dist/ssr.d.cts +63 -5
  92. package/dist/ssr.d.ts +63 -5
  93. package/dist/ssr.js +136 -14
  94. package/dist/testing.cjs +6543 -87
  95. package/dist/testing.d.cts +80 -1
  96. package/dist/testing.d.ts +80 -1
  97. package/dist/testing.js +117 -4
  98. package/dist/ui.cjs +36 -2
  99. package/dist/ui.js +17 -14
  100. package/dist/widgets.cjs +14 -1
  101. package/dist/widgets.js +11 -9
  102. package/package.json +9 -2
  103. package/dist/chunk-2F4PSJ5Z.js +0 -765
  104. package/dist/chunk-7XHATCIH.js +0 -121
  105. package/dist/chunk-B36ADEIR.js +0 -21
  106. package/dist/ssr-Db0Sgdu3.d.cts +0 -245
  107. package/dist/ssr-Db0Sgdu3.d.ts +0 -245
@@ -0,0 +1,995 @@
1
+ import { C as Component$1, N as NodeChildren } from './types-CJFViL6Q.js';
2
+
3
+ /**
4
+ * Creation scopes for adopting hydration (see docs/architecture/hydration-adopt.md).
5
+ *
6
+ * The server and the browser agree on an id for every element a builder
7
+ * creates. An id is a PATH, not a global counter: each directive (`when`,
8
+ * `match`, `each` row, reactive child, `lazy()`, `Suspense`, route outlet)
9
+ * opens its own scope, and an element's id is its creation index inside the
10
+ * scope it was built in. So a branch the server rebuilt after its data arrived,
11
+ * or two boundaries that resolved in a different order, still get the ids the
12
+ * browser's first build gives them.
13
+ *
14
+ * App scope "" div#0 span#1 when → scope "2."
15
+ * when branch "2." ul#2.0 each → scope "2.1."
16
+ * each row "2.1._a." li#2.1._a.0 (key "a"; key 7 → "2.1.-7.")
17
+ *
18
+ * Nothing here runs unless a server render asked for adoption markers or a
19
+ * browser hydration is adopting: with no current scope every hook is one
20
+ * comparison. The sink deciding what an id means (stamp it on the server,
21
+ * claim the server element in the browser) lives in `sibujs/ssr`.
22
+ */
23
+ /** What a scope's elements are created through. */
24
+ interface AdoptSink {
25
+ /** False once the session is over: scopes stop numbering, and nothing is claimed any more. */
26
+ on: boolean;
27
+ /** Create (server) or claim (browser) the element with `id`; `null` means "create it normally". */
28
+ el(tag: string, ns: string | undefined, id: string): Element | null;
29
+ /** The hydration session a browser sink belongs to (see `hydrationHooks`). */
30
+ h?: unknown;
31
+ }
32
+ /** A creation scope: id prefix, next index, the sink. */
33
+ interface AdoptScope {
34
+ p: string;
35
+ n: number;
36
+ s: AdoptSink;
37
+ }
38
+
39
+ /**
40
+ * A route component. An alias of the public `Component` (props-less, any
41
+ * `Element` root), kept under this name so existing imports of the router's
42
+ * `Component` continue to work.
43
+ */
44
+ type Component = Component$1<void, Element>;
45
+ type AsyncComponent = () => Promise<Element>;
46
+ type LazyComponent = () => Promise<{
47
+ default: Component;
48
+ }>;
49
+ type Params = Record<string, string>;
50
+ /** Every value of every query key, in URL order: `?tag=a&tag=b` is `{ tag: ["a", "b"] }`. */
51
+ type QueryValues = Readonly<Record<string, readonly string[]>>;
52
+ /**
53
+ * The query of a navigation object. An array value repeats its key once per
54
+ * value, in order: `{ tag: ["a", "b"] }` is `?tag=a&tag=b`.
55
+ */
56
+ type NavigationQuery = Readonly<Record<string, string | readonly string[]>>;
57
+ interface RouteContext {
58
+ readonly path: string;
59
+ readonly params: Params;
60
+ /**
61
+ * The decoded query (`+` is a space, `%XX` is decoded). A repeated key keeps
62
+ * its last value here: `?tag=a&tag=b` is `{ tag: "b" }`. Read
63
+ * {@link RouteContext.queryAll} for every value.
64
+ */
65
+ readonly query: Params;
66
+ /**
67
+ * Every value of every query key, in URL order, with the same decoding as
68
+ * `query`: `?tag=a&tag=b` is `{ tag: ["a", "b"] }`. Two locations are the
69
+ * same route — for duplicate detection and `RouterLink` exact-active — when
70
+ * these match, key order aside.
71
+ */
72
+ readonly queryAll: QueryValues;
73
+ readonly hash: string;
74
+ readonly meta: RouteMeta;
75
+ readonly matched: RouteDef[];
76
+ }
77
+ type GuardResult = boolean | string | Promise<boolean | string>;
78
+ type Guard = (to: RouteContext, from?: RouteContext) => GuardResult;
79
+ type NavigationGuard = (to: RouteContext, from: RouteContext, next: NavigationNext) => void;
80
+ type NavigationNext = (result?: boolean | string | Error) => void;
81
+ type RouteMeta = Record<string, unknown>;
82
+ interface RouteBase {
83
+ readonly path: string;
84
+ readonly name?: string;
85
+ readonly meta?: RouteMeta;
86
+ /**
87
+ * The document head while this route is committed: `title`, `description`,
88
+ * `canonical`, Open Graph (`og`) and extra `meta` entries. Values may be
89
+ * getters `(route) => string`, evaluated reactively. Nested records merge,
90
+ * the deepest winning; `createRouter({ head })` provides the defaults.
91
+ *
92
+ * The router manages the head only once some route (or the router) declares
93
+ * one, so an app without `head` is never touched.
94
+ */
95
+ readonly head?: RouteHead;
96
+ readonly children?: RouteDef[];
97
+ readonly beforeEnter?: Guard | Guard[];
98
+ readonly alias?: string | string[];
99
+ /**
100
+ * Identity of the mounted instance of this route's component.
101
+ *
102
+ * When a navigation stays on this route but the key changes, the outlet
103
+ * disposes the old instance (running its `onUnmount` / `onCleanup`
104
+ * callbacks and releasing its bindings) and mounts a fresh one — so a page
105
+ * that reads `route().params.id` once at setup always sees the right id.
106
+ *
107
+ * **Default:** the path params this record declares in its *own* path (and
108
+ * aliases). `/records/1` → `/records/2` remounts; `?tab=a` → `?tab=b` or a
109
+ * hash change does not. A parent layout is not remounted when only a child's
110
+ * param changes — the child's `Outlet()` remounts the child instead. A
111
+ * record with no params of its own is never remounted by a param change, and
112
+ * a `*` tail (`pathMatch`) is not part of the default: a catch-all keeps its
113
+ * instance and reads `pathMatch` reactively (`key: (r) => r.params.pathMatch`
114
+ * remounts per path).
115
+ *
116
+ * **Override:** return any string derived from the route. Return a constant
117
+ * to keep one long-lived instance across param changes (the instance must
118
+ * then read params reactively, e.g. `routerState().params()` or
119
+ * `() => route().params.id`). Include query values to also remount on a
120
+ * query change.
121
+ *
122
+ * Called untracked with the committed route. Ignored by `KeepAliveRoute()`,
123
+ * which already keeps one instance per full location.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * // Keep one instance for every record; the page reads params reactively.
128
+ * { path: "/records/:id", component: RecordPage, key: () => "record" }
129
+ * // Also remount when ?tab changes.
130
+ * { path: "/records/:id", component: RecordPage, key: (r) => `${r.params.id}|${r.query.tab ?? ""}` }
131
+ * ```
132
+ */
133
+ readonly key?: (route: RouteContext) => string;
134
+ /**
135
+ * HTTP status a server render of this route answers with. A `*` record
136
+ * answers 404 without it; a `redirect` record answers 302 (set `301` for a
137
+ * permanent move). The deepest matched record that declares one wins.
138
+ */
139
+ readonly status?: number;
140
+ /**
141
+ * How a server render treats this route (the deepest record that declares
142
+ * it wins; the default is to render it):
143
+ *
144
+ * - `"prerender"` — rendered at build time and on a per-request server.
145
+ * - `"ssr"` — rendered per request only; the build-time pre-renderer emits
146
+ * the app shell plus the route's `head` for it, because its content is
147
+ * per-request (a status page).
148
+ * - `"client"` — never rendered on a server: the shell plus the route's
149
+ * `head` (a canvas designer, an admin panel). Excluded from the sitemap.
150
+ */
151
+ readonly render?: "ssr" | "prerender" | "client";
152
+ /**
153
+ * The concrete paths of a dynamic route, for the build-time pre-renderer:
154
+ * each entry is a params object for this record's path (`{ id: "taza" }`
155
+ * for `/producto/:id`) or a ready path string. May be async (read the
156
+ * catalog from the API at build time).
157
+ */
158
+ readonly getStaticPaths?: () => Iterable<Params | string> | Promise<Iterable<Params | string>>;
159
+ }
160
+ interface ComponentRoute extends RouteBase {
161
+ readonly component: Component;
162
+ }
163
+ interface AsyncRoute extends RouteBase {
164
+ readonly component: AsyncComponent | LazyComponent;
165
+ }
166
+ interface RedirectRoute extends RouteBase {
167
+ readonly redirect: string | ((to: RouteContext) => string);
168
+ }
169
+ /**
170
+ * A route whose component is loaded on first visit via a dynamic
171
+ * `import()`. This is the ergonomic shorthand for
172
+ * `{ component: lazy(() => import("./Page")) }`.
173
+ *
174
+ * The loader must return a module with a `default` Component export.
175
+ */
176
+ interface LazyRoute extends RouteBase {
177
+ readonly lazy: () => Promise<{
178
+ default: Component;
179
+ }>;
180
+ }
181
+ type RouteDef = ComponentRoute | AsyncRoute | RedirectRoute | LazyRoute;
182
+ interface RouterOptions {
183
+ /**
184
+ * `"history"` (default) and `"hash"` read and write the browser location.
185
+ * `"memory"` keeps the location in the router itself: it starts at `url`
186
+ * and never touches `window`, `history` or `location`. Created inside a
187
+ * server request scope (`runInSSRContext`, `renderRoute`), a memory router
188
+ * belongs to that request only, so concurrent renders each have their own.
189
+ */
190
+ readonly mode?: "history" | "hash" | "memory";
191
+ /**
192
+ * Initial location of a `"memory"` router (`"/cotizar?servicio=dtf"`).
193
+ * Default `"/"`. It is the REQUEST URL, like the browser's location: with a
194
+ * `base`, a path under it (`/app/cotizar`, `/app`, `/app/`) has the base
195
+ * stripped at a segment boundary, the query and hash kept; a path not under
196
+ * it (`/application`) is taken as it is. Navigation targets (`navigate()`,
197
+ * links, redirects) are application paths, without the base, in every mode.
198
+ */
199
+ readonly url?: string;
200
+ /**
201
+ * `"strict"` (default): `/cotizar/` and `/cotizar` are different paths.
202
+ * `"ignore"`: a trailing slash is dropped before matching, so `/cotizar/` is
203
+ * the `/cotizar` route — what a static server serving `cotizar/index.html`
204
+ * at `/cotizar/` (FastAPI `StaticFiles`, nginx, S3) needs.
205
+ */
206
+ readonly trailingSlash?: "strict" | "ignore";
207
+ readonly base?: string;
208
+ readonly linkActiveClass?: string;
209
+ readonly linkExactActiveClass?: string;
210
+ readonly fallback?: boolean;
211
+ /**
212
+ * Scroll position to restore after a navigation commits.
213
+ *
214
+ * A **browser-only, post-navigation side effect**:
215
+ *
216
+ * - In a runtime without the required scrolling primitives
217
+ * (`requestAnimationFrame` and `window.scrollTo`) the hook is **not invoked
218
+ * at all** — it is browser code, and its result would be discarded anyway.
219
+ * - It runs *after* the route and history have committed. An exception it
220
+ * throws is reported to the console but never retroactively fails an
221
+ * already-committed navigation: `NavigationResult.success` stays `true` and
222
+ * `currentRoute` stays authoritative.
223
+ * - Returning a falsy value means "do not scroll" and is not an error.
224
+ */
225
+ readonly scrollBehavior?: ScrollBehavior;
226
+ readonly guardTimeout?: number;
227
+ readonly cacheSize?: number;
228
+ readonly errorRetryDelay?: number;
229
+ readonly preloadStrategy?: "none" | "hover" | "visible";
230
+ /**
231
+ * Enable KeepAlive caching for route components.
232
+ * - `true` — cache all routes
233
+ * - string[] — cache only named routes matching these names
234
+ * - number — cache all routes with this max cache size
235
+ */
236
+ readonly keepAlive?: boolean | string[] | number;
237
+ /**
238
+ * Head defaults for every route, plus `titleTemplate`. Declaring it (or a
239
+ * `head` on any route) makes the router own `document.title` and the
240
+ * description / canonical / Open Graph tags; see {@link RouteBase.head}.
241
+ */
242
+ readonly head?: RouterHeadOptions;
243
+ }
244
+ type ScrollBehavior = (to: RouteContext, from: RouteContext, savedPosition: ScrollPosition | null) => ScrollPosition | null;
245
+ interface ScrollPosition {
246
+ readonly x: number;
247
+ readonly y: number;
248
+ }
249
+ /**
250
+ * Why a navigation failed, at a finer grain than `type`.
251
+ *
252
+ * `type: "aborted"` alone cannot tell a caller whether the user was denied
253
+ * access, whether they simply clicked a newer link, or whether the app tore the
254
+ * router down — three situations that call for very different handling (show a
255
+ * message / stay silent / stay silent). `reason` disambiguates them without
256
+ * changing the existing `type` values, so code branching on `type` keeps
257
+ * working unchanged.
258
+ *
259
+ * - `guard` — a navigation guard returned `false`.
260
+ * - `superseded` — a newer navigation started before this one could commit.
261
+ * Expected during rapid navigation; normally not surfaced to users.
262
+ * - `router-destroyed` — the router was destroyed while this navigation was in
263
+ * flight.
264
+ * - `redirect-loop` — redirect resolution exceeded the maximum hop count.
265
+ * - `unsafe-target` — the target or a redirect used a blocked URI scheme, or was
266
+ * an absolute/protocol-relative URL (open-redirect protection).
267
+ * - `duplicate` — the target is identical to the current route.
268
+ * - `error` — an unexpected error escaped the navigation pipeline.
269
+ */
270
+ type NavigationFailureReason = "guard" | "superseded" | "router-destroyed" | "redirect-loop" | "unsafe-target" | "duplicate" | "error";
271
+ interface NavigationFailure {
272
+ readonly type: "aborted" | "cancelled" | "duplicated" | "timeout";
273
+ /**
274
+ * Finer-grained cause. Additive — existing checks on `type` are unaffected.
275
+ */
276
+ readonly reason?: NavigationFailureReason;
277
+ readonly from: RouteContext;
278
+ readonly to: RouteContext;
279
+ readonly error?: Error;
280
+ }
281
+ type NavigationResult = {
282
+ success: true;
283
+ route: RouteContext;
284
+ } | {
285
+ success: false;
286
+ type: NavigationFailure["type"];
287
+ /** Mirrors `failure.reason`; see {@link NavigationFailureReason}. */
288
+ reason?: NavigationFailureReason;
289
+ failure: NavigationFailure;
290
+ };
291
+ type NavigationTarget = string | {
292
+ path?: string;
293
+ name?: string;
294
+ params?: Params;
295
+ query?: NavigationQuery;
296
+ hash?: string;
297
+ };
298
+ /**
299
+ * Component loader with caching and error recovery
300
+ */
301
+ /**
302
+ * How the router will produce an instance of a route's component.
303
+ *
304
+ * Deliberately never holds an `Element`: an instance produced by one invocation
305
+ * must never become a reusable factory. (LOAD-002)
306
+ *
307
+ * The two kinds are structural, not provisional:
308
+ *
309
+ * - `factory` — invoke once per instance, only when an instance is needed.
310
+ * Everything unbranded lands here: synchronous, `async`, and
311
+ * Promise-returning component factories alike.
312
+ * - `deferred` — an **explicitly branded** `lazy()` module loader. This is the
313
+ * only kind preload may execute. (LOAD-004)
314
+ */
315
+ type RoutePlan = {
316
+ kind: "factory";
317
+ component: Component;
318
+ } | {
319
+ kind: "deferred";
320
+ load: AsyncComponent | LazyComponent;
321
+ };
322
+ declare class SibuRouter {
323
+ private options;
324
+ private matcher;
325
+ private guards;
326
+ private loader;
327
+ private navigator;
328
+ private currentRouteGetter;
329
+ private currentRouteSetter;
330
+ private isReadyGetter;
331
+ private isReadySetter;
332
+ private cleanup;
333
+ private destroyed;
334
+ private navEpoch;
335
+ private headController;
336
+ private serverHeadRoute;
337
+ private withdrawServerHead;
338
+ private memoryLocation;
339
+ /**
340
+ * What the initial navigation did, for a server render's HTTP status: the
341
+ * result, and the status of the first redirect hop it took (a `redirect`
342
+ * record's `status`, 302 by default; 302 for a guard redirect).
343
+ * @internal
344
+ */
345
+ initialOutcome: {
346
+ result: NavigationResult | null;
347
+ redirectStatus: number | null;
348
+ /** Who redirected: a `redirect` record (the same for every visitor) or a guard (visitor-dependent). */
349
+ redirectBy: "route" | "guard" | null;
350
+ };
351
+ private readonly readyPromise;
352
+ private resolveReady;
353
+ private loadedPromise;
354
+ constructor(routes: RouteDef[], options?: RouterOptions);
355
+ private initialize;
356
+ /** Load the component of every record the current route matched, without instantiating any. */
357
+ private preloadMatched;
358
+ /** Settles once the initial navigation committed or failed (before component loads). @internal */
359
+ whenNavigated(): Promise<void>;
360
+ /** Settles after the initial navigation and its component loads; see `routerReady()`. */
361
+ whenReady(): Promise<void>;
362
+ private createInitialRoute;
363
+ private handleLocationChange;
364
+ private getCurrentPath;
365
+ private createRouteContext;
366
+ navigate(to: NavigationTarget, options?: {
367
+ replace?: boolean;
368
+ state?: unknown;
369
+ skipHistory?: boolean;
370
+ initialResolution?: boolean;
371
+ }): Promise<NavigationResult>;
372
+ private static readonly MAX_REDIRECT_DEPTH;
373
+ private performNavigation;
374
+ private updateHead;
375
+ private resolvePath;
376
+ private isSameRoute;
377
+ private updateHistory;
378
+ /**
379
+ * Apply the configured `scrollBehavior`, if the runtime can actually scroll.
380
+ *
381
+ * This runs *after* the route has been committed, so an exception here does
382
+ * not merely skip scrolling — it propagates out of `navigateInternal` and the
383
+ * navigation is reported as `success: false` while the route state says it
384
+ * succeeded. In a DOM-less runtime the bare `requestAnimationFrame` did
385
+ * exactly that: a legal `scrollBehavior` option made every navigation report
386
+ * failure under SSR and in memory-router-style tests. (MEM-001)
387
+ *
388
+ * Policy A, consistent with the history write in `updateHistory()` (NODE-001):
389
+ * the route still commits, and only the browser-only side effect is skipped.
390
+ *
391
+ * Both primitives are probed, not just `window` — `requestAnimationFrame` and
392
+ * `scrollTo` can be missing independently (jsdom without a rAF polyfill, a
393
+ * partial DOM shim). `requestAnimationFrame` is invoked as a method on the
394
+ * global object rather than through a detached reference, since browsers
395
+ * reject a bare call with "Illegal invocation".
396
+ */
397
+ private handleScrollBehavior;
398
+ /**
399
+ * Resolve how this route's component will be produced, without instantiating
400
+ * anything. Pair with {@link instantiateComponent} at the commit boundary.
401
+ */
402
+ loadPlan(route: RouteDef, routePath: string): Promise<RoutePlan>;
403
+ /**
404
+ * Create exactly one real instance from an already-resolved plan. With a
405
+ * `scope`, the factory runs in it (see `RouteScope`).
406
+ */
407
+ instantiateComponent(plan: RoutePlan, route: RouteDef, routePath: string, scope?: RouteScope): Promise<Element>;
408
+ /** Resolve a route's module/factory without creating any component instance. */
409
+ preloadPlan(route: RouteDef, routePath: string): Promise<void>;
410
+ beforeEach(guard: NavigationGuard): () => void;
411
+ beforeResolve(guard: NavigationGuard): () => void;
412
+ afterEach(hook: (to: RouteContext, from: RouteContext) => void): () => void;
413
+ push(to: NavigationTarget): Promise<NavigationResult>;
414
+ replace(to: NavigationTarget): Promise<NavigationResult>;
415
+ go(delta: number): void;
416
+ back(): void;
417
+ forward(): void;
418
+ get currentRoute(): RouteContext;
419
+ get isReady(): boolean;
420
+ /** @internal Navigation generation — see `navEpoch`. Not a public API. */
421
+ get navigationEpoch(): number;
422
+ get isNavigating(): boolean;
423
+ destroy(): void;
424
+ clearCache(): void;
425
+ clearErrorCache(): void;
426
+ updateRoutes(routes: RouteDef[]): void;
427
+ /**
428
+ * Add a route dynamically at runtime.
429
+ */
430
+ addRoute(route: RouteDef, parentPath?: string): void;
431
+ /**
432
+ * Remove a route by path.
433
+ */
434
+ removeRoute(path: string): void;
435
+ /**
436
+ * Get the reactive route getter for tracking.
437
+ */
438
+ get routeGetter(): () => RouteContext;
439
+ }
440
+ /**
441
+ * Create the global router and make it the one `route()`, `navigate()` and
442
+ * `RouterLink` talk to. Any previously created router is destroyed first.
443
+ *
444
+ * @param routesOrOptions Either the route table, or the options object when
445
+ * routes are supplied later via {@link setRoutes}.
446
+ * @param options Router options when the first argument is the route table.
447
+ * @returns The created router instance.
448
+ */
449
+ declare function createRouter(routesOrOptions: RouteDef[] | RouterOptions, options?: RouterOptions): SibuRouter;
450
+ /**
451
+ * Set routes on the global router (replaces existing routes).
452
+ */
453
+ declare function setRoutes(routes: RouteDef[]): void;
454
+ /**
455
+ * The route one mounted route instance was committed with.
456
+ *
457
+ * A navigation commits the router's route first; the outlet swaps the
458
+ * component afterwards — a microtask later for a synchronous page, a whole
459
+ * chunk download later for a lazy one. If route instances read the
460
+ * router-global route, the outgoing page re-runs its effects against the
461
+ * INCOMING route's params in that window, and its teardown sees them too.
462
+ *
463
+ * So each instance reads its own scope instead. Outlets create one per
464
+ * instance, run the component factory in it (an owner scope, see
465
+ * reactivity/track-core.ts, so every computation the page creates — now or
466
+ * later, through its directives — inherits it), and then:
467
+ *
468
+ * - an instance the outlet KEEPS across a navigation (same record and instance
469
+ * key: a query/hash-only change, or a layout whose child changed) is handed
470
+ * the new route — it follows the navigation, as before;
471
+ * - an instance being REPLACED is never updated: it keeps observing its own
472
+ * route until it is disposed, and its teardown runs in its scope;
473
+ * - a KeepAlive view keeps its route while cached and gets the new route when
474
+ * it is shown again.
475
+ *
476
+ * Once the instance is disposed the scope is released: a computation that
477
+ * outlived it (an app-lifetime derived that happened to be created while the
478
+ * page rendered) is not the page's, and falls back to the router-global route.
479
+ */
480
+ declare class RouteScope {
481
+ /** Creation scope the instance is built in (adopting hydration); see `adoptScope`. */
482
+ adopt: AdoptScope | null;
483
+ private readonly get;
484
+ private readonly set;
485
+ private released;
486
+ constructor(route: RouteContext);
487
+ /** The instance's route, or `null` once released. Reactive. */
488
+ read(): RouteContext | null;
489
+ /** Hand a kept instance the route of the navigation that kept it. */
490
+ update(route: RouteContext): void;
491
+ /**
492
+ * Called when the instance is disposed. The switch to "no scope" is
493
+ * published a microtask later, once the whole dispose has finished: a
494
+ * teardown the instance registered after this one (an `onMount` effect, say)
495
+ * may still be alive at this point, and must not re-run against the
496
+ * router-global route on its way out.
497
+ */
498
+ release(): void;
499
+ }
500
+ /**
501
+ * Read the current route as a plain snapshot.
502
+ *
503
+ * Reading it once in a route component's setup is safe: when a navigation
504
+ * changes that route's own params, the outlet mounts a fresh instance (see
505
+ * `RouteBase.key`). Inside a getter — `() => route().params.id` — the read is
506
+ * reactive, which is what a long-lived layout or a constant-`key` route needs.
507
+ *
508
+ * Inside a route instance (anything its component creates, and any effect,
509
+ * derived or binding created from there) this is the route THAT INSTANCE was
510
+ * committed with. An instance the outlet keeps across a navigation follows it;
511
+ * an instance being replaced keeps its own route until it is disposed, so its
512
+ * effects and teardown never observe the next page's params. Everywhere else
513
+ * it is the router's current route.
514
+ *
515
+ * @returns The active {@link RouteContext} (path, params, query, hash).
516
+ * @throws If no router has been created yet.
517
+ */
518
+ declare function route(): RouteContext;
519
+ /**
520
+ * Handle onto the global router's navigation surface.
521
+ *
522
+ * @returns An object exposing the current route plus `push`, `replace`, `go`,
523
+ * `back`, `forward` and the three guard registrars.
524
+ * @throws If no router has been created yet.
525
+ */
526
+ declare function router(): {
527
+ currentRoute: RouteContext;
528
+ isReady: boolean;
529
+ isNavigating: boolean;
530
+ push: (to: NavigationTarget) => Promise<NavigationResult> | undefined;
531
+ replace: (to: NavigationTarget) => Promise<NavigationResult> | undefined;
532
+ go: (delta: number) => void | undefined;
533
+ back: () => void | undefined;
534
+ forward: () => void | undefined;
535
+ beforeEach: (guard: NavigationGuard) => (() => void) | undefined;
536
+ beforeResolve: (guard: NavigationGuard) => (() => void) | undefined;
537
+ afterEach: (hook: (to: RouteContext, from: RouteContext) => void) => (() => void) | undefined;
538
+ };
539
+ /**
540
+ * Resolves once the router has resolved the page's initial location AND loaded
541
+ * the components that location matched (lazy route chunks included).
542
+ *
543
+ * Await it before `hydrate()` on a pre-rendered page: the client tree is then
544
+ * built with every route component already loaded, so the outlet renders in
545
+ * the same task and the swap from pre-rendered markup shows no empty frame.
546
+ * Also resolves if the initial navigation fails or the router is destroyed.
547
+ *
548
+ * @returns A promise that never rejects.
549
+ * @throws If no router has been created yet.
550
+ *
551
+ * @example
552
+ * ```ts
553
+ * createRouter(routes, { mode: "history" });
554
+ * await routerReady();
555
+ * hydrate(App, document.getElementById("app")!);
556
+ * ```
557
+ */
558
+ declare function routerReady(): Promise<void>;
559
+ /**
560
+ * Navigate to a target, running every registered guard.
561
+ *
562
+ * @param to Path string or location descriptor.
563
+ * @param options `replace` swaps the current history entry instead of pushing;
564
+ * `state` is stored on the history entry.
565
+ * @returns A promise for the {@link NavigationResult} — resolved even when a
566
+ * guard cancels, so the outcome must be inspected rather than assumed.
567
+ * @throws If no router has been created yet.
568
+ */
569
+ declare function navigate(to: NavigationTarget, options?: {
570
+ replace?: boolean;
571
+ state?: unknown;
572
+ }): Promise<NavigationResult>;
573
+ /**
574
+ * Navigate by pushing a new history entry.
575
+ *
576
+ * @param to Path string or location descriptor.
577
+ * @returns A promise for the {@link NavigationResult}.
578
+ */
579
+ declare function push(to: NavigationTarget): Promise<NavigationResult>;
580
+ /**
581
+ * Navigate by replacing the current history entry.
582
+ *
583
+ * @param to Path string or location descriptor.
584
+ * @returns A promise for the {@link NavigationResult}.
585
+ */
586
+ declare function replace(to: NavigationTarget): Promise<NavigationResult>;
587
+ /**
588
+ * Move through history by a relative offset, as `history.go` does.
589
+ *
590
+ * @param delta Number of entries to move; negative goes back.
591
+ * @returns Nothing — history moves asynchronously, so nothing is awaited here.
592
+ */
593
+ declare function go(delta: number): void;
594
+ /** Go back one history entry. Equivalent to `go(-1)`. */
595
+ declare function back(): void;
596
+ /** Go forward one history entry. Equivalent to `go(1)`. */
597
+ declare function forward(): void;
598
+ /**
599
+ * Register a guard that runs before every navigation is confirmed.
600
+ *
601
+ * @param guard Called with the target and current routes; return `false` to
602
+ * cancel, or a target to redirect.
603
+ * @returns An unregister function.
604
+ */
605
+ declare function beforeEach(guard: NavigationGuard): () => void;
606
+ /**
607
+ * Register a guard that runs after `beforeEach` and after async components have
608
+ * resolved, but before the route commits.
609
+ *
610
+ * @param guard Called with the target and current routes.
611
+ * @returns An unregister function.
612
+ */
613
+ declare function beforeResolve(guard: NavigationGuard): () => void;
614
+ /**
615
+ * Register a hook that runs after every confirmed navigation. It cannot cancel
616
+ * or redirect — by the time it runs the route has already committed.
617
+ *
618
+ * @param hook Called with the new and previous routes.
619
+ * @returns An unregister function.
620
+ */
621
+ declare function afterEach(hook: (to: RouteContext, from: RouteContext) => void): () => void;
622
+ /**
623
+ * The route outlet: renders whichever component matches the current route, and
624
+ * swaps it when the top-level matched record or its instance key changes. By
625
+ * default the key is that record's own path params, so `/records/1` →
626
+ * `/records/2` mounts a fresh instance while a query- or hash-only change keeps
627
+ * the current one (see `RouteBase.key`).
628
+ *
629
+ * @returns A Comment anchor that manages the matched component. Like every
630
+ * factory here it hands back a live node the caller can insert directly.
631
+ *
632
+ * TRAP — a navigation REBUILDS the outlet's subtree, so focus and selection
633
+ * inside the outgoing route are discarded. The runtime restores focus when the
634
+ * incoming route contains an element with the same `id`, `name` or
635
+ * `data-focus-key`, and warns in dev when it cannot.
636
+ */
637
+ declare function Route(): Node;
638
+ /**
639
+ * A route outlet that caches rendered components using KeepAlive.
640
+ * Routes are preserved in the DOM cache so signals, form state, and scroll
641
+ * position survive navigation.
642
+ *
643
+ * Uses the `keepAlive` option from RouterOptions if set, or accepts
644
+ * explicit options.
645
+ *
646
+ * ## Cache identity
647
+ *
648
+ * A cached view is keyed by the **full location** — path + query + hash — so
649
+ * `/search?q=a`, `/search?q=b`, and `/docs#one` are three distinct cached views.
650
+ * Keying on `route.path` alone would serve one query's cached DOM and state for
651
+ * another.
652
+ *
653
+ * ## Reuse and disposal
654
+ *
655
+ * | Event | Effect on a cached view |
656
+ * |---|---|
657
+ * | navigated away from | detached, kept in cache, **not** disposed |
658
+ * | navigated back to | the same node is re-attached — no remount |
659
+ * | evicted past `max` (LRU) | disposed and dropped |
660
+ * | excluded by `include` | never cached; disposed on navigation away |
661
+ * | outlet disposed | whole cache disposed and cleared |
662
+ *
663
+ * ## Cache identity is not async ownership
664
+ *
665
+ * The cache key answers *"which cached view is this?"*. It never answers *"may
666
+ * this async completion still commit?"* — that is what the update generation is
667
+ * for, because `same route value !== same navigation generation`. See
668
+ * `docs/architecture/router.md`.
669
+ *
670
+ * @param options Optional: override the router-level keepAlive setting
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * // Cache all routes (max 10)
675
+ * createRouter(routes, { keepAlive: 10 });
676
+ * mount(() => div([nav, KeepAliveRoute()]), root);
677
+ *
678
+ * // Or cache specific routes by name
679
+ * createRouter(routes, { keepAlive: ["dashboard", "settings"] });
680
+ * mount(() => div([nav, KeepAliveRoute()]), root);
681
+ *
682
+ * // Or override per-outlet
683
+ * KeepAliveRoute({ max: 5, include: ["dashboard"] })
684
+ * ```
685
+ */
686
+ declare function KeepAliveRoute(options?: {
687
+ max?: number;
688
+ include?: string[];
689
+ }): Node;
690
+ declare function RouterLink(props: {
691
+ to: NavigationTarget;
692
+ replace?: boolean;
693
+ activeClass?: string;
694
+ exactActiveClass?: string;
695
+ /** @deprecated Pass children positionally: `RouterLink(props, children)`. */
696
+ nodes?: NodeChildren;
697
+ target?: string;
698
+ rel?: string;
699
+ [key: string]: unknown;
700
+ }, children?: NodeChildren): HTMLAnchorElement;
701
+ /**
702
+ * Async boundary for code-split / deferred content.
703
+ *
704
+ * **Ownership contract.** The boundary owns every node it creates or installs.
705
+ * A node that leaves the boundary — replaced by resolved content, or dropped on
706
+ * teardown — is lifecycle-disposed exactly once. Native detachment alone is not
707
+ * cleanup: a detached subtree keeps its effects, listeners and registered
708
+ * disposers alive, firing against DOM nobody can see. (SUS-001)
709
+ *
710
+ * **Async completion grants no commit permission.** A resolution that arrives
711
+ * after the boundary was torn down, or after a newer generation superseded it,
712
+ * may never insert into the DOM. Because the promise typically resolves with an
713
+ * *already constructed* Element — whose effects and listeners were created
714
+ * before the boundary discovered it lost ownership — the stale result is
715
+ * disposed rather than merely dropped. (SUS-002)
716
+ *
717
+ * The boundary is **single-shot**: `props.nodes()` is invoked exactly once, so
718
+ * only one async generation is reachable in practice. The generation token is
719
+ * kept anyway because teardown must be able to invalidate the in-flight one, and
720
+ * because ownership is the correct primitive to express that with.
721
+ */
722
+ declare function Suspense(props: {
723
+ /** Shown while `nodes` is pending. An element, or a function returning one. */
724
+ fallback?: (() => HTMLElement) | HTMLElement;
725
+ /**
726
+ * The content. May be an element, or any thenable resolving to one —
727
+ * `PromiseLike`, not `Promise`, because a promise from another realm or a
728
+ * custom thenable is awaited just the same. The previous type admitted only
729
+ * `Promise`, so the very values the runtime was fixed to handle still had to
730
+ * be cast at the call site.
731
+ */
732
+ nodes: () => HTMLElement | PromiseLike<HTMLElement>;
733
+ }): Node;
734
+ /**
735
+ * Creates a lazy-loaded component.
736
+ * Marks the function with a symbol so the router can detect it reliably
737
+ * (no AsyncFunction heuristic needed).
738
+ */
739
+ declare function lazy(importFn: () => Promise<{
740
+ default: Component;
741
+ }>): LazyComponent;
742
+ /**
743
+ * Preloads a route component
744
+ */
745
+ declare function preloadRoute(to: NavigationTarget): Promise<void>;
746
+ /**
747
+ * Validates if a route exists
748
+ */
749
+ /**
750
+ * Current navigation generation for the active router, or `-1` when no router
751
+ * exists (including after `destroyRouter()`).
752
+ *
753
+ * @internal Not part of the public API. Exists so asynchronous work that can
754
+ * acquire DOM ownership — notably `hydrateRouter()`'s bootstrap — can prove it
755
+ * has not been superseded. Capture before the async gap, re-check immediately
756
+ * before committing. A URL comparison cannot serve this purpose: an A→B→A
757
+ * navigation returns to the same URL under a *newer* generation, and stale work
758
+ * must stay superseded.
759
+ */
760
+ declare function __getNavigationEpoch(): number;
761
+ /**
762
+ * Whether a named route is registered.
763
+ *
764
+ * @param name The route's `name`.
765
+ * @returns `true` when a route with that name exists; `false` when it does not,
766
+ * and also when no router has been created yet.
767
+ */
768
+ declare function hasRoute(name: string): boolean;
769
+ /**
770
+ * Gets route information by name
771
+ */
772
+ declare function getRouteInfo(name: string): RouteDef | null;
773
+ /**
774
+ * Builds a URL for a route
775
+ */
776
+ declare function buildURL(to: NavigationTarget): string;
777
+ declare function destroyRouter(): void;
778
+ /**
779
+ * Remove the module-level `pagehide` listener. Intended for HMR and tests —
780
+ * normal apps never need to call this (the listener is page-lifetime).
781
+ *
782
+ * @internal
783
+ */
784
+ declare function __removeRouterPagehideHandler(): void;
785
+ /**
786
+ * Outlet renders the child route component within a layout.
787
+ * Use inside a parent route's component to render matched children.
788
+ *
789
+ * The child is remounted when the matched child record changes or when its
790
+ * instance key does — by default the params declared below the top-level
791
+ * record, so a child-only param change remounts the child and leaves the
792
+ * layout in place (see `RouteBase.key`).
793
+ */
794
+ declare function Outlet(): Node;
795
+ /**
796
+ * Add a route at runtime.
797
+ */
798
+ declare function addRoute(route: RouteDef, parentPath?: string): void;
799
+ /**
800
+ * Remove a route at runtime.
801
+ */
802
+ declare function removeRoute(path: string): void;
803
+ /**
804
+ * routerState provides centralized reactive access to router state.
805
+ */
806
+ declare function routerState(): {
807
+ currentPath: () => string;
808
+ params: () => Params;
809
+ query: () => Params;
810
+ queryAll: () => QueryValues;
811
+ hash: () => string;
812
+ meta: () => RouteMeta;
813
+ isNavigating: () => boolean;
814
+ isReady: () => boolean;
815
+ };
816
+ interface RouterPlugin {
817
+ name: string;
818
+ onNavigate?: (to: RouteContext, from: RouteContext) => void;
819
+ onError?: (error: Error, to: RouteContext) => void;
820
+ onReady?: () => void;
821
+ }
822
+ /**
823
+ * Register a router plugin (analytics, breadcrumbs, permissions, etc.).
824
+ */
825
+ declare function routerPlugin(plugin: RouterPlugin): () => void;
826
+ interface RouteTransitionOptions {
827
+ enterClass?: string;
828
+ leaveClass?: string;
829
+ duration?: number;
830
+ }
831
+ /**
832
+ * Configure route transition animations.
833
+ */
834
+ declare function setRouteTransition(options: RouteTransitionOptions): void;
835
+ /**
836
+ * Get current route transition options.
837
+ */
838
+ declare function getRouteTransition(): RouteTransitionOptions | null;
839
+ /**
840
+ * createMemoryRouter creates a router that doesn't interact with browser history.
841
+ * Useful for testing and SSR.
842
+ */
843
+ declare function createMemoryRouter(routes: RouteDef[], _initialPath?: string): {
844
+ router: SibuRouter;
845
+ currentPath: () => string;
846
+ push: (path: string) => Promise<NavigationResult>;
847
+ };
848
+
849
+ /**
850
+ * One head value: a string, nothing, or a getter evaluated reactively against
851
+ * the committed route — so route params (`(r) => \`Order ${r.params.id}\``) and
852
+ * translated strings (`() => t("pricing.title")`) both work and stay current.
853
+ * `null` explicitly clears a value a parent route or the defaults set.
854
+ */
855
+ type RouteHeadValue = string | null | undefined | ((route: RouteContext) => string | null | undefined);
856
+ /** Open Graph tags a route can set. */
857
+ interface RouteHeadOpenGraph {
858
+ /** `og:title`. Defaults to the route's title (without `titleTemplate`). */
859
+ title?: RouteHeadValue;
860
+ /** `og:description`. Defaults to the route's description. */
861
+ description?: RouteHeadValue;
862
+ /** `og:image` — use an ABSOLUTE URL; link-preview bots do not resolve relative ones. */
863
+ image?: RouteHeadValue;
864
+ /** `og:url`. Defaults to the canonical URL. */
865
+ url?: RouteHeadValue;
866
+ /** `og:type`, e.g. `"website"` or `"article"`. */
867
+ type?: RouteHeadValue;
868
+ /** `og:site_name`. */
869
+ siteName?: RouteHeadValue;
870
+ }
871
+ /** Twitter card tags a route can set (`twitter:*`). Declaring any turns on the fallbacks below. */
872
+ interface RouteHeadTwitter {
873
+ /** `twitter:card`, e.g. `"summary_large_image"`. */
874
+ card?: RouteHeadValue;
875
+ /** `twitter:site`, the site's handle. */
876
+ site?: RouteHeadValue;
877
+ /** `twitter:creator`. */
878
+ creator?: RouteHeadValue;
879
+ /** `twitter:title`. Defaults to the Open Graph title. */
880
+ title?: RouteHeadValue;
881
+ /** `twitter:description`. Defaults to the Open Graph description. */
882
+ description?: RouteHeadValue;
883
+ /** `twitter:image` (absolute URL). Defaults to `og:image`. */
884
+ image?: RouteHeadValue;
885
+ }
886
+ /** One `<link rel="alternate" hreflang>` translation of the page. */
887
+ interface RouteHeadAlternate {
888
+ /** BCP 47 language tag, or `"x-default"`. */
889
+ hreflang: string;
890
+ /** Absolute URL of the translated page. */
891
+ href: RouteHeadValue;
892
+ }
893
+ /**
894
+ * A JSON-LD object (`{ "@context": "https://schema.org", "@type": "Product", … }`).
895
+ * Serialized with `JSON.stringify` and the script escaping `serializeState`
896
+ * uses, so `</script>`, `<!--` and U+2028/U+2029 in the data cannot leave the
897
+ * script element.
898
+ */
899
+ type JsonLd = {
900
+ [key: string]: JsonLdValue;
901
+ };
902
+ type JsonLdValue = string | number | boolean | null | JsonLdValue[] | {
903
+ [key: string]: JsonLdValue;
904
+ };
905
+ /**
906
+ * The `head` of a route record (and of `createRouter({ head })`, as defaults).
907
+ *
908
+ * Nested records merge field by field, the deepest record winning; the router
909
+ * defaults sit underneath every route.
910
+ */
911
+ interface RouteHead {
912
+ /** `document.title`. */
913
+ title?: RouteHeadValue;
914
+ /** `<meta name="description">`. */
915
+ description?: RouteHeadValue;
916
+ /** `<link rel="canonical">` — use an absolute URL. */
917
+ canonical?: RouteHeadValue;
918
+ /** Open Graph tags. */
919
+ og?: RouteHeadOpenGraph;
920
+ /**
921
+ * Any other `<meta>` entries (`{ name: "robots", content: "noindex" }`,
922
+ * `{ name: "twitter:card", content: "summary_large_image" }`). Identified by
923
+ * their `name` or `property`; an entry with neither is ignored.
924
+ */
925
+ meta?: Record<string, RouteHeadValue>[];
926
+ /** `<meta name="robots">`, e.g. `"noindex, nofollow"`. A `noindex` route is left out of the sitemap. */
927
+ robots?: RouteHeadValue;
928
+ /** Twitter card tags. */
929
+ twitter?: RouteHeadTwitter;
930
+ /** Translations of the page (`<link rel="alternate" hreflang>`), identified by `hreflang`. */
931
+ alternates?: RouteHeadAlternate[] | ((route: RouteContext) => RouteHeadAlternate[]);
932
+ /** Structured data: one or more JSON-LD objects, or a getter returning them. */
933
+ jsonLd?: JsonLd | JsonLd[] | ((route: RouteContext) => JsonLd | JsonLd[] | null | undefined);
934
+ }
935
+ /** Router-level head options: defaults for every route, plus the title template. */
936
+ interface RouterHeadOptions extends RouteHead {
937
+ /**
938
+ * Wraps every route title: `"%s · Studio"` or `(title) => \`${title} · Studio\``.
939
+ * Not applied to the default title itself.
940
+ */
941
+ titleTemplate?: string | ((title: string) => string);
942
+ }
943
+ /** One resolved `<head>` element, keyed by what it describes. */
944
+ interface ResolvedHeadTag {
945
+ /** Identity used to replace / remove the tag across navigations. */
946
+ key: string;
947
+ tag: "meta" | "link" | "script";
948
+ attrs: Record<string, string>;
949
+ /** Text content (the escaped JSON of a JSON-LD script). */
950
+ text?: string;
951
+ }
952
+ /** The head a route resolves to. */
953
+ interface ResolvedRouteHead {
954
+ title: string | null;
955
+ tags: ResolvedHeadTag[];
956
+ }
957
+ /**
958
+ * Serialize JSON-LD for a `<script type="application/ld+json">` body: `<`, `>`,
959
+ * `&`, U+2028 and U+2029 become `\u` escapes, which JSON parsers read back as
960
+ * the original characters, so the data can never close the script element.
961
+ *
962
+ * @param data One JSON-LD object or a list of them.
963
+ * @returns The escaped JSON text.
964
+ */
965
+ declare function serializeJsonLd(data: JsonLd | JsonLd[]): string;
966
+ /**
967
+ * A complete JSON-LD `<script>` element, for a backend template or a static
968
+ * page. The only non-executable script SibuJS emits: its type is
969
+ * `application/ld+json` and its body is {@link serializeJsonLd}'s output.
970
+ *
971
+ * @param data One JSON-LD object or a list of them.
972
+ * @returns `<script type="application/ld+json">…</script>`.
973
+ */
974
+ declare function jsonLdScript(data: JsonLd | JsonLd[]): string;
975
+ /**
976
+ * Resolve the head a committed route renders: the router defaults, then every
977
+ * matched record from the root down. Getters are invoked here, so calling this
978
+ * inside an effect subscribes to whatever they read.
979
+ *
980
+ * @param route The committed route.
981
+ * @param defaults `createRouter({ head })`, if any.
982
+ * @returns The title and the tags, after the shared head policy.
983
+ */
984
+ declare function resolveRouteHead(route: RouteContext, defaults?: RouterHeadOptions): ResolvedRouteHead;
985
+ /**
986
+ * Serialize a resolved head to HTML, for SSR or a static page. Each tag carries
987
+ * the `data-sibu-head` marker so the client router adopts it on start-up
988
+ * instead of duplicating it.
989
+ *
990
+ * @param head The result of {@link resolveRouteHead}.
991
+ * @returns `<title>` plus one element per tag.
992
+ */
993
+ declare function routeHeadToHTML(head: ResolvedRouteHead): string;
994
+
995
+ export { forward as $, type AsyncComponent as A, type RouterPlugin as B, type Component as C, type ScrollPosition as D, SibuRouter as E, Suspense as F, type Guard as G, __removeRouterPagehideHandler as H, addRoute as I, type JsonLd as J, KeepAliveRoute as K, type LazyComponent as L, afterEach as M, type NavigationFailure as N, Outlet as O, type Params as P, type QueryValues as Q, type RouterOptions as R, type ScrollBehavior as S, back as T, beforeEach as U, beforeResolve as V, buildURL as W, createMemoryRouter as X, createRouter as Y, destroyRouter as Z, __getNavigationEpoch as _, type RouteDef as a, getRouteInfo as a0, getRouteTransition as a1, go as a2, hasRoute as a3, jsonLdScript as a4, lazy as a5, navigate as a6, preloadRoute as a7, push as a8, removeRoute as a9, replace as aa, resolveRouteHead as ab, route as ac, routeHeadToHTML as ad, router as ae, routerPlugin as af, routerReady as ag, routerState as ah, serializeJsonLd as ai, setRouteTransition as aj, setRoutes as ak, type AsyncRoute as b, type ComponentRoute as c, type GuardResult as d, type LazyRoute as e, type NavigationFailureReason as f, type NavigationGuard as g, type NavigationNext as h, type NavigationQuery as i, type NavigationResult as j, type NavigationTarget as k, type RedirectRoute as l, type ResolvedHeadTag as m, type ResolvedRouteHead as n, Route as o, type RouteBase as p, type RouteContext as q, type RouteHead as r, type RouteHeadAlternate as s, type RouteHeadOpenGraph as t, type RouteHeadTwitter as u, type RouteHeadValue as v, type RouteMeta as w, type RouteTransitionOptions as x, type RouterHeadOptions as y, RouterLink as z };