@kimesh/router-runtime 0.2.23 → 0.2.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.
@@ -134,6 +134,63 @@ interface FullContext extends KimeshContext {
134
134
  * Injection key for Kimesh context
135
135
  */
136
136
  declare const KIMESH_CONTEXT_KEY: InjectionKey<FullContext>;
137
+ /**
138
+ * Context passed to beforeLoad function
139
+ */
140
+ interface BeforeLoadContextFull<TParams = Record<string, string>> {
141
+ /** Route params extracted from URL */
142
+ params: TParams;
143
+ /** Validated search/query params */
144
+ search: Record<string, unknown>;
145
+ /** Current app context (built-in + user-defined) */
146
+ context: FullContext;
147
+ /** AbortSignal for cancellation */
148
+ signal: AbortSignal;
149
+ }
150
+ /**
151
+ * Before load function type
152
+ * Used to augment context for child routes
153
+ */
154
+ type BeforeLoadFn<TParams = Record<string, string>, TReturn extends Record<string, unknown> = Record<string, unknown>> = (ctx: BeforeLoadContextFull<TParams>) => TReturn | Promise<TReturn>;
155
+ /**
156
+ * Pending component configuration for routes
157
+ */
158
+ interface PendingConfig {
159
+ /**
160
+ * Component to show while route is loading
161
+ * @example
162
+ * pendingComponent: () => import('./DashboardSkeleton.vue')
163
+ */
164
+ pendingComponent?: () => Promise<{
165
+ default: Component;
166
+ }>;
167
+ /**
168
+ * Milliseconds to wait before showing pending component
169
+ * Prevents flash of loading state for fast loads
170
+ * @default 200
171
+ */
172
+ pendingMs?: number;
173
+ /**
174
+ * Minimum milliseconds to show pending component once visible
175
+ * Prevents jarring quick flash of loading state
176
+ * @default 500
177
+ */
178
+ pendingMinMs?: number;
179
+ }
180
+ /**
181
+ * Context accumulated from parent routes' beforeLoad functions
182
+ * This is merged into FullContext during navigation
183
+ */
184
+ interface RouteContext {
185
+ /** Marker to identify route-accumulated context */
186
+ __routeContext?: true;
187
+ /** Additional properties from beforeLoad functions */
188
+ [key: string]: unknown;
189
+ }
190
+ /**
191
+ * Augmented context with route-level additions
192
+ */
193
+ type AugmentedContext<TBeforeLoad extends Record<string, unknown> = Record<string, unknown>> = FullContext & TBeforeLoad;
137
194
  /**
138
195
  * Param value types for route parameters
139
196
  */
@@ -223,13 +280,13 @@ interface KimeshApp {
223
280
  /**
224
281
  * Loader context passed to loader functions
225
282
  */
226
- interface LoaderContext<TParams = Record<string, string>, TSearch = Record<string, unknown>> {
283
+ interface LoaderContext<TParams = Record<string, string>, TSearch = Record<string, unknown>, TRouteContext extends Record<string, unknown> = {}> {
227
284
  /** Route params (e.g., { postId: '123' }) */
228
285
  params: TParams;
229
286
  /** Validated search/query params */
230
287
  search: TSearch;
231
- /** App context (built-in + user-defined) */
232
- context: FullContext;
288
+ /** App context (built-in + user-defined + route-accumulated) */
289
+ context: FullContext & TRouteContext;
233
290
  /** AbortSignal for cancellation */
234
291
  signal: AbortSignal;
235
292
  }
@@ -349,7 +406,7 @@ type InlineRouteMiddleware<TParams = Record<string, string>> = (to: RouteLocatio
349
406
  /**
350
407
  * File route options
351
408
  */
352
- interface FileRouteOptions<TMeta = unknown, TParams = Record<string, string>, TSearch = Record<string, unknown>, TLoaderData = unknown> {
409
+ interface FileRouteOptions<TMeta = unknown, TParams = Record<string, string>, TSearch = Record<string, unknown>, TLoaderData = unknown, TBeforeLoadReturn extends Record<string, unknown> = Record<string, unknown>> extends PendingConfig {
353
410
  /** Route meta */
354
411
  meta?: TMeta;
355
412
  /** Loader function - runs before navigation completes */
@@ -379,7 +436,7 @@ interface FileRouteOptions<TMeta = unknown, TParams = Record<string, string>, TS
379
436
  */
380
437
  middleware?: string | InlineRouteMiddleware<TParams> | Array<string | InlineRouteMiddleware<TParams>>;
381
438
  /** Before load hook (runs after middleware) */
382
- beforeLoad?: (ctx: BeforeLoadContext) => Promise<void> | void;
439
+ beforeLoad?: BeforeLoadFn<TParams, TBeforeLoadReturn>;
383
440
  /**
384
441
  * Route-level head configuration
385
442
  *
@@ -447,17 +504,19 @@ interface BeforeLoadContext {
447
504
  /**
448
505
  * Route definition returned by createFileRoute
449
506
  */
450
- interface RouteDefinition<TPath extends string = string, TMeta = unknown, TParams = Record<string, string>, TSearch = Record<string, unknown>, TLoaderData = unknown> {
507
+ interface RouteDefinition<TPath extends string = string, TMeta = unknown, TParams = Record<string, string>, TSearch = Record<string, unknown>, TLoaderData = unknown> extends PendingConfig {
451
508
  path: TPath;
452
509
  meta?: TMeta;
453
510
  loader?: LoaderFn<TParams, TSearch, TLoaderData>;
454
511
  validateSearch?: SearchSchema<TSearch>;
455
512
  middleware?: string | InlineRouteMiddleware<TParams> | Array<string | InlineRouteMiddleware<TParams>>;
456
- beforeLoad?: (ctx: BeforeLoadContext) => Promise<void> | void;
513
+ beforeLoad?: BeforeLoadFn<TParams>;
457
514
  head?: RouteHeadFn<TParams, TLoaderData> | RouteHeadConfig;
458
515
  transition?: boolean | TransitionProps;
459
516
  viewTransition?: boolean | 'always';
460
517
  keepalive?: boolean | KeepAliveProps;
518
+ /** Internal: marks this as a layout route */
519
+ __isLayout?: boolean;
461
520
  }
462
521
  declare module 'vue-router' {
463
522
  interface RouteMeta {
@@ -467,6 +526,20 @@ declare module 'vue-router' {
467
526
  __kimeshError?: unknown;
468
527
  /** Source layer name for layer-aware features */
469
528
  __kimeshLayer?: string;
529
+ /** Marks this route as a layout */
530
+ __kimeshIsLayout?: boolean;
531
+ /** Has pending component */
532
+ __kimeshHasPending?: boolean;
533
+ /** Pending delay in ms */
534
+ __kimeshPendingMs?: number;
535
+ /** Minimum pending duration in ms */
536
+ __kimeshPendingMinMs?: number;
537
+ /** Has beforeLoad function */
538
+ __kimeshHasBeforeLoad?: boolean;
539
+ /** Has head configuration */
540
+ __kimeshHasHead?: boolean;
541
+ /** Head is a function (vs static) */
542
+ __kimeshHeadIsFunction?: boolean;
470
543
  }
471
544
  }
472
545
  //#endregion
@@ -646,6 +719,52 @@ declare const queryPlugin: KimeshRuntimePlugin<QueryPluginOptions>;
646
719
  */
647
720
  declare function getCorePlugins(): KimeshRuntimePlugin<Record<string, unknown>>[];
648
721
  //#endregion
722
+ //#region src/plugins/head.d.ts
723
+ /**
724
+ * Head plugin options
725
+ */
726
+ interface HeadPluginOptions {
727
+ /** Default title when no route title is set */
728
+ defaultTitle?: string;
729
+ /** Global title template (can be overridden by routes) */
730
+ titleTemplate?: string | ((title: string) => string);
731
+ /** Whether to merge layout head with page head */
732
+ mergeLayoutHead?: boolean;
733
+ }
734
+ /**
735
+ * Merge two head configurations
736
+ * Page head takes priority over layout head
737
+ */
738
+ declare function mergeHeadConfigs(layoutHead: RouteHeadConfig | undefined, pageHead: RouteHeadConfig | undefined): RouteHeadConfig;
739
+ /**
740
+ * Apply title template to title
741
+ */
742
+ declare function applyTitleTemplate(title: string | undefined, template: string | ((title: string) => string) | undefined, defaultTitle?: string): string;
743
+ /**
744
+ * Collect head configurations from matched routes
745
+ */
746
+ declare function collectRouteHeads(route: RouteLocationNormalizedLoaded, kimeshApp: KimeshAppContext): RouteHeadConfig[];
747
+ /**
748
+ * Merge all route heads into final config
749
+ */
750
+ declare function mergeAllRouteHeads(heads: RouteHeadConfig[]): RouteHeadConfig;
751
+ /**
752
+ * Apply head config to document (native DOM)
753
+ */
754
+ declare function applyHeadConfigNative(config: RouteHeadConfig, options?: HeadPluginOptions): void;
755
+ /**
756
+ * Create the head plugin
757
+ */
758
+ declare function createHeadPlugin(options?: HeadPluginOptions): {
759
+ install(app: App, {
760
+ router,
761
+ kimeshApp
762
+ }: {
763
+ router: Router;
764
+ kimeshApp: KimeshAppContext;
765
+ }): void;
766
+ };
767
+ //#endregion
649
768
  //#region src/components/KmOutlet.d.ts
650
769
  /**
651
770
  * KmOutlet - Router view wrapper component
@@ -896,6 +1015,8 @@ declare const KmDeferred: vue13.DefineComponent<vue13.ExtractPropTypes<{
896
1015
  //#region src/guards/loader-guard.d.ts
897
1016
  interface LoaderGuardOptions {
898
1017
  onError?: (error: Error, to: RouteLocationNormalized) => void;
1018
+ /** Kimesh app context for storing loader data */
1019
+ appContext?: KimeshAppContext;
899
1020
  }
900
1021
  /**
901
1022
  * Create the loader navigation guard
@@ -906,6 +1027,7 @@ declare function createLoaderGuard(context: FullContext, options?: LoaderGuardOp
906
1027
  */
907
1028
  declare function installLoaderGuard(router: Router, context: FullContext, options?: {
908
1029
  onError?: (error: Error, to: RouteLocationNormalized) => void;
1030
+ appContext?: KimeshAppContext;
909
1031
  }): () => void;
910
1032
  //#endregion
911
1033
  //#region src/composables/use-search.d.ts
@@ -1201,6 +1323,76 @@ declare function useNavigationGuard(guard: BeforeNavigationHandler): () => void;
1201
1323
  */
1202
1324
  declare function useAfterNavigation(callback: AfterNavigationHandler): () => void;
1203
1325
  //#endregion
1326
+ //#region src/composables/use-loader-data.d.ts
1327
+ /**
1328
+ * Get loader data for the current route
1329
+ */
1330
+ declare function useLoaderData<T = unknown>(): ComputedRef<T>;
1331
+ /**
1332
+ * Get loader data from a specific route by path
1333
+ */
1334
+ declare function useMatchLoaderData<T = unknown>(routePath: string): ComputedRef<T | undefined>;
1335
+ /**
1336
+ * Get loader data from parent layout(s)
1337
+ */
1338
+ declare function useLayoutData<T = unknown>(): ComputedRef<T | undefined>;
1339
+ /**
1340
+ * Get all loader data from the route hierarchy
1341
+ */
1342
+ declare function useRouteLoaderData(): ComputedRef<Record<string, unknown>>;
1343
+ //#endregion
1344
+ //#region src/types/layout.d.ts
1345
+ /**
1346
+ * Layout loader data access result
1347
+ */
1348
+ interface LayoutLoaderData<T = unknown> {
1349
+ /** Loader data from the layout */
1350
+ data: T;
1351
+ /** Whether data is still loading */
1352
+ isLoading: boolean;
1353
+ /** Error if loader failed */
1354
+ error: Error | null;
1355
+ }
1356
+ /**
1357
+ * Match loader data access options
1358
+ */
1359
+ interface UseMatchLoaderDataOptions {
1360
+ /**
1361
+ * Route path to get loader data from
1362
+ * @example '/_dashboard' or '/posts'
1363
+ */
1364
+ routePath: string;
1365
+ }
1366
+ /**
1367
+ * Layout route metadata for internal use
1368
+ */
1369
+ interface LayoutRouteMeta {
1370
+ /** This route is a layout */
1371
+ __isLayout: true;
1372
+ /** Pending component for this layout */
1373
+ __pendingComponent?: () => Promise<{
1374
+ default: Component;
1375
+ }>;
1376
+ /** Pending delay in ms */
1377
+ __pendingMs?: number;
1378
+ /** Minimum pending duration in ms */
1379
+ __pendingMinMs?: number;
1380
+ /** Context additions from beforeLoad */
1381
+ __contextAdditions?: Record<string, unknown>;
1382
+ }
1383
+ /**
1384
+ * Loader data storage key format
1385
+ */
1386
+ type LoaderDataKey = `__loaderData:${string}`;
1387
+ /**
1388
+ * Create a loader data storage key
1389
+ */
1390
+ declare function createLoaderDataKey(routePath: string): LoaderDataKey;
1391
+ /**
1392
+ * Type guard for layout route meta
1393
+ */
1394
+ declare function isLayoutRouteMeta(meta: unknown): meta is LayoutRouteMeta;
1395
+ //#endregion
1204
1396
  //#region src/middleware/types.d.ts
1205
1397
  /**
1206
1398
  * Navigation redirect configuration
@@ -1459,4 +1651,4 @@ declare function createMiddlewareExecutor(appContext: KimeshAppContext): {
1459
1651
  executeChain(middlewares: RouteMiddleware[], context: MiddlewareContext): Promise<void | false | NavigationRedirect>;
1460
1652
  };
1461
1653
  //#endregion
1462
- export { UseSearchReturn as $, RouteParams as $t, NavigateToOptions as A, ExtractRouteParams as At, STATE_KEY_PREFIX as B, LoaderFn as Bt, KnownMiddleware as C, createFileRoute as Ct, MiddlewareRegistry as D, createKimeshApp as Dt, MiddlewareOption as E, KIMESH_APP_CONTEXT_KEY as Et, NavigationErrorContext as F, InlineRouteMiddleware as Ft, tryUseKimeshApp as G, RouteDefinition as Gt, getKmStateKeys as H, ParamValueOneOrMore as Ht, NavigationMiddlewareOptions as I, KIMESH_CONTEXT_KEY as It, useRuntimeConfig as J, RouteHeadFn as Jt, useKimeshApp as K, RouteHeadConfig as Kt, useAfterNavigation as L, KimeshApp as Lt, RouteMiddleware as M, FullContext as Mt, TypedRouteMiddleware as N, HasParams as Nt, MiddlewareRegistryEntry as O, BeforeLoadContext as Ot, NavigationContext as P, InlineMiddlewareContext as Pt, useReactiveParams as Q, RouteNames as Qt, useNavigationGuard as R, KimeshContext as Rt, KimeshMiddlewareNames as S, useKimeshContext as St, MiddlewareDefinition as T, CreateKimeshAppOptionsExtended as Tt, hasKmState as U, ParamValueZeroOrMore as Ut, clearKmState as V, ParamValue as Vt, useState as W, ParamValueZeroOrOne as Wt, useNavigate as X, RouteLocationRaw as Xt, NavigateOptions as Y, RouteInfoByName as Yt, useParams as Z, RouteNamedMap as Zt, createMiddlewarePlugin as _, getPluginHooks as _t, RouteRecordRaw$1 as a, KimeshRuntimeHooks as an, KmLink as at, defineKimeshMiddleware as b, isKimeshRuntimePlugin as bt, onBeforeRouteUpdate as c, KimeshRuntimePluginMeta as cn, QueryPluginOptions as ct, useRouter$1 as d, NavigationErrorHookContext as dn, routerPlugin as dt, RouteParamsRaw as en, useSearch as et, createMiddlewareExecutor as f, NavigationHookContext as fn, applyPlugin as ft, MiddlewarePluginOptions as g, defineKimeshRuntimePlugin as gt, createMiddlewareContext as h, KIMESH_PLUGIN_INDICATOR as ht, RouteLocationNormalizedLoaded$1 as i, KimeshAppContext as in, KmDeferred as it, NavigationRedirect as j, FileRouteOptions as jt, MiddlewareResult as k, CreateKimeshAppOptions as kt, useLink as l, KimeshRuntimePluginResult as ln, getCorePlugins as lt, executeMiddlewareChain as m, applyPluginsWithParallel as mt, NavigationHookAfter as n, RouteRecordInfo as nn, createLoaderGuard as nt, Router$1 as o, KimeshRuntimePlugin as on, KmLinkProps as ot, executeMiddleware as p, RuntimeConfigPublic as pn, applyPlugins as pt, RuntimeConfig as q, RouteHeadContext as qt, RouteLocationNormalized$1 as r, SearchSchema as rn, installLoaderGuard as rt, onBeforeRouteLeave as s, KimeshRuntimePluginDefinition as sn, KmOutlet as st, NavigationGuard as t, RoutePath as tn, LoaderGuardOptions as tt, useRoute$1 as u, NavigationAfterHookContext as un, queryPlugin as ut, middlewarePlugin as v, getPluginMeta as vt, MiddlewareContext as w, defineRoute as wt, navigateTo as x, defineContext as xt, abortNavigation as y, getPluginName as yt, useNavigationMiddleware as z, LoaderContext as zt };
1654
+ export { hasKmState as $, HasParams as $t, NavigateToOptions as A, KimeshRuntimePluginMeta as An, applyPlugins as At, useLayoutData as B, createFileRoute as Bt, KnownMiddleware as C, RoutePath as Cn, mergeAllRouteHeads as Ct, MiddlewareRegistry as D, KimeshRuntimeHooks as Dn, queryPlugin as Dt, MiddlewareOption as E, KimeshAppContext as En, getCorePlugins as Et, LayoutRouteMeta as F, RuntimeConfigPublic as Fn, getPluginMeta as Ft, NavigationErrorContext as G, AugmentedContext as Gt, useMatchLoaderData as H, CreateKimeshAppOptionsExtended as Ht, LoaderDataKey as I, getPluginName as It, useNavigationGuard as J, BeforeLoadFn as Jt, NavigationMiddlewareOptions as K, BeforeLoadContext as Kt, UseMatchLoaderDataOptions as L, isKimeshRuntimePlugin as Lt, RouteMiddleware as M, NavigationAfterHookContext as Mn, KIMESH_PLUGIN_INDICATOR as Mt, TypedRouteMiddleware as N, NavigationErrorHookContext as Nn, defineKimeshRuntimePlugin as Nt, MiddlewareRegistryEntry as O, KimeshRuntimePlugin as On, routerPlugin as Ot, LayoutLoaderData as P, NavigationHookContext as Pn, getPluginHooks as Pt, getKmStateKeys as Q, FullContext as Qt, createLoaderDataKey as R, defineContext as Rt, KimeshMiddlewareNames as S, RouteParamsRaw as Sn, createHeadPlugin as St, MiddlewareDefinition as T, SearchSchema as Tn, QueryPluginOptions as Tt, useRouteLoaderData as U, KIMESH_APP_CONTEXT_KEY as Ut, useLoaderData as V, defineRoute as Vt, NavigationContext as W, createKimeshApp as Wt, STATE_KEY_PREFIX as X, ExtractRouteParams as Xt, useNavigationMiddleware as Y, CreateKimeshAppOptions as Yt, clearKmState as Z, FileRouteOptions as Zt, createMiddlewarePlugin as _, RouteInfoByName as _n, KmOutlet as _t, RouteRecordRaw$1 as a, LoaderContext as an, NavigateOptions as at, defineKimeshMiddleware as b, RouteNames as bn, applyTitleTemplate as bt, onBeforeRouteUpdate as c, ParamValueOneOrMore as cn, useReactiveParams as ct, useRouter$1 as d, PendingConfig as dn, LoaderGuardOptions as dt, InlineMiddlewareContext as en, useState as et, createMiddlewareExecutor as f, RouteContext as fn, createLoaderGuard as ft, MiddlewarePluginOptions as g, RouteHeadFn as gn, KmLinkProps as gt, createMiddlewareContext as h, RouteHeadContext as hn, KmLink as ht, RouteLocationNormalizedLoaded$1 as i, KimeshContext as in, useRuntimeConfig as it, NavigationRedirect as j, KimeshRuntimePluginResult as jn, applyPluginsWithParallel as jt, MiddlewareResult as k, KimeshRuntimePluginDefinition as kn, applyPlugin as kt, useLink as l, ParamValueZeroOrMore as ln, UseSearchReturn as lt, executeMiddlewareChain as m, RouteHeadConfig as mn, KmDeferred as mt, NavigationHookAfter as n, KIMESH_CONTEXT_KEY as nn, useKimeshApp as nt, Router$1 as o, LoaderFn as on, useNavigate as ot, executeMiddleware as p, RouteDefinition as pn, installLoaderGuard as pt, useAfterNavigation as q, BeforeLoadContextFull as qt, RouteLocationNormalized$1 as r, KimeshApp as rn, RuntimeConfig as rt, onBeforeRouteLeave as s, ParamValue as sn, useParams as st, NavigationGuard as t, InlineRouteMiddleware as tn, tryUseKimeshApp as tt, useRoute$1 as u, ParamValueZeroOrOne as un, useSearch as ut, middlewarePlugin as v, RouteLocationRaw as vn, HeadPluginOptions as vt, MiddlewareContext as w, RouteRecordInfo as wn, mergeHeadConfigs as wt, navigateTo as x, RouteParams as xn, collectRouteHeads as xt, abortNavigation as y, RouteNamedMap as yn, applyHeadConfigNative as yt, isLayoutRouteMeta as z, useKimeshContext as zt };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as UseSearchReturn, $t as RouteParams, A as NavigateToOptions, At as ExtractRouteParams, B as STATE_KEY_PREFIX, Bt as LoaderFn, C as KnownMiddleware, Ct as createFileRoute, Dt as createKimeshApp, E as MiddlewareOption, Et as KIMESH_APP_CONTEXT_KEY, F as NavigationErrorContext, Ft as InlineRouteMiddleware, G as tryUseKimeshApp, Gt as RouteDefinition, H as getKmStateKeys, Ht as ParamValueOneOrMore, I as NavigationMiddlewareOptions, It as KIMESH_CONTEXT_KEY, J as useRuntimeConfig, Jt as RouteHeadFn, K as useKimeshApp, Kt as RouteHeadConfig, L as useAfterNavigation, Lt as KimeshApp, M as RouteMiddleware, Mt as FullContext, N as TypedRouteMiddleware, Nt as HasParams, Ot as BeforeLoadContext, P as NavigationContext, Pt as InlineMiddlewareContext, Q as useReactiveParams, Qt as RouteNames, R as useNavigationGuard, Rt as KimeshContext, S as KimeshMiddlewareNames, St as useKimeshContext, T as MiddlewareDefinition, Tt as CreateKimeshAppOptionsExtended, U as hasKmState, Ut as ParamValueZeroOrMore, V as clearKmState, Vt as ParamValue, W as useState, Wt as ParamValueZeroOrOne, X as useNavigate, Xt as RouteLocationRaw, Y as NavigateOptions, Yt as RouteInfoByName, Z as useParams, Zt as RouteNamedMap, _ as createMiddlewarePlugin, _t as getPluginHooks, a as RouteRecordRaw, an as KimeshRuntimeHooks, at as KmLink, b as defineKimeshMiddleware, bt as isKimeshRuntimePlugin, c as onBeforeRouteUpdate, cn as KimeshRuntimePluginMeta, ct as QueryPluginOptions, d as useRouter, dn as NavigationErrorHookContext, dt as routerPlugin, en as RouteParamsRaw, et as useSearch, f as createMiddlewareExecutor, fn as NavigationHookContext, ft as applyPlugin, g as MiddlewarePluginOptions, gt as defineKimeshRuntimePlugin, h as createMiddlewareContext, ht as KIMESH_PLUGIN_INDICATOR, i as RouteLocationNormalizedLoaded, in as KimeshAppContext, it as KmDeferred, j as NavigationRedirect, jt as FileRouteOptions, k as MiddlewareResult, kt as CreateKimeshAppOptions, l as useLink, ln as KimeshRuntimePluginResult, lt as getCorePlugins, mt as applyPluginsWithParallel, n as NavigationHookAfter, nn as RouteRecordInfo, nt as createLoaderGuard, o as Router, on as KimeshRuntimePlugin, ot as KmLinkProps, pn as RuntimeConfigPublic, pt as applyPlugins, q as RuntimeConfig, qt as RouteHeadContext, r as RouteLocationNormalized, rn as SearchSchema, rt as installLoaderGuard, s as onBeforeRouteLeave, sn as KimeshRuntimePluginDefinition, st as KmOutlet, t as NavigationGuard, tn as RoutePath, tt as LoaderGuardOptions, u as useRoute, un as NavigationAfterHookContext, ut as queryPlugin, v as middlewarePlugin, vt as getPluginMeta, w as MiddlewareContext, wt as defineRoute, x as navigateTo, xt as defineContext, y as abortNavigation, yt as getPluginName, z as useNavigationMiddleware, zt as LoaderContext } from "./index-9xJk3v3Z.mjs";
2
- export { BeforeLoadContext, CreateKimeshAppOptions, CreateKimeshAppOptionsExtended, ExtractRouteParams, FileRouteOptions, FullContext, HasParams, InlineMiddlewareContext, InlineRouteMiddleware, KIMESH_APP_CONTEXT_KEY, KIMESH_CONTEXT_KEY, KIMESH_PLUGIN_INDICATOR, KimeshApp, KimeshAppContext, KimeshContext, KimeshMiddlewareNames, KimeshRuntimeHooks, KimeshRuntimePlugin, KimeshRuntimePluginDefinition, KimeshRuntimePluginMeta, KimeshRuntimePluginResult, KmDeferred, KmLink, KmLinkProps, KmOutlet, KnownMiddleware, LoaderContext, LoaderFn, LoaderGuardOptions, MiddlewareContext, MiddlewareDefinition, MiddlewareOption, MiddlewarePluginOptions, MiddlewareResult, NavigateOptions, NavigateToOptions, NavigationAfterHookContext, NavigationContext, NavigationErrorContext, NavigationErrorHookContext, NavigationGuard, NavigationHookAfter, NavigationHookContext, NavigationMiddlewareOptions, NavigationRedirect, ParamValue, ParamValueOneOrMore, ParamValueZeroOrMore, ParamValueZeroOrOne, QueryPluginOptions, RouteDefinition, RouteHeadConfig, RouteHeadContext, RouteHeadFn, RouteInfoByName, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationRaw, RouteMiddleware, RouteNamedMap, RouteNames, RouteParams, RouteParamsRaw, RoutePath, RouteRecordInfo, RouteRecordRaw, Router, RuntimeConfig, RuntimeConfigPublic, STATE_KEY_PREFIX, SearchSchema, TypedRouteMiddleware, UseSearchReturn, abortNavigation, applyPlugin, applyPlugins, applyPluginsWithParallel, clearKmState, createFileRoute, createKimeshApp, createLoaderGuard, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineContext, defineKimeshMiddleware, defineKimeshRuntimePlugin, defineRoute, getCorePlugins, getKmStateKeys, getPluginHooks, getPluginMeta, getPluginName, hasKmState, installLoaderGuard, isKimeshRuntimePlugin, middlewarePlugin, navigateTo, onBeforeRouteLeave, onBeforeRouteUpdate, queryPlugin, routerPlugin, tryUseKimeshApp, useAfterNavigation, useKimeshApp, useKimeshContext, useLink, useNavigate, useNavigationGuard, useNavigationMiddleware, useParams, useReactiveParams, useRoute, useRouter, useRuntimeConfig, useSearch, useState };
1
+ import { $ as hasKmState, $t as HasParams, A as NavigateToOptions, An as KimeshRuntimePluginMeta, At as applyPlugins, B as useLayoutData, Bt as createFileRoute, C as KnownMiddleware, Cn as RoutePath, Ct as mergeAllRouteHeads, Dn as KimeshRuntimeHooks, Dt as queryPlugin, E as MiddlewareOption, En as KimeshAppContext, Et as getCorePlugins, F as LayoutRouteMeta, Fn as RuntimeConfigPublic, Ft as getPluginMeta, G as NavigationErrorContext, Gt as AugmentedContext, H as useMatchLoaderData, Ht as CreateKimeshAppOptionsExtended, I as LoaderDataKey, It as getPluginName, J as useNavigationGuard, Jt as BeforeLoadFn, K as NavigationMiddlewareOptions, Kt as BeforeLoadContext, L as UseMatchLoaderDataOptions, Lt as isKimeshRuntimePlugin, M as RouteMiddleware, Mn as NavigationAfterHookContext, Mt as KIMESH_PLUGIN_INDICATOR, N as TypedRouteMiddleware, Nn as NavigationErrorHookContext, Nt as defineKimeshRuntimePlugin, On as KimeshRuntimePlugin, Ot as routerPlugin, P as LayoutLoaderData, Pn as NavigationHookContext, Pt as getPluginHooks, Q as getKmStateKeys, Qt as FullContext, R as createLoaderDataKey, Rt as defineContext, S as KimeshMiddlewareNames, Sn as RouteParamsRaw, St as createHeadPlugin, T as MiddlewareDefinition, Tn as SearchSchema, Tt as QueryPluginOptions, U as useRouteLoaderData, Ut as KIMESH_APP_CONTEXT_KEY, V as useLoaderData, Vt as defineRoute, W as NavigationContext, Wt as createKimeshApp, X as STATE_KEY_PREFIX, Xt as ExtractRouteParams, Y as useNavigationMiddleware, Yt as CreateKimeshAppOptions, Z as clearKmState, Zt as FileRouteOptions, _ as createMiddlewarePlugin, _n as RouteInfoByName, _t as KmOutlet, a as RouteRecordRaw, an as LoaderContext, at as NavigateOptions, b as defineKimeshMiddleware, bn as RouteNames, bt as applyTitleTemplate, c as onBeforeRouteUpdate, cn as ParamValueOneOrMore, ct as useReactiveParams, d as useRouter, dn as PendingConfig, dt as LoaderGuardOptions, en as InlineMiddlewareContext, et as useState, f as createMiddlewareExecutor, fn as RouteContext, ft as createLoaderGuard, g as MiddlewarePluginOptions, gn as RouteHeadFn, gt as KmLinkProps, h as createMiddlewareContext, hn as RouteHeadContext, ht as KmLink, i as RouteLocationNormalizedLoaded, in as KimeshContext, it as useRuntimeConfig, j as NavigationRedirect, jn as KimeshRuntimePluginResult, jt as applyPluginsWithParallel, k as MiddlewareResult, kn as KimeshRuntimePluginDefinition, kt as applyPlugin, l as useLink, ln as ParamValueZeroOrMore, lt as UseSearchReturn, mn as RouteHeadConfig, mt as KmDeferred, n as NavigationHookAfter, nn as KIMESH_CONTEXT_KEY, nt as useKimeshApp, o as Router, on as LoaderFn, ot as useNavigate, pn as RouteDefinition, pt as installLoaderGuard, q as useAfterNavigation, qt as BeforeLoadContextFull, r as RouteLocationNormalized, rn as KimeshApp, rt as RuntimeConfig, s as onBeforeRouteLeave, sn as ParamValue, st as useParams, t as NavigationGuard, tn as InlineRouteMiddleware, tt as tryUseKimeshApp, u as useRoute, un as ParamValueZeroOrOne, ut as useSearch, v as middlewarePlugin, vn as RouteLocationRaw, vt as HeadPluginOptions, w as MiddlewareContext, wn as RouteRecordInfo, wt as mergeHeadConfigs, x as navigateTo, xn as RouteParams, xt as collectRouteHeads, y as abortNavigation, yn as RouteNamedMap, yt as applyHeadConfigNative, z as isLayoutRouteMeta, zt as useKimeshContext } from "./index-Bi0AaYQy.mjs";
2
+ export { AugmentedContext, BeforeLoadContext, BeforeLoadContextFull, BeforeLoadFn, CreateKimeshAppOptions, CreateKimeshAppOptionsExtended, ExtractRouteParams, FileRouteOptions, FullContext, HasParams, HeadPluginOptions, InlineMiddlewareContext, InlineRouteMiddleware, KIMESH_APP_CONTEXT_KEY, KIMESH_CONTEXT_KEY, KIMESH_PLUGIN_INDICATOR, KimeshApp, KimeshAppContext, KimeshContext, KimeshMiddlewareNames, KimeshRuntimeHooks, KimeshRuntimePlugin, KimeshRuntimePluginDefinition, KimeshRuntimePluginMeta, KimeshRuntimePluginResult, KmDeferred, KmLink, KmLinkProps, KmOutlet, KnownMiddleware, LayoutLoaderData, LayoutRouteMeta, LoaderContext, LoaderDataKey, LoaderFn, LoaderGuardOptions, MiddlewareContext, MiddlewareDefinition, MiddlewareOption, MiddlewarePluginOptions, MiddlewareResult, NavigateOptions, NavigateToOptions, NavigationAfterHookContext, NavigationContext, NavigationErrorContext, NavigationErrorHookContext, NavigationGuard, NavigationHookAfter, NavigationHookContext, NavigationMiddlewareOptions, NavigationRedirect, ParamValue, ParamValueOneOrMore, ParamValueZeroOrMore, ParamValueZeroOrOne, PendingConfig, QueryPluginOptions, RouteContext, RouteDefinition, RouteHeadConfig, RouteHeadContext, RouteHeadFn, RouteInfoByName, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationRaw, RouteMiddleware, RouteNamedMap, RouteNames, RouteParams, RouteParamsRaw, RoutePath, RouteRecordInfo, RouteRecordRaw, Router, RuntimeConfig, RuntimeConfigPublic, STATE_KEY_PREFIX, SearchSchema, TypedRouteMiddleware, UseMatchLoaderDataOptions, UseSearchReturn, abortNavigation, applyHeadConfigNative, applyPlugin, applyPlugins, applyPluginsWithParallel, applyTitleTemplate, clearKmState, collectRouteHeads, createFileRoute, createHeadPlugin, createKimeshApp, createLoaderDataKey, createLoaderGuard, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineContext, defineKimeshMiddleware, defineKimeshRuntimePlugin, defineRoute, getCorePlugins, getKmStateKeys, getPluginHooks, getPluginMeta, getPluginName, hasKmState, installLoaderGuard, isKimeshRuntimePlugin, isLayoutRouteMeta, mergeAllRouteHeads, mergeHeadConfigs, middlewarePlugin, navigateTo, onBeforeRouteLeave, onBeforeRouteUpdate, queryPlugin, routerPlugin, tryUseKimeshApp, useAfterNavigation, useKimeshApp, useKimeshContext, useLayoutData, useLink, useLoaderData, useMatchLoaderData, useNavigate, useNavigationGuard, useNavigationMiddleware, useParams, useReactiveParams, useRoute, useRouteLoaderData, useRouter, useRuntimeConfig, useSearch, useState };
package/dist/index.mjs CHANGED
@@ -1,22 +1,121 @@
1
- import { a as KIMESH_PLUGIN_INDICATOR, c as getPluginMeta, i as createMiddlewareContext, l as getPluginName, n as middlewarePlugin, o as defineKimeshRuntimePlugin, s as getPluginHooks, t as createMiddlewarePlugin, u as isKimeshRuntimePlugin } from "./plugin-BfxCqV0B.mjs";
1
+ import { a as KIMESH_PLUGIN_INDICATOR, c as getPluginMeta, i as createMiddlewareContext, l as getPluginName, n as middlewarePlugin, o as defineKimeshRuntimePlugin, s as getPluginHooks, t as createMiddlewarePlugin, u as isKimeshRuntimePlugin } from "./plugin-0r5-meph.mjs";
2
2
  import { a as defineKimeshMiddleware, i as abortNavigation, o as navigateTo, t as createMiddlewareExecutor } from "./middleware-DdlzhfiB.mjs";
3
3
  import { KeepAlive, Suspense, Transition, computed, createApp, defineComponent, h, inject, isRef, nextTick, onErrorCaptured, reactive, ref, toRef } from "vue";
4
4
  import { RouterLink, RouterView, createRouter, createWebHashHistory, createWebHistory, onBeforeRouteLeave, onBeforeRouteUpdate, useLink, useRoute, useRoute as useRoute$1, useRouter, useRouter as useRouter$1 } from "vue-router";
5
5
  import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
6
6
  import { createHooks } from "hookable";
7
7
 
8
- //#region src/guards/loader-guard.ts
8
+ //#region src/types/layout.ts
9
9
  /**
10
- * Safely parse input with schema, returning input on failure
10
+ * Create a loader data storage key
11
11
  */
12
- function safeParse(schema, input) {
13
- try {
14
- return schema.parse(input);
15
- } catch {
16
- return input;
12
+ function createLoaderDataKey(routePath) {
13
+ return `__loaderData:${routePath}`;
14
+ }
15
+ /**
16
+ * Type guard for layout route meta
17
+ */
18
+ function isLayoutRouteMeta(meta) {
19
+ return typeof meta === "object" && meta !== null && "__isLayout" in meta;
20
+ }
21
+
22
+ //#endregion
23
+ //#region src/composables/use-loader-data.ts
24
+ /**
25
+ * @kimesh/router-runtime - useLoaderData composable
26
+ *
27
+ * Access loader data from the current route or parent layouts.
28
+ */
29
+ const LOADER_DATA_PREFIX = "__loaderData:";
30
+ function useKimeshAppRequired(composableName) {
31
+ const kimeshApp = inject(KIMESH_APP_CONTEXT_KEY);
32
+ if (!kimeshApp) throw new Error(`[kimesh] ${composableName} must be used within a Kimesh app`);
33
+ return kimeshApp;
34
+ }
35
+ /**
36
+ * Get loader data for the current route
37
+ */
38
+ function useLoaderData() {
39
+ const route = useRoute$1();
40
+ const kimeshApp = useKimeshAppRequired("useLoaderData");
41
+ return computed(() => {
42
+ const key = createLoaderDataKey(route.path);
43
+ return kimeshApp._state[key];
44
+ });
45
+ }
46
+ /**
47
+ * Get loader data from a specific route by path
48
+ */
49
+ function useMatchLoaderData(routePath) {
50
+ const kimeshApp = useKimeshAppRequired("useMatchLoaderData");
51
+ return computed(() => {
52
+ const key = createLoaderDataKey(routePath);
53
+ return kimeshApp._state[key];
54
+ });
55
+ }
56
+ /**
57
+ * Get loader data from parent layout(s)
58
+ */
59
+ function useLayoutData() {
60
+ const route = useRoute$1();
61
+ const kimeshApp = useKimeshAppRequired("useLayoutData");
62
+ return computed(() => {
63
+ const parentLayout = findParentLayout(route);
64
+ if (!parentLayout) return void 0;
65
+ const key = createLoaderDataKey(parentLayout.path);
66
+ return kimeshApp._state[key];
67
+ });
68
+ }
69
+ /**
70
+ * Get all loader data from the route hierarchy
71
+ */
72
+ function useRouteLoaderData() {
73
+ const route = useRoute$1();
74
+ const kimeshApp = useKimeshAppRequired("useRouteLoaderData");
75
+ return computed(() => {
76
+ const result = {};
77
+ for (const matched of route.matched) {
78
+ const key = createLoaderDataKey(matched.path);
79
+ if (kimeshApp._state[key] !== void 0) result[matched.path] = kimeshApp._state[key];
80
+ }
81
+ return result;
82
+ });
83
+ }
84
+ /**
85
+ * Find the parent layout route in matched routes
86
+ */
87
+ function findParentLayout(route) {
88
+ const currentIndex = route.matched.length - 1;
89
+ for (let i = currentIndex - 1; i >= 0; i--) {
90
+ const matched = route.matched[i];
91
+ if (matched.meta?.__kimeshIsLayout) return matched;
17
92
  }
93
+ return null;
18
94
  }
19
95
  /**
96
+ * Store loader data for a route
97
+ * Used internally by loader guard
98
+ */
99
+ function storeLoaderData(kimeshApp, routePath, data) {
100
+ const key = createLoaderDataKey(routePath);
101
+ kimeshApp._state[key] = data;
102
+ }
103
+ /**
104
+ * Clear loader data for routes not in the new matched routes
105
+ * Used internally during navigation
106
+ */
107
+ function clearStaleLoaderData(kimeshApp, newMatchedPaths) {
108
+ const keysToDelete = [];
109
+ for (const key of Object.keys(kimeshApp._state)) if (key.startsWith(LOADER_DATA_PREFIX)) {
110
+ const routePath = key.slice(13);
111
+ if (!newMatchedPaths.has(routePath)) keysToDelete.push(key);
112
+ }
113
+ for (const key of keysToDelete) delete kimeshApp._state[key];
114
+ }
115
+
116
+ //#endregion
117
+ //#region src/guards/loader-guard.ts
118
+ /**
20
119
  * Validate search params using the provided schema
21
120
  */
22
121
  function validateSearchParams(query, schema) {
@@ -24,12 +123,16 @@ function validateSearchParams(query, schema) {
24
123
  if (schema.safeParse) {
25
124
  const result = schema.safeParse(query);
26
125
  if (result.success && result.data !== void 0) return result.data;
27
- return safeParse(schema, {});
28
126
  }
29
- const parsed = safeParse(schema, query);
30
- if (parsed !== query) return parsed;
31
- const defaults = safeParse(schema, {});
32
- return defaults !== query ? defaults : query;
127
+ try {
128
+ return schema.parse(query);
129
+ } catch {
130
+ try {
131
+ return schema.parse({});
132
+ } catch {
133
+ return query;
134
+ }
135
+ }
33
136
  }
34
137
  /**
35
138
  * Collect loaders from matched route records
@@ -40,12 +143,28 @@ function collectLoaders(to) {
40
143
  const routeDef = record.meta?.__kimesh;
41
144
  if (routeDef?.loader) loaders.push({
42
145
  loader: routeDef.loader,
43
- routeDef
146
+ routeDef,
147
+ routeRecord: record
44
148
  });
45
149
  }
46
150
  return loaders;
47
151
  }
48
152
  /**
153
+ * Collect beforeLoad functions from matched route records (in order)
154
+ */
155
+ function collectBeforeLoads(to) {
156
+ const beforeLoads = [];
157
+ for (const record of to.matched) {
158
+ const routeDef = record.meta?.__kimesh;
159
+ if (routeDef?.beforeLoad) beforeLoads.push({
160
+ beforeLoad: routeDef.beforeLoad,
161
+ routeDef,
162
+ routeRecord: record
163
+ });
164
+ }
165
+ return beforeLoads;
166
+ }
167
+ /**
49
168
  * Check if error is an AbortError
50
169
  */
51
170
  function isAbortError(error) {
@@ -55,20 +174,45 @@ function isAbortError(error) {
55
174
  * Create the loader navigation guard
56
175
  */
57
176
  function createLoaderGuard(context, options = {}) {
58
- const { onError } = options;
177
+ const { onError, appContext } = options;
59
178
  return async function loaderGuard(to, _from, abortController) {
179
+ const beforeLoads = collectBeforeLoads(to);
60
180
  const loaders = collectLoaders(to);
61
- if (loaders.length === 0) return;
62
- await Promise.all(loaders.map(async ({ loader, routeDef }) => {
181
+ if (appContext) clearStaleLoaderData(appContext, new Set(to.matched.map((r) => r.path)));
182
+ if (beforeLoads.length === 0 && loaders.length === 0) return;
183
+ let accumulatedContext = { ...context };
184
+ for (const { beforeLoad, routeDef } of beforeLoads) {
185
+ const search = validateSearchParams(to.query, routeDef.validateSearch);
186
+ try {
187
+ const result = await beforeLoad({
188
+ params: to.params,
189
+ search,
190
+ context: accumulatedContext,
191
+ signal: abortController.signal
192
+ });
193
+ if (result && typeof result === "object") accumulatedContext = {
194
+ ...accumulatedContext,
195
+ ...result
196
+ };
197
+ } catch (error) {
198
+ if (isAbortError(error)) throw error;
199
+ console.error("[kimesh] beforeLoad error:", error);
200
+ onError?.(error, to);
201
+ to.meta.__kimeshError = error;
202
+ return;
203
+ }
204
+ }
205
+ await Promise.all(loaders.map(async ({ loader, routeDef, routeRecord }) => {
63
206
  const search = validateSearchParams(to.query, routeDef.validateSearch);
64
207
  const loaderContext = {
65
208
  params: to.params,
66
209
  search,
67
- context,
210
+ context: accumulatedContext,
68
211
  signal: abortController.signal
69
212
  };
70
213
  try {
71
- await loader(loaderContext);
214
+ const data = await loader(loaderContext);
215
+ if (appContext && data !== void 0) storeLoaderData(appContext, routeRecord.path, data);
72
216
  } catch (error) {
73
217
  if (isAbortError(error)) throw error;
74
218
  console.error("[kimesh] Loader error:", error);
@@ -89,7 +233,10 @@ function installLoaderGuard(router, context, options) {
89
233
  });
90
234
  const removeBeforeResolve = router.beforeResolve(async (to, from) => {
91
235
  if (!abortController) abortController = new AbortController();
92
- const guard = createLoaderGuard(context, { onError: options?.onError });
236
+ const guard = createLoaderGuard(context, {
237
+ onError: options?.onError,
238
+ appContext: options?.appContext
239
+ });
93
240
  try {
94
241
  await guard(to, from, abortController);
95
242
  } catch (error) {
@@ -247,7 +394,6 @@ async function createKimeshApp(options) {
247
394
  routes,
248
395
  scrollBehavior
249
396
  });
250
- const removeLoaderGuard = installLoaderGuard(router, fullContext, { onError: (error, to) => console.error(`[Kimesh] Loader error for ${to.path}:`, error) });
251
397
  const vueApp = createApp({ render: () => h(rootComponent) });
252
398
  const hooks = createHooks();
253
399
  const appContext = {
@@ -271,6 +417,10 @@ async function createKimeshApp(options) {
271
417
  return vueApp.runWithContext(fn);
272
418
  }
273
419
  };
420
+ const removeLoaderGuard = installLoaderGuard(router, fullContext, {
421
+ onError: (error, to) => console.error(`[Kimesh] Loader error for ${to.path}:`, error),
422
+ appContext
423
+ });
274
424
  if (plugins.length > 0) await applyPlugins(appContext, plugins);
275
425
  await hooks.callHook("app:created", vueApp);
276
426
  vueApp.provide(KIMESH_CONTEXT_KEY, fullContext);
@@ -589,11 +739,161 @@ const viewTransitionsPlugin = defineKimeshRuntimePlugin({
589
739
  });
590
740
 
591
741
  //#endregion
592
- //#region src/components/KmOutlet.ts
742
+ //#region src/plugins/head.ts
743
+ /**
744
+ * Merge two head configurations
745
+ * Page head takes priority over layout head
746
+ */
747
+ function mergeHeadConfigs(layoutHead, pageHead) {
748
+ if (!layoutHead && !pageHead) return {};
749
+ if (!layoutHead) return pageHead;
750
+ if (!pageHead) return layoutHead;
751
+ return {
752
+ title: pageHead.title ?? layoutHead.title,
753
+ titleTemplate: pageHead.titleTemplate ?? layoutHead.titleTemplate,
754
+ meta: mergeMetaTags(layoutHead.meta, pageHead.meta),
755
+ link: mergeLinkTags(layoutHead.link, pageHead.link),
756
+ script: [...layoutHead.script || [], ...pageHead.script || []],
757
+ style: [...layoutHead.style || [], ...pageHead.style || []],
758
+ htmlAttrs: {
759
+ ...layoutHead.htmlAttrs,
760
+ ...pageHead.htmlAttrs
761
+ },
762
+ bodyAttrs: {
763
+ ...layoutHead.bodyAttrs,
764
+ ...pageHead.bodyAttrs
765
+ }
766
+ };
767
+ }
768
+ /**
769
+ * Generic tag merger with deduplication
770
+ */
771
+ function mergeTagsWithDedup(layoutTags, pageTags, getKey) {
772
+ if (!layoutTags && !pageTags) return void 0;
773
+ if (!layoutTags) return pageTags;
774
+ if (!pageTags) return layoutTags;
775
+ const tagMap = /* @__PURE__ */ new Map();
776
+ for (const tag of layoutTags) tagMap.set(getKey(tag), tag);
777
+ for (const tag of pageTags) tagMap.set(getKey(tag), tag);
778
+ return Array.from(tagMap.values());
779
+ }
780
+ /**
781
+ * Merge meta tags with deduplication
782
+ */
783
+ function mergeMetaTags(layoutMeta, pageMeta) {
784
+ return mergeTagsWithDedup(layoutMeta, pageMeta, getMetaKey);
785
+ }
786
+ /**
787
+ * Get unique key for a meta tag
788
+ */
789
+ function getMetaKey(tag) {
790
+ if (tag.name) return `name:${tag.name}`;
791
+ if (tag.property) return `property:${tag.property}`;
792
+ if (tag.charset) return "charset";
793
+ if (tag["http-equiv"]) return `http-equiv:${tag["http-equiv"]}`;
794
+ return JSON.stringify(tag);
795
+ }
796
+ /**
797
+ * Merge link tags with deduplication
798
+ */
799
+ function mergeLinkTags(layoutLink, pageLink) {
800
+ return mergeTagsWithDedup(layoutLink, pageLink, getLinkKey);
801
+ }
593
802
  /**
594
- * Check if View Transitions API is supported
803
+ * Get unique key for a link tag
595
804
  */
596
- const supportsViewTransitions = () => typeof document !== "undefined" && "startViewTransition" in document;
805
+ function getLinkKey(tag) {
806
+ if (tag.rel === "canonical") return "canonical";
807
+ if (tag.rel === "icon" && tag.sizes) return `icon:${tag.sizes}`;
808
+ if (tag.hreflang) return `alternate:${tag.hreflang}`;
809
+ return `${tag.rel}:${tag.href}`;
810
+ }
811
+ /**
812
+ * Apply title template to title
813
+ */
814
+ function applyTitleTemplate(title, template, defaultTitle) {
815
+ const resolvedTitle = title ?? defaultTitle ?? "";
816
+ if (!template) return resolvedTitle;
817
+ if (typeof template === "function") return template(resolvedTitle);
818
+ return template.replace(/%s/g, resolvedTitle);
819
+ }
820
+ /**
821
+ * Collect head configurations from matched routes
822
+ */
823
+ function collectRouteHeads(route, kimeshApp) {
824
+ const heads = [];
825
+ for (const matched of route.matched) {
826
+ const routeDef = matched.meta?.__kimesh;
827
+ if (!routeDef?.head) continue;
828
+ const loaderDataKey = createLoaderDataKey(matched.path);
829
+ const loaderData = kimeshApp._state[loaderDataKey];
830
+ const headContext = {
831
+ params: route.params,
832
+ loaderData,
833
+ route
834
+ };
835
+ let headConfig;
836
+ if (typeof routeDef.head === "function") headConfig = routeDef.head(headContext);
837
+ else headConfig = routeDef.head;
838
+ heads.push(headConfig);
839
+ }
840
+ return heads;
841
+ }
842
+ /**
843
+ * Merge all route heads into final config
844
+ */
845
+ function mergeAllRouteHeads(heads) {
846
+ if (heads.length === 0) return {};
847
+ if (heads.length === 1) return heads[0];
848
+ return heads.reduce((merged, current) => mergeHeadConfigs(merged, current), {});
849
+ }
850
+ /**
851
+ * Apply head config to document (native DOM)
852
+ */
853
+ function applyHeadConfigNative(config, options = {}) {
854
+ const finalTitle = applyTitleTemplate(config.title, config.titleTemplate ?? options.titleTemplate, options.defaultTitle);
855
+ if (finalTitle) document.title = finalTitle;
856
+ if (config.meta) for (const meta of config.meta) updateMetaTag(meta);
857
+ if (config.htmlAttrs) {
858
+ for (const [key, value] of Object.entries(config.htmlAttrs)) if (value !== void 0) document.documentElement.setAttribute(key, value);
859
+ }
860
+ if (config.bodyAttrs) {
861
+ for (const [key, value] of Object.entries(config.bodyAttrs)) if (value !== void 0) document.body.setAttribute(key, value);
862
+ }
863
+ }
864
+ /**
865
+ * Update or create a meta tag
866
+ */
867
+ function updateMetaTag(meta) {
868
+ let selector;
869
+ if (meta.name) selector = `meta[name="${meta.name}"]`;
870
+ else if (meta.property) selector = `meta[property="${meta.property}"]`;
871
+ else if (meta.charset) selector = "meta[charset]";
872
+ else if (meta["http-equiv"]) selector = `meta[http-equiv="${meta["http-equiv"]}"]`;
873
+ else return;
874
+ let element = document.head.querySelector(selector);
875
+ if (!element) {
876
+ element = document.createElement("meta");
877
+ document.head.appendChild(element);
878
+ }
879
+ for (const [key, value] of Object.entries(meta)) if (value !== void 0) element.setAttribute(key, value);
880
+ }
881
+ /**
882
+ * Create the head plugin
883
+ */
884
+ function createHeadPlugin(options = {}) {
885
+ return { install(app, { router, kimeshApp }) {
886
+ router.afterEach((to) => {
887
+ applyHeadConfigNative(mergeAllRouteHeads(collectRouteHeads(to, kimeshApp)), options);
888
+ });
889
+ } };
890
+ }
891
+
892
+ //#endregion
893
+ //#region src/components/KmOutlet.ts
894
+ function supportsViewTransitions() {
895
+ return typeof document !== "undefined" && "startViewTransition" in document;
896
+ }
597
897
  /**
598
898
  * KmOutlet - Router view wrapper component
599
899
  * Renders the matched route component with optional transitions and keepalive
@@ -1138,7 +1438,11 @@ function clearKmState(keys) {
1138
1438
  const app = tryUseKimeshApp();
1139
1439
  if (!app?._state) return;
1140
1440
  const allStateKeys = getKmStateKeys();
1141
- const keysToDelete = keys === void 0 ? allStateKeys : typeof keys === "function" ? allStateKeys.filter(keys) : Array.isArray(keys) ? keys : [keys];
1441
+ let keysToDelete;
1442
+ if (keys === void 0) keysToDelete = allStateKeys;
1443
+ else if (typeof keys === "function") keysToDelete = allStateKeys.filter(keys);
1444
+ else if (Array.isArray(keys)) keysToDelete = keys;
1445
+ else keysToDelete = [keys];
1142
1446
  for (const key of keysToDelete) {
1143
1447
  const prefixedKey = STATE_KEY_PREFIX + key;
1144
1448
  if (prefixedKey in app._state) delete app._state[prefixedKey];
@@ -1239,4 +1543,4 @@ function useAfterNavigation(callback) {
1239
1543
  }
1240
1544
 
1241
1545
  //#endregion
1242
- export { KIMESH_APP_CONTEXT_KEY, KIMESH_CONTEXT_KEY, KIMESH_PLUGIN_INDICATOR, KmDeferred, KmLink, KmOutlet, STATE_KEY_PREFIX, abortNavigation, applyPlugin, applyPlugins, applyPluginsWithParallel, clearKmState, createFileRoute, createKimeshApp, createLoaderGuard, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineContext, defineKimeshMiddleware, defineKimeshRuntimePlugin, defineRoute, getCorePlugins, getKmStateKeys, getPluginHooks, getPluginMeta, getPluginName, hasKmState, installLoaderGuard, isKimeshRuntimePlugin, middlewarePlugin, navigateTo, onBeforeRouteLeave, onBeforeRouteUpdate, queryPlugin, routerPlugin, tryUseKimeshApp, useAfterNavigation, useKimeshApp, useKimeshContext, useLink, useNavigate, useNavigationGuard, useNavigationMiddleware, useParams, useReactiveParams, useRoute, useRouter, useRuntimeConfig, useSearch, useState };
1546
+ export { KIMESH_APP_CONTEXT_KEY, KIMESH_CONTEXT_KEY, KIMESH_PLUGIN_INDICATOR, KmDeferred, KmLink, KmOutlet, STATE_KEY_PREFIX, abortNavigation, applyHeadConfigNative, applyPlugin, applyPlugins, applyPluginsWithParallel, applyTitleTemplate, clearKmState, collectRouteHeads, createFileRoute, createHeadPlugin, createKimeshApp, createLoaderDataKey, createLoaderGuard, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineContext, defineKimeshMiddleware, defineKimeshRuntimePlugin, defineRoute, getCorePlugins, getKmStateKeys, getPluginHooks, getPluginMeta, getPluginName, hasKmState, installLoaderGuard, isKimeshRuntimePlugin, isLayoutRouteMeta, mergeAllRouteHeads, mergeHeadConfigs, middlewarePlugin, navigateTo, onBeforeRouteLeave, onBeforeRouteUpdate, queryPlugin, routerPlugin, tryUseKimeshApp, useAfterNavigation, useKimeshApp, useKimeshContext, useLayoutData, useLink, useLoaderData, useMatchLoaderData, useNavigate, useNavigationGuard, useNavigationMiddleware, useParams, useReactiveParams, useRoute, useRouteLoaderData, useRouter, useRuntimeConfig, useSearch, useState };
@@ -1,2 +1,2 @@
1
- import { A as NavigateToOptions, C as KnownMiddleware, D as MiddlewareRegistry, E as MiddlewareOption, M as RouteMiddleware, N as TypedRouteMiddleware, O as MiddlewareRegistryEntry, S as KimeshMiddlewareNames, T as MiddlewareDefinition, _ as createMiddlewarePlugin, b as defineKimeshMiddleware, f as createMiddlewareExecutor, g as MiddlewarePluginOptions, h as createMiddlewareContext, j as NavigationRedirect, k as MiddlewareResult, m as executeMiddlewareChain, p as executeMiddleware, v as middlewarePlugin, w as MiddlewareContext, x as navigateTo, y as abortNavigation } from "../index-9xJk3v3Z.mjs";
1
+ import { A as NavigateToOptions, C as KnownMiddleware, D as MiddlewareRegistry, E as MiddlewareOption, M as RouteMiddleware, N as TypedRouteMiddleware, O as MiddlewareRegistryEntry, S as KimeshMiddlewareNames, T as MiddlewareDefinition, _ as createMiddlewarePlugin, b as defineKimeshMiddleware, f as createMiddlewareExecutor, g as MiddlewarePluginOptions, h as createMiddlewareContext, j as NavigationRedirect, k as MiddlewareResult, m as executeMiddlewareChain, p as executeMiddleware, v as middlewarePlugin, w as MiddlewareContext, x as navigateTo, y as abortNavigation } from "../index-Bi0AaYQy.mjs";
2
2
  export { KimeshMiddlewareNames, KnownMiddleware, MiddlewareContext, MiddlewareDefinition, MiddlewareOption, MiddlewarePluginOptions, MiddlewareRegistry, MiddlewareRegistryEntry, MiddlewareResult, NavigateToOptions, NavigationRedirect, RouteMiddleware, TypedRouteMiddleware, abortNavigation, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineKimeshMiddleware, executeMiddleware, executeMiddlewareChain, middlewarePlugin, navigateTo };
@@ -1,4 +1,4 @@
1
- import { i as createMiddlewareContext, n as middlewarePlugin, t as createMiddlewarePlugin } from "../plugin-BfxCqV0B.mjs";
1
+ import { i as createMiddlewareContext, n as middlewarePlugin, t as createMiddlewarePlugin } from "../plugin-0r5-meph.mjs";
2
2
  import { a as defineKimeshMiddleware, i as abortNavigation, n as executeMiddleware, o as navigateTo, r as executeMiddlewareChain, t as createMiddlewareExecutor } from "../middleware-DdlzhfiB.mjs";
3
3
 
4
4
  export { abortNavigation, createMiddlewareContext, createMiddlewareExecutor, createMiddlewarePlugin, defineKimeshMiddleware, executeMiddleware, executeMiddlewareChain, middlewarePlugin, navigateTo };
@@ -1,2 +1,2 @@
1
- import { _ as createMiddlewarePlugin, g as MiddlewarePluginOptions, v as middlewarePlugin } from "../index-9xJk3v3Z.mjs";
1
+ import { _ as createMiddlewarePlugin, g as MiddlewarePluginOptions, v as middlewarePlugin } from "../index-Bi0AaYQy.mjs";
2
2
  export { MiddlewarePluginOptions, createMiddlewarePlugin, middlewarePlugin as default, middlewarePlugin };
@@ -1,3 +1,3 @@
1
- import { n as middlewarePlugin, r as plugin_default, t as createMiddlewarePlugin } from "../plugin-BfxCqV0B.mjs";
1
+ import { n as middlewarePlugin, r as plugin_default, t as createMiddlewarePlugin } from "../plugin-0r5-meph.mjs";
2
2
 
3
3
  export { createMiddlewarePlugin, plugin_default as default, middlewarePlugin };
@@ -171,36 +171,31 @@ async function extractRouteMiddleware(route, namedMiddleware) {
171
171
  return normalizeMiddleware(middlewareOption, namedMiddleware);
172
172
  }
173
173
  /**
174
+ * Load named middleware by name
175
+ */
176
+ async function loadNamedMiddleware(name, namedMiddleware) {
177
+ const loader = namedMiddleware[name];
178
+ if (!loader) {
179
+ if (__KIMESH_DEV__) console.warn(`[Kimesh] Unknown middleware: ${name}`);
180
+ return null;
181
+ }
182
+ return loader();
183
+ }
184
+ /**
174
185
  * Normalize middleware to array of functions
175
186
  */
176
187
  async function normalizeMiddleware(middleware, namedMiddleware) {
177
- const result = [];
178
188
  if (typeof middleware === "string") {
179
- const loader = namedMiddleware[middleware];
180
- if (!loader) {
181
- if (__KIMESH_DEV__) console.warn(`[Kimesh] Unknown middleware: ${middleware}`);
182
- return [];
183
- }
184
- const mw = await loader();
185
- result.push(mw);
186
- return result;
187
- }
188
- if (typeof middleware === "function") {
189
- result.push(middleware);
190
- return result;
191
- }
192
- if (Array.isArray(middleware)) {
193
- for (const item of middleware) if (typeof item === "string") {
194
- const loader = namedMiddleware[item];
195
- if (!loader) {
196
- if (__KIMESH_DEV__) console.warn(`[Kimesh] Unknown middleware: ${item}`);
197
- continue;
198
- }
199
- const mw = await loader();
200
- result.push(mw);
201
- } else if (typeof item === "function") result.push(item);
202
- return result;
189
+ const mw = await loadNamedMiddleware(middleware, namedMiddleware);
190
+ return mw ? [mw] : [];
203
191
  }
192
+ if (typeof middleware === "function") return [middleware];
193
+ if (!Array.isArray(middleware)) return [];
194
+ const result = [];
195
+ for (const item of middleware) if (typeof item === "string") {
196
+ const mw = await loadNamedMiddleware(item, namedMiddleware);
197
+ if (mw) result.push(mw);
198
+ } else if (typeof item === "function") result.push(item);
204
199
  return result;
205
200
  }
206
201
  var plugin_default = middlewarePlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kimesh/router-runtime",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Runtime router for Kimesh framework",
5
5
  "repository": {
6
6
  "type": "git",