@moku-labs/web 0.4.0 → 0.4.2

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/index.cjs CHANGED
@@ -2534,13 +2534,20 @@ function route(pattern) {
2534
2534
  return set("load", loader);
2535
2535
  },
2536
2536
  /**
2537
- * Attach a layout wrapper component.
2537
+ * Attach a ctx-aware layout wrapper that frames the page in persistent chrome.
2538
+ * The wrapper receives the route's `LayoutContext` (render context + `.meta()`
2539
+ * bag) and the page children. Applied in the SSG render path only — client
2540
+ * navigation keeps the chrome and swaps just the inner region.
2538
2541
  *
2539
- * @param component - The layout component.
2542
+ * @param component - The layout component `(ctx, children) => VNode`.
2540
2543
  * @returns The same builder for chaining.
2541
2544
  * @example
2542
2545
  * ```ts
2543
- * route("/").layout((children) => children);
2546
+ * route("/")
2547
+ * .meta({ activeTab: "home" })
2548
+ * .layout((ctx, children) => (
2549
+ * <Shell locale={ctx.locale} active={ctx.meta.activeTab}>{children}</Shell>
2550
+ * ));
2544
2551
  * ```
2545
2552
  */
2546
2553
  layout(component) {
@@ -4431,7 +4438,12 @@ async function renderInstance(ctx, instance, shell) {
4431
4438
  const headHtml = ctx.require(headPlugin).render(resolved, data);
4432
4439
  const buildIdMeta = `<meta name="build-id" content="${ctx.state.runId ?? ""}">`;
4433
4440
  const vnode = definition._handlers.render?.(routeContext);
4434
- const bodyHtml = vnode ? (0, preact_render_to_string.renderToString)(vnode) : "";
4441
+ const layoutCtx = {
4442
+ ...routeContext,
4443
+ meta: definition._meta
4444
+ };
4445
+ const page = vnode && definition._handlers.layout ? definition._handlers.layout(layoutCtx, vnode) : vnode;
4446
+ const bodyHtml = page ? (0, preact_render_to_string.renderToString)(page) : "";
4435
4447
  const parts = {
4436
4448
  head: `${headHtml}${buildIdMeta}`,
4437
4449
  body: bodyHtml,
package/dist/index.d.cts CHANGED
@@ -542,7 +542,7 @@ type Api$5 = {
542
542
  t(locale: string, key: string): string;
543
543
  };
544
544
  declare namespace types_d_exports$7 {
545
- export { Api$4 as Api, ClientRoute, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
545
+ export { Api$4 as Api, ClientRoute, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, LayoutContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
546
546
  }
547
547
  /**
548
548
  * Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
@@ -582,6 +582,22 @@ interface RouteContext<S extends RouteState> {
582
582
  /** Active locale for this render. */
583
583
  readonly locale: string;
584
584
  }
585
+ /**
586
+ * Context handed to a route's `.layout()` wrapper: the render-time
587
+ * {@link RouteContext} plus the route's `.meta()` bag, so persistent chrome (e.g. a
588
+ * TopBar/TabNav) can read `locale` and `meta.activeTab`. Distinct from
589
+ * `RouteContext` because the layout is the only handler that needs `meta`; keeping
590
+ * it on its own type leaves `.render()`/`.head()` contexts unchanged.
591
+ *
592
+ * @remarks
593
+ * The layout is applied in the SSG render path ONLY. On client (SPA) navigation the
594
+ * chrome is persistent and the layout is intentionally NOT re-applied — only the
595
+ * inner swap region is replaced. See `build`'s pages phase and `spa`'s kernel.
596
+ */
597
+ interface LayoutContext<S extends RouteState> extends RouteContext<S> {
598
+ /** The route's `.meta()` bag (e.g. `{ activeTab: "home" }`). */
599
+ readonly meta: Record<string, unknown>;
600
+ }
585
601
  /** Head metadata produced by a route's `.head()` handler. */
586
602
  interface HeadConfig$1 {
587
603
  /** Document title. */
@@ -604,8 +620,14 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
604
620
  readonly params: S["params"];
605
621
  readonly data: Awaited<D>;
606
622
  }>;
607
- /** Attach a layout wrapper component. */
608
- layout(component: (children: ComponentChildren) => VNode): RouteBuilder<S>;
623
+ /**
624
+ * Attach a ctx-aware layout wrapper that frames this route's rendered page in
625
+ * persistent chrome. Receives the route's {@link LayoutContext} (render context +
626
+ * `meta`) and the page `children`. Applied in the SSG render path ONLY — on client
627
+ * navigation the chrome persists and only the inner swap region is replaced, so the
628
+ * layout is not re-run.
629
+ */
630
+ layout(component: (ctx: LayoutContext<S>, children: ComponentChildren) => VNode): RouteBuilder<S>;
609
631
  /** Attach the page render handler. */
610
632
  render(handler: (ctx: RouteContext<S>) => VNode): RouteBuilder<S>;
611
633
  /**
@@ -635,8 +657,8 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
635
657
  interface RouteHandlers {
636
658
  /** Data loader. */
637
659
  readonly load?: (params: Record<string, string>, locale: string) => unknown;
638
- /** Layout wrapper. */
639
- readonly layout?: (children: ComponentChildren) => VNode;
660
+ /** Layout wrapper (ctx-aware): frames the page in persistent chrome. SSG-only. */
661
+ readonly layout?: (ctx: LayoutContext<RouteState>, children: ComponentChildren) => VNode;
640
662
  /** Page renderer. */
641
663
  readonly render?: (ctx: RouteContext<RouteState>) => VNode;
642
664
  /** Client-side validation gate: `unknown` (fetched JSON) → the route's data type, or throw. */
package/dist/index.d.mts CHANGED
@@ -542,7 +542,7 @@ type Api$5 = {
542
542
  t(locale: string, key: string): string;
543
543
  };
544
544
  declare namespace types_d_exports$7 {
545
- export { Api$4 as Api, ClientRoute, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
545
+ export { Api$4 as Api, ClientRoute, CompileInput, CompiledRoute, Config$4 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, LayoutContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$4 as State, TypedRoute };
546
546
  }
547
547
  /**
548
548
  * Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
@@ -582,6 +582,22 @@ interface RouteContext<S extends RouteState> {
582
582
  /** Active locale for this render. */
583
583
  readonly locale: string;
584
584
  }
585
+ /**
586
+ * Context handed to a route's `.layout()` wrapper: the render-time
587
+ * {@link RouteContext} plus the route's `.meta()` bag, so persistent chrome (e.g. a
588
+ * TopBar/TabNav) can read `locale` and `meta.activeTab`. Distinct from
589
+ * `RouteContext` because the layout is the only handler that needs `meta`; keeping
590
+ * it on its own type leaves `.render()`/`.head()` contexts unchanged.
591
+ *
592
+ * @remarks
593
+ * The layout is applied in the SSG render path ONLY. On client (SPA) navigation the
594
+ * chrome is persistent and the layout is intentionally NOT re-applied — only the
595
+ * inner swap region is replaced. See `build`'s pages phase and `spa`'s kernel.
596
+ */
597
+ interface LayoutContext<S extends RouteState> extends RouteContext<S> {
598
+ /** The route's `.meta()` bag (e.g. `{ activeTab: "home" }`). */
599
+ readonly meta: Record<string, unknown>;
600
+ }
585
601
  /** Head metadata produced by a route's `.head()` handler. */
586
602
  interface HeadConfig$1 {
587
603
  /** Document title. */
@@ -604,8 +620,14 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
604
620
  readonly params: S["params"];
605
621
  readonly data: Awaited<D>;
606
622
  }>;
607
- /** Attach a layout wrapper component. */
608
- layout(component: (children: ComponentChildren) => VNode): RouteBuilder<S>;
623
+ /**
624
+ * Attach a ctx-aware layout wrapper that frames this route's rendered page in
625
+ * persistent chrome. Receives the route's {@link LayoutContext} (render context +
626
+ * `meta`) and the page `children`. Applied in the SSG render path ONLY — on client
627
+ * navigation the chrome persists and only the inner swap region is replaced, so the
628
+ * layout is not re-run.
629
+ */
630
+ layout(component: (ctx: LayoutContext<S>, children: ComponentChildren) => VNode): RouteBuilder<S>;
609
631
  /** Attach the page render handler. */
610
632
  render(handler: (ctx: RouteContext<S>) => VNode): RouteBuilder<S>;
611
633
  /**
@@ -635,8 +657,8 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
635
657
  interface RouteHandlers {
636
658
  /** Data loader. */
637
659
  readonly load?: (params: Record<string, string>, locale: string) => unknown;
638
- /** Layout wrapper. */
639
- readonly layout?: (children: ComponentChildren) => VNode;
660
+ /** Layout wrapper (ctx-aware): frames the page in persistent chrome. SSG-only. */
661
+ readonly layout?: (ctx: LayoutContext<RouteState>, children: ComponentChildren) => VNode;
640
662
  /** Page renderer. */
641
663
  readonly render?: (ctx: RouteContext<RouteState>) => VNode;
642
664
  /** Client-side validation gate: `unknown` (fetched JSON) → the route's data type, or throw. */
package/dist/index.mjs CHANGED
@@ -2521,13 +2521,20 @@ function route(pattern) {
2521
2521
  return set("load", loader);
2522
2522
  },
2523
2523
  /**
2524
- * Attach a layout wrapper component.
2524
+ * Attach a ctx-aware layout wrapper that frames the page in persistent chrome.
2525
+ * The wrapper receives the route's `LayoutContext` (render context + `.meta()`
2526
+ * bag) and the page children. Applied in the SSG render path only — client
2527
+ * navigation keeps the chrome and swaps just the inner region.
2525
2528
  *
2526
- * @param component - The layout component.
2529
+ * @param component - The layout component `(ctx, children) => VNode`.
2527
2530
  * @returns The same builder for chaining.
2528
2531
  * @example
2529
2532
  * ```ts
2530
- * route("/").layout((children) => children);
2533
+ * route("/")
2534
+ * .meta({ activeTab: "home" })
2535
+ * .layout((ctx, children) => (
2536
+ * <Shell locale={ctx.locale} active={ctx.meta.activeTab}>{children}</Shell>
2537
+ * ));
2531
2538
  * ```
2532
2539
  */
2533
2540
  layout(component) {
@@ -4418,7 +4425,12 @@ async function renderInstance(ctx, instance, shell) {
4418
4425
  const headHtml = ctx.require(headPlugin).render(resolved, data);
4419
4426
  const buildIdMeta = `<meta name="build-id" content="${ctx.state.runId ?? ""}">`;
4420
4427
  const vnode = definition._handlers.render?.(routeContext);
4421
- const bodyHtml = vnode ? renderToString(vnode) : "";
4428
+ const layoutCtx = {
4429
+ ...routeContext,
4430
+ meta: definition._meta
4431
+ };
4432
+ const page = vnode && definition._handlers.layout ? definition._handlers.layout(layoutCtx, vnode) : vnode;
4433
+ const bodyHtml = page ? renderToString(page) : "";
4422
4434
  const parts = {
4423
4435
  head: `${headHtml}${buildIdMeta}`,
4424
4436
  body: bodyHtml,
package/package.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "name": "@moku-labs/web",
3
- "version": "0.4.0",
4
3
  "description": "Content static-site generator + SPA web framework for TypeScript, built on @moku-labs/core.",
5
4
  "author": "Oleksandr Kucherenko",
6
5
  "license": "MIT",
@@ -105,5 +104,6 @@
105
104
  "test:unit": "vitest run --project unit",
106
105
  "test:integration": "vitest run --project integration",
107
106
  "test:coverage": "vitest run --project unit --project integration --coverage"
108
- }
107
+ },
108
+ "version": "0.4.2"
109
109
  }