@moku-labs/web 1.7.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