@moku-labs/web 1.8.0 → 1.8.1

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.
@@ -1157,6 +1157,19 @@ type Api$1 = {
1157
1157
  url: string;
1158
1158
  locale?: string;
1159
1159
  }): string;
1160
+ /**
1161
+ * Resolve the FINAL document title for a route's head config — the same value `render`
1162
+ * emits in its `<title>` element (`titleTemplate` applied; a route-pinned `title`-keyed
1163
+ * element wins). Used by `spa` to sync `document.title` on client DATA-path navigation.
1164
+ *
1165
+ * @param head - The route's head config (may be `undefined` for head-less routes).
1166
+ * @returns The final document title string.
1167
+ * @example
1168
+ * ```ts
1169
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
1170
+ * ```
1171
+ */
1172
+ composeTitle(head: HeadConfig | undefined): string;
1160
1173
  };
1161
1174
  declare namespace types_d_exports$6 {
1162
1175
  export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
package/dist/browser.mjs CHANGED
@@ -2579,6 +2579,26 @@ function composeHead(input) {
2579
2579
  }), ...head.elements ?? []]);
2580
2580
  }
2581
2581
  /**
2582
+ * Resolve the FINAL document title for a route's head config — the same value
2583
+ * {@link composeHead} emits in its `<title>` element. A route-supplied `title`-keyed
2584
+ * element wins the keyed last-wins de-dupe over the templated base title (how a route
2585
+ * pins a bare title past `titleTemplate`), so it must win here too; otherwise the
2586
+ * template is applied to `head.title ?? site.name()`. Reused by `spa` for the client
2587
+ * DATA-path `document.title` sync, so client-side navigation matches the SSG output.
2588
+ *
2589
+ * @param head - The route's head config (may be `undefined` for head-less routes).
2590
+ * @param defaults - The normalized head defaults (provides `titleTemplate`).
2591
+ * @param site - The site slice (title fallback).
2592
+ * @returns The final document title string.
2593
+ * @example composeTitle({ title: "Page 2" }, defaults, site) // "Page 2 — Site"
2594
+ */
2595
+ function composeTitle(head, defaults, site) {
2596
+ const config = head ?? {};
2597
+ const pinned = config.elements?.findLast((element) => element.key === "title");
2598
+ if (pinned?.children !== void 0) return pinned.children;
2599
+ return applyTemplate(config.title ?? site.name(), defaults.titleTemplate);
2600
+ }
2601
+ /**
2582
2602
  * Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
2583
2603
  * page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
2584
2604
  * configured — so apps that opt out keep a bare redirect (no behavior change). The site
@@ -2748,6 +2768,21 @@ function createApi$1(ctx) {
2748
2768
  url: site.canonical(input.url),
2749
2769
  ...ogLocale === void 0 ? {} : { ogLocale }
2750
2770
  }));
2771
+ },
2772
+ /**
2773
+ * Resolve the FINAL document title for a route's head config — the same value `render`
2774
+ * emits in its `<title>` element. Pulled by `spa` on the client DATA path so a
2775
+ * client-side navigation's `document.title` matches the SSG output.
2776
+ *
2777
+ * @param head - The route's head config (may be `undefined` for head-less routes).
2778
+ * @returns The final document title string.
2779
+ * @example
2780
+ * ```ts
2781
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
2782
+ * ```
2783
+ */
2784
+ composeTitle(head) {
2785
+ return composeTitle(head, readDefaults(ctx.state), ctx.require(sitePlugin));
2751
2786
  }
2752
2787
  };
2753
2788
  }
@@ -4020,16 +4055,19 @@ function currentLocationUrl() {
4020
4055
  /**
4021
4056
  * Apply the matched route's `head` config to the live document (minimal client
4022
4057
  * head-sync for the DATA path: title only — the full meta sync runs on the
4023
- * HTML-over-fetch path from the fetched `<head>`).
4058
+ * HTML-over-fetch path from the fetched `<head>`). The title is resolved through
4059
+ * `head.composeTitle` — the SAME composition `render` uses (`titleTemplate` applied;
4060
+ * a route-pinned `title` element wins) — so a client-side navigation's
4061
+ * `document.title` matches the SSG output instead of the raw route title.
4024
4062
  *
4063
+ * @param head - The head plugin API (resolves the final templated title).
4025
4064
  * @param route - The matched route definition.
4026
4065
  * @param routeContext - The render context (params/data/locale).
4027
4066
  * @example
4028
- * syncDataHead(hit.route, { params, data, locale });
4067
+ * syncDataHead(deps.head, hit.route, { params, data, locale });
4029
4068
  */
4030
- function syncDataHead(route, routeContext) {
4031
- const title = route._handlers.head?.(routeContext)?.title;
4032
- if (title !== void 0 && title !== "") document.title = title;
4069
+ function syncDataHead(head, route, routeContext) {
4070
+ document.title = head.composeTitle(route._handlers.head?.(routeContext));
4033
4071
  }
4034
4072
  /**
4035
4073
  * Builds the single shared SPA kernel — a pure factory over state/config/emit.
@@ -4185,7 +4223,7 @@ function createSpaKernel(state, config, emit, deps) {
4185
4223
  handleStart(pathname);
4186
4224
  const { renderVNode } = await import("./render-BNe0s7fr.mjs");
4187
4225
  if (signal?.aborted) return;
4188
- syncDataHead(route, routeContext);
4226
+ syncDataHead(deps.head, route, routeContext);
4189
4227
  unmountPageSpecific(state, emit);
4190
4228
  /**
4191
4229
  * Render the VNode into the region and re-mount its islands in one paint — the
package/dist/index.cjs CHANGED
@@ -3169,6 +3169,26 @@ function composeHead(input) {
3169
3169
  }), ...head.elements ?? []]);
3170
3170
  }
3171
3171
  /**
3172
+ * Resolve the FINAL document title for a route's head config — the same value
3173
+ * {@link composeHead} emits in its `<title>` element. A route-supplied `title`-keyed
3174
+ * element wins the keyed last-wins de-dupe over the templated base title (how a route
3175
+ * pins a bare title past `titleTemplate`), so it must win here too; otherwise the
3176
+ * template is applied to `head.title ?? site.name()`. Reused by `spa` for the client
3177
+ * DATA-path `document.title` sync, so client-side navigation matches the SSG output.
3178
+ *
3179
+ * @param head - The route's head config (may be `undefined` for head-less routes).
3180
+ * @param defaults - The normalized head defaults (provides `titleTemplate`).
3181
+ * @param site - The site slice (title fallback).
3182
+ * @returns The final document title string.
3183
+ * @example composeTitle({ title: "Page 2" }, defaults, site) // "Page 2 — Site"
3184
+ */
3185
+ function composeTitle(head, defaults, site) {
3186
+ const config = head ?? {};
3187
+ const pinned = config.elements?.findLast((element) => element.key === "title");
3188
+ if (pinned?.children !== void 0) return pinned.children;
3189
+ return applyTemplate(config.title ?? site.name(), defaults.titleTemplate);
3190
+ }
3191
+ /**
3172
3192
  * Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
3173
3193
  * page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
3174
3194
  * configured — so apps that opt out keep a bare redirect (no behavior change). The site
@@ -3338,6 +3358,21 @@ function createApi$4(ctx) {
3338
3358
  url: site.canonical(input.url),
3339
3359
  ...ogLocale === void 0 ? {} : { ogLocale }
3340
3360
  }));
3361
+ },
3362
+ /**
3363
+ * Resolve the FINAL document title for a route's head config — the same value `render`
3364
+ * emits in its `<title>` element. Pulled by `spa` on the client DATA path so a
3365
+ * client-side navigation's `document.title` matches the SSG output.
3366
+ *
3367
+ * @param head - The route's head config (may be `undefined` for head-less routes).
3368
+ * @returns The final document title string.
3369
+ * @example
3370
+ * ```ts
3371
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
3372
+ * ```
3373
+ */
3374
+ composeTitle(head) {
3375
+ return composeTitle(head, readDefaults(ctx.state), ctx.require(sitePlugin));
3341
3376
  }
3342
3377
  };
3343
3378
  }
@@ -10868,16 +10903,19 @@ function currentLocationUrl() {
10868
10903
  /**
10869
10904
  * Apply the matched route's `head` config to the live document (minimal client
10870
10905
  * head-sync for the DATA path: title only — the full meta sync runs on the
10871
- * HTML-over-fetch path from the fetched `<head>`).
10906
+ * HTML-over-fetch path from the fetched `<head>`). The title is resolved through
10907
+ * `head.composeTitle` — the SAME composition `render` uses (`titleTemplate` applied;
10908
+ * a route-pinned `title` element wins) — so a client-side navigation's
10909
+ * `document.title` matches the SSG output instead of the raw route title.
10872
10910
  *
10911
+ * @param head - The head plugin API (resolves the final templated title).
10873
10912
  * @param route - The matched route definition.
10874
10913
  * @param routeContext - The render context (params/data/locale).
10875
10914
  * @example
10876
- * syncDataHead(hit.route, { params, data, locale });
10915
+ * syncDataHead(deps.head, hit.route, { params, data, locale });
10877
10916
  */
10878
- function syncDataHead(route, routeContext) {
10879
- const title = route._handlers.head?.(routeContext)?.title;
10880
- if (title !== void 0 && title !== "") document.title = title;
10917
+ function syncDataHead(head, route, routeContext) {
10918
+ document.title = head.composeTitle(route._handlers.head?.(routeContext));
10881
10919
  }
10882
10920
  /**
10883
10921
  * Builds the single shared SPA kernel — a pure factory over state/config/emit.
@@ -11033,7 +11071,7 @@ function createSpaKernel(state, config, emit, deps) {
11033
11071
  handleStart(pathname);
11034
11072
  const { renderVNode } = await Promise.resolve().then(() => require("./render-DLZEOe4M.cjs"));
11035
11073
  if (signal?.aborted) return;
11036
- syncDataHead(route, routeContext);
11074
+ syncDataHead(deps.head, route, routeContext);
11037
11075
  unmountPageSpecific(state, emit);
11038
11076
  /**
11039
11077
  * Render the VNode into the region and re-mount its islands in one paint — the
package/dist/index.d.cts CHANGED
@@ -1157,6 +1157,19 @@ type Api$4 = {
1157
1157
  url: string;
1158
1158
  locale?: string;
1159
1159
  }): string;
1160
+ /**
1161
+ * Resolve the FINAL document title for a route's head config — the same value `render`
1162
+ * emits in its `<title>` element (`titleTemplate` applied; a route-pinned `title`-keyed
1163
+ * element wins). Used by `spa` to sync `document.title` on client DATA-path navigation.
1164
+ *
1165
+ * @param head - The route's head config (may be `undefined` for head-less routes).
1166
+ * @returns The final document title string.
1167
+ * @example
1168
+ * ```ts
1169
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
1170
+ * ```
1171
+ */
1172
+ composeTitle(head: HeadConfig | undefined): string;
1160
1173
  };
1161
1174
  declare namespace types_d_exports$9 {
1162
1175
  export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi$1 as ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
package/dist/index.d.mts CHANGED
@@ -1157,6 +1157,19 @@ type Api$4 = {
1157
1157
  url: string;
1158
1158
  locale?: string;
1159
1159
  }): string;
1160
+ /**
1161
+ * Resolve the FINAL document title for a route's head config — the same value `render`
1162
+ * emits in its `<title>` element (`titleTemplate` applied; a route-pinned `title`-keyed
1163
+ * element wins). Used by `spa` to sync `document.title` on client DATA-path navigation.
1164
+ *
1165
+ * @param head - The route's head config (may be `undefined` for head-less routes).
1166
+ * @returns The final document title string.
1167
+ * @example
1168
+ * ```ts
1169
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
1170
+ * ```
1171
+ */
1172
+ composeTitle(head: HeadConfig | undefined): string;
1160
1173
  };
1161
1174
  declare namespace types_d_exports$9 {
1162
1175
  export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi$1 as ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
package/dist/index.mjs CHANGED
@@ -3156,6 +3156,26 @@ function composeHead(input) {
3156
3156
  }), ...head.elements ?? []]);
3157
3157
  }
3158
3158
  /**
3159
+ * Resolve the FINAL document title for a route's head config — the same value
3160
+ * {@link composeHead} emits in its `<title>` element. A route-supplied `title`-keyed
3161
+ * element wins the keyed last-wins de-dupe over the templated base title (how a route
3162
+ * pins a bare title past `titleTemplate`), so it must win here too; otherwise the
3163
+ * template is applied to `head.title ?? site.name()`. Reused by `spa` for the client
3164
+ * DATA-path `document.title` sync, so client-side navigation matches the SSG output.
3165
+ *
3166
+ * @param head - The route's head config (may be `undefined` for head-less routes).
3167
+ * @param defaults - The normalized head defaults (provides `titleTemplate`).
3168
+ * @param site - The site slice (title fallback).
3169
+ * @returns The final document title string.
3170
+ * @example composeTitle({ title: "Page 2" }, defaults, site) // "Page 2 — Site"
3171
+ */
3172
+ function composeTitle(head, defaults, site) {
3173
+ const config = head ?? {};
3174
+ const pinned = config.elements?.findLast((element) => element.key === "title");
3175
+ if (pinned?.children !== void 0) return pinned.children;
3176
+ return applyTemplate(config.title ?? site.name(), defaults.titleTemplate);
3177
+ }
3178
+ /**
3159
3179
  * Compose the SITE-LEVEL Open Graph / Twitter block for a bare-path redirect or landing
3160
3180
  * page that has no per-route head of its own. Returns `[]` UNLESS a `defaultOgImage` is
3161
3181
  * configured — so apps that opt out keep a bare redirect (no behavior change). The site
@@ -3325,6 +3345,21 @@ function createApi$4(ctx) {
3325
3345
  url: site.canonical(input.url),
3326
3346
  ...ogLocale === void 0 ? {} : { ogLocale }
3327
3347
  }));
3348
+ },
3349
+ /**
3350
+ * Resolve the FINAL document title for a route's head config — the same value `render`
3351
+ * emits in its `<title>` element. Pulled by `spa` on the client DATA path so a
3352
+ * client-side navigation's `document.title` matches the SSG output.
3353
+ *
3354
+ * @param head - The route's head config (may be `undefined` for head-less routes).
3355
+ * @returns The final document title string.
3356
+ * @example
3357
+ * ```ts
3358
+ * api.composeTitle({ title: "Page 2" }); // "Page 2 — Site"
3359
+ * ```
3360
+ */
3361
+ composeTitle(head) {
3362
+ return composeTitle(head, readDefaults(ctx.state), ctx.require(sitePlugin));
3328
3363
  }
3329
3364
  };
3330
3365
  }
@@ -10855,16 +10890,19 @@ function currentLocationUrl() {
10855
10890
  /**
10856
10891
  * Apply the matched route's `head` config to the live document (minimal client
10857
10892
  * head-sync for the DATA path: title only — the full meta sync runs on the
10858
- * HTML-over-fetch path from the fetched `<head>`).
10893
+ * HTML-over-fetch path from the fetched `<head>`). The title is resolved through
10894
+ * `head.composeTitle` — the SAME composition `render` uses (`titleTemplate` applied;
10895
+ * a route-pinned `title` element wins) — so a client-side navigation's
10896
+ * `document.title` matches the SSG output instead of the raw route title.
10859
10897
  *
10898
+ * @param head - The head plugin API (resolves the final templated title).
10860
10899
  * @param route - The matched route definition.
10861
10900
  * @param routeContext - The render context (params/data/locale).
10862
10901
  * @example
10863
- * syncDataHead(hit.route, { params, data, locale });
10902
+ * syncDataHead(deps.head, hit.route, { params, data, locale });
10864
10903
  */
10865
- function syncDataHead(route, routeContext) {
10866
- const title = route._handlers.head?.(routeContext)?.title;
10867
- if (title !== void 0 && title !== "") document.title = title;
10904
+ function syncDataHead(head, route, routeContext) {
10905
+ document.title = head.composeTitle(route._handlers.head?.(routeContext));
10868
10906
  }
10869
10907
  /**
10870
10908
  * Builds the single shared SPA kernel — a pure factory over state/config/emit.
@@ -11020,7 +11058,7 @@ function createSpaKernel(state, config, emit, deps) {
11020
11058
  handleStart(pathname);
11021
11059
  const { renderVNode } = await import("./render-BNe0s7fr.mjs");
11022
11060
  if (signal?.aborted) return;
11023
- syncDataHead(route, routeContext);
11061
+ syncDataHead(deps.head, route, routeContext);
11024
11062
  unmountPageSpecific(state, emit);
11025
11063
  /**
11026
11064
  * Render the VNode into the region and re-mount its islands in one paint — the
package/package.json CHANGED
@@ -118,5 +118,5 @@
118
118
  "test:build-e2e": "bun test src/plugins/build/__tests__/e2e/",
119
119
  "test:coverage": "vitest run --project unit --project integration --coverage"
120
120
  },
121
- "version": "1.8.0"
121
+ "version": "1.8.1"
122
122
  }