@kimesh/head 0.0.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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @kimesh/head
2
+
3
+ Part of the [Kimesh](https://github.com/kimesh/kimesh) framework.
package/augment.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @kimesh/head - Type Augmentation
3
+ *
4
+ * Augments Kimesh types to include head management.
5
+ */
6
+
7
+ import type { Head, MergeHead, VueHeadClient } from '@unhead/vue'
8
+
9
+ import type { KmHead, KmTitle, KmMeta, KmLink, KmScript, KmStyle, KmHtml, KmBody } from '@kimesh/head'
10
+
11
+ declare module '@kimesh/router-runtime' {
12
+ interface KimeshAppContext {
13
+ /** Unhead client instance */
14
+ head: VueHeadClient
15
+ }
16
+ }
17
+
18
+ declare module 'vue' {
19
+ export interface GlobalComponents {
20
+ KmHead: typeof KmHead
21
+ KmTitle: typeof KmTitle
22
+ KmMeta: typeof KmMeta
23
+ KmLink: typeof KmLink
24
+ KmScript: typeof KmScript
25
+ KmStyle: typeof KmStyle
26
+ KmHtml: typeof KmHtml
27
+ KmBody: typeof KmBody
28
+ }
29
+ }
30
+
31
+ export interface KimeshHeadConfig {
32
+ /** Enable head management (default: true) */
33
+ enabled?: boolean
34
+
35
+ /** Global title template */
36
+ titleTemplate?: string | ((title: string) => string)
37
+
38
+ /** Template parameters */
39
+ templateParams?: Record<string, string>
40
+ }
41
+
42
+ export type KimeshHeadOptions = MergeHead & Head
@@ -0,0 +1,294 @@
1
+ import { HeadClient, KIMESH_HEAD_KEY, KimeshHeadPlugin } from "./plugin.mjs";
2
+ import { RouteHeadConfig } from "@kimesh/router-runtime";
3
+ import "@unhead/vue/client";
4
+ import * as vue6 from "vue";
5
+ import { PropType, Ref } from "vue";
6
+ import { ActiveHeadEntry, Head, Head as Head$1, HeadEntryOptions, HeadSafe, MergeHead, MergeHead as MergeHead$1, UseHeadInput, UseHeadInput as UseHeadInput$1, UseHeadSafeInput, UseHeadSafeInput as UseHeadSafeInput$1, UseSeoMetaInput, UseSeoMetaInput as UseSeoMetaInput$1 } from "@unhead/vue";
7
+
8
+ //#region src/composables/use-head.d.ts
9
+ /**
10
+ * Set head meta tags with full reactivity support
11
+ *
12
+ * @example Basic usage
13
+ * ```ts
14
+ * useHead({
15
+ * title: 'My Page',
16
+ * meta: [
17
+ * { name: 'description', content: 'Page description' }
18
+ * ]
19
+ * })
20
+ * ```
21
+ *
22
+ * @example Reactive values
23
+ * ```ts
24
+ * const title = ref('Dynamic Title')
25
+ * useHead({
26
+ * title: () => title.value
27
+ * })
28
+ * ```
29
+ */
30
+ declare function useHead<T extends UseHeadInput$1>(input: T): void;
31
+ //#endregion
32
+ //#region src/composables/use-seo-meta.d.ts
33
+ /**
34
+ * Flat API for SEO meta tags
35
+ *
36
+ * Provides a cleaner, flat interface for setting SEO-related meta tags
37
+ * without needing to manually structure the meta array.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * useSeoMeta({
42
+ * title: 'My Site',
43
+ * description: 'Site description',
44
+ * ogTitle: 'Open Graph Title',
45
+ * ogDescription: 'OG Description',
46
+ * ogImage: 'https://example.com/image.png',
47
+ * twitterCard: 'summary_large_image',
48
+ * })
49
+ * ```
50
+ */
51
+ declare function useSeoMeta(input: UseSeoMetaInput$1): void;
52
+ //#endregion
53
+ //#region src/composables/use-head-safe.d.ts
54
+ /**
55
+ * XSS-safe version of useHead - sanitizes all input
56
+ *
57
+ * Use this when head values come from user input or external sources.
58
+ * This prevents XSS attacks by sanitizing potentially malicious content.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const userTitle = ref(userInput) // potentially unsafe
63
+ *
64
+ * useHeadSafe({
65
+ * title: userTitle,
66
+ * meta: [
67
+ * { name: 'description', content: userDescription }
68
+ * ]
69
+ * })
70
+ * ```
71
+ */
72
+ declare function useHeadSafe(input: UseHeadSafeInput$1): void;
73
+ //#endregion
74
+ //#region src/composables/use-route-head.d.ts
75
+ /**
76
+ * Apply head configuration from current route
77
+ *
78
+ * Call this in your root layout/app component to enable route-based head.
79
+ *
80
+ * @example
81
+ * ```vue
82
+ * <script setup>
83
+ * import { useRouteHead } from '@kimesh/head'
84
+ *
85
+ * useRouteHead()
86
+ * </script>
87
+ * ```
88
+ */
89
+ declare function useRouteHead(): Ref<RouteHeadConfig | undefined>;
90
+ //#endregion
91
+ //#region src/components/KmHead.d.ts
92
+ /**
93
+ * KmHead - Container component for head tags
94
+ *
95
+ * This is a wrapper component that renders slot content.
96
+ * Use the specific components (KmTitle, KmMeta, etc.) inside.
97
+ */
98
+ declare const KmHead: vue6.DefineComponent<{}, () => vue6.VNode<vue6.RendererNode, vue6.RendererElement, {
99
+ [key: string]: any;
100
+ }>[] | null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
101
+ //#endregion
102
+ //#region src/components/KmTitle.d.ts
103
+ /**
104
+ * KmTitle - Set page title
105
+ *
106
+ * @example
107
+ * ```vue
108
+ * <KmTitle>My Page Title</KmTitle>
109
+ * ```
110
+ */
111
+ declare const KmTitle: vue6.DefineComponent<{}, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
112
+ //#endregion
113
+ //#region src/components/KmMeta.d.ts
114
+ /**
115
+ * KmMeta - Set meta tags
116
+ *
117
+ * @example
118
+ * ```vue
119
+ * <KmMeta name="description" content="Page description" />
120
+ * <KmMeta property="og:title" content="OG Title" />
121
+ * <KmMeta charset="utf-8" />
122
+ * ```
123
+ */
124
+ declare const KmMeta: vue6.DefineComponent<vue6.ExtractPropTypes<{
125
+ name: StringConstructor;
126
+ property: StringConstructor;
127
+ httpEquiv: StringConstructor;
128
+ content: StringConstructor;
129
+ charset: StringConstructor;
130
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
131
+ name: StringConstructor;
132
+ property: StringConstructor;
133
+ httpEquiv: StringConstructor;
134
+ content: StringConstructor;
135
+ charset: StringConstructor;
136
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
137
+ //#endregion
138
+ //#region src/components/KmLink.d.ts
139
+ type AsValue = 'object' | 'audio' | 'document' | 'embed' | 'fetch' | 'font' | 'image' | 'script' | 'style' | 'track' | 'video' | 'worker';
140
+ /**
141
+ * KmLink - Set link tags (stylesheets, preload, favicon, etc.)
142
+ *
143
+ * @example
144
+ * ```vue
145
+ * <KmLink rel="stylesheet" href="/styles.css" />
146
+ * <KmLink rel="icon" type="image/x-icon" href="/favicon.ico" />
147
+ * <KmLink rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
148
+ * ```
149
+ */
150
+ declare const KmLink: vue6.DefineComponent<vue6.ExtractPropTypes<{
151
+ rel: StringConstructor;
152
+ href: StringConstructor;
153
+ type: StringConstructor;
154
+ as: PropType<AsValue>;
155
+ crossorigin: PropType<"" | "anonymous" | "use-credentials">;
156
+ media: StringConstructor;
157
+ sizes: StringConstructor;
158
+ hreflang: StringConstructor;
159
+ title: StringConstructor;
160
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
161
+ rel: StringConstructor;
162
+ href: StringConstructor;
163
+ type: StringConstructor;
164
+ as: PropType<AsValue>;
165
+ crossorigin: PropType<"" | "anonymous" | "use-credentials">;
166
+ media: StringConstructor;
167
+ sizes: StringConstructor;
168
+ hreflang: StringConstructor;
169
+ title: StringConstructor;
170
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
171
+ //#endregion
172
+ //#region src/components/KmScript.d.ts
173
+ type ReferrerPolicy = '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
174
+ /**
175
+ * KmScript - Set script tags
176
+ *
177
+ * @example External script
178
+ * ```vue
179
+ * <KmScript src="https://example.com/script.js" async />
180
+ * ```
181
+ *
182
+ * @example Inline script
183
+ * ```vue
184
+ * <KmScript>console.log('Hello')</KmScript>
185
+ * ```
186
+ */
187
+ declare const KmScript: vue6.DefineComponent<vue6.ExtractPropTypes<{
188
+ src: StringConstructor;
189
+ type: StringConstructor;
190
+ async: BooleanConstructor;
191
+ defer: BooleanConstructor;
192
+ crossorigin: PropType<"" | "anonymous" | "use-credentials">;
193
+ integrity: StringConstructor;
194
+ nomodule: BooleanConstructor;
195
+ nonce: StringConstructor;
196
+ referrerpolicy: PropType<ReferrerPolicy>;
197
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
198
+ src: StringConstructor;
199
+ type: StringConstructor;
200
+ async: BooleanConstructor;
201
+ defer: BooleanConstructor;
202
+ crossorigin: PropType<"" | "anonymous" | "use-credentials">;
203
+ integrity: StringConstructor;
204
+ nomodule: BooleanConstructor;
205
+ nonce: StringConstructor;
206
+ referrerpolicy: PropType<ReferrerPolicy>;
207
+ }>> & Readonly<{}>, {
208
+ async: boolean;
209
+ defer: boolean;
210
+ nomodule: boolean;
211
+ }, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
212
+ //#endregion
213
+ //#region src/components/KmStyle.d.ts
214
+ /**
215
+ * KmStyle - Set inline styles
216
+ *
217
+ * @example
218
+ * ```vue
219
+ * <KmStyle>
220
+ * body { background: #fff; }
221
+ * </KmStyle>
222
+ * ```
223
+ *
224
+ * @example With media query
225
+ * ```vue
226
+ * <KmStyle media="print">
227
+ * body { font-size: 12pt; }
228
+ * </KmStyle>
229
+ * ```
230
+ */
231
+ declare const KmStyle: vue6.DefineComponent<vue6.ExtractPropTypes<{
232
+ type: StringConstructor;
233
+ media: StringConstructor;
234
+ nonce: StringConstructor;
235
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
236
+ type: StringConstructor;
237
+ media: StringConstructor;
238
+ nonce: StringConstructor;
239
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
240
+ //#endregion
241
+ //#region src/components/KmHtml.d.ts
242
+ /**
243
+ * KmHtml - Set HTML element attributes
244
+ *
245
+ * @example
246
+ * ```vue
247
+ * <KmHtml lang="en" dir="ltr" />
248
+ * <KmHtml lang="vi" class="dark-mode" />
249
+ * ```
250
+ */
251
+ declare const KmHtml: vue6.DefineComponent<vue6.ExtractPropTypes<{
252
+ lang: StringConstructor;
253
+ dir: PropType<"ltr" | "rtl" | "auto">;
254
+ class: StringConstructor;
255
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
256
+ lang: StringConstructor;
257
+ dir: PropType<"ltr" | "rtl" | "auto">;
258
+ class: StringConstructor;
259
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
260
+ //#endregion
261
+ //#region src/components/KmBody.d.ts
262
+ /**
263
+ * KmBody - Set body element attributes
264
+ *
265
+ * @example
266
+ * ```vue
267
+ * <KmBody class="dark-mode" />
268
+ * ```
269
+ */
270
+ declare const KmBody: vue6.DefineComponent<vue6.ExtractPropTypes<{
271
+ class: StringConstructor;
272
+ }>, () => null, {}, {}, {}, vue6.ComponentOptionsMixin, vue6.ComponentOptionsMixin, {}, string, vue6.PublicProps, Readonly<vue6.ExtractPropTypes<{
273
+ class: StringConstructor;
274
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, vue6.ComponentProvideOptions, true, {}, any>;
275
+ //#endregion
276
+ //#region src/types/index.d.ts
277
+ /**
278
+ * Kimesh head module configuration
279
+ */
280
+ interface KimeshHeadConfig {
281
+ /** Enable head management (default: true) */
282
+ enabled?: boolean;
283
+ /** Global title template */
284
+ titleTemplate?: string | ((title: string) => string);
285
+ /** Template parameters */
286
+ templateParams?: Record<string, string>;
287
+ }
288
+ /**
289
+ * Head options that can be set in kimesh.config.ts app.head
290
+ */
291
+ type KimeshHeadOptions = MergeHead$1 & Head$1;
292
+ //#endregion
293
+ export { type ActiveHeadEntry, type Head, type HeadClient, type HeadEntryOptions, type HeadSafe, KIMESH_HEAD_KEY, type KimeshHeadConfig, type KimeshHeadOptions, KimeshHeadPlugin, KmBody, KmHead, KmHtml, KmLink, KmMeta, KmScript, KmStyle, KmTitle, type MergeHead, type UseHeadInput, type UseHeadSafeInput, type UseSeoMetaInput, useHead, useHeadSafe, useRouteHead, useSeoMeta };
294
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/composables/use-head.ts","../src/composables/use-seo-meta.ts","../src/composables/use-head-safe.ts","../src/composables/use-route-head.ts","../src/components/KmHead.ts","../src/components/KmTitle.ts","../src/components/KmMeta.ts","../src/components/KmLink.ts","../src/components/KmScript.ts","../src/components/KmStyle.ts","../src/components/KmHtml.ts","../src/components/KmBody.ts","../src/types/index.ts"],"mappings":";;;;;;;;;AA+BA;;;;ACLA;;;;ACAA;;;;ACwCA;;;;ACzDA;;;;iBJsBgB,OAAA,WAAkB,cAAA,CAAA,CAAA,KAAA,EAAqB,CAAA;;;;ACLvD;;;;ACAA;;;;ACwCA;;;;ACzDA;;;;;iBHiBgB,UAAA,CAAA,KAAA,EAAkB,iBAAA;;;;ACAlC;;;;ACwCA;;;;ACzDA;;;;;;;;;iBFiBgB,WAAA,CAAA,KAAA,EAAmB,kBAAA;;;;ACwCnC;;;;ACzDA;;;;;;;;;iBDyDgB,YAAA,CAAA,GAAgB,GAAA,CAAI,eAAA;;;;ACzDpC;;;;;cAAa,MAAA,OAAM,eAAA,WAAA,IAAA,CAAA,KAAA,CAMjB,IAAA,CANiB,YAAA,EAAA,IAAA,CAAA,eAAA;EAAA,CAAA,GAAA;AAAA,yBAAA,IAAA,CAAA,qBAAA;;;;ACcnB;;;;;;;cAAa,OAAA,EAAO,IAAA,CAAA,eAAA,6BAclB,IAAA,CAdkB,qBAAA,EAAA,IAAA,CAAA,qBAAA,cAAA,IAAA,CAAA,WAAA,EAAA,QAAA,OAAA,QAAA,8BAAA,IAAA,CAAA,uBAAA;;;;ACVpB;;;;;;;;;cAAa,MAAA,OAAM,eAAA,MAAA,gBAAA;EAAA,IAAA;;;;;4BAAA,IAAA,CAAA,qBAAA;;;;;;;;;KCVd,OAAA;AAAA;AAYL;;;;;;;;;AAZK,cAYQ,MAAA,OAAM,eAAA,MAAA,gBAAA;EAAA,GAAA;;;MAOD,QAAA,CAAS,OAAA;EAAA,WAAA,EACA,QAAA;EAAA,KAAA;;;;4BARR,IAAA,CAAA,qBAAA;;;;MAOD,QAAA,CAAS,OAAA;EAAA,WAAA,EACA,QAAA;EAAA,KAAA;;;;;;;KCpBtB,cAAA;AAAA;AA2BL;;;;;;;;;;;;AA3BK,cA2BQ,QAAA,OAAQ,eAAA,CAYiB,IAAA,CAZjB,gBAAA;EAAA,GAAA;;;;eAQM,QAAA;EAAA,SAAA;;;kBAIG,QAAA,CAAS,cAAA;AAAA,4BAZlB,IAAA,CAAA,qBAAA,qEAYiB,IAAA,CAAA,gBAAA;EAAA,GAAA;;;;eAJX,QAAA;EAAA,SAAA;;;kBAIG,QAAA,CAAS,cAAA;AAAA;;;;;;;;ACVvC;;;;;;;;;;;;;;;;cAAa,OAAA,OAAO,eAAA,MAAA,gBAAA;EAAA,IAAA;;;4BAAA,IAAA,CAAA,qBAAA;;;;;;;;ACpBpB;;;;;;;;cAAa,MAAA,OAAM,eAAA,MAAA,gBAAA;EAAA,IAAA;OAKA,QAAA;EAAA,KAAA;4BALA,IAAA,CAAA,qBAAA;;OAKA,QAAA;EAAA,KAAA;;;;;ACNnB;;;;;;;cAAa,MAAA,OAAM,eAAA,MAAA,gBAAA;EAAA,KAAA;4BAAA,IAAA,CAAA,qBAAA;;;;;;ACoBnB;AAcA;UAdiB,gBAAA;EAAA;EAAA,OAAA;EAAA;EAAA,aAAA,cAAA,KAAA;EAAA;EAAA,cAAA,GAQE,MAAA;AAAA;AAAA;AAMnB;;AANmB,KAMP,iBAAA,GAAoB,WAAA,GAAY,MAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,430 @@
1
+ import { KIMESH_HEAD_KEY, KimeshHeadPlugin } from "./plugin.mjs";
2
+ import { tryUseKimeshApp } from "@kimesh/router-runtime";
3
+ import { computed, defineComponent, getCurrentInstance } from "vue";
4
+ import { useHead as useHead$1, useHeadSafe as useHeadSafe$1, useSeoMeta as useSeoMeta$1 } from "@unhead/vue";
5
+ import { useRoute } from "vue-router";
6
+
7
+ //#region src/composables/use-head.ts
8
+ /**
9
+ * @kimesh/head - useHead composable
10
+ *
11
+ * Set head meta tags with full reactivity support.
12
+ */
13
+ /**
14
+ * Set head meta tags with full reactivity support
15
+ *
16
+ * @example Basic usage
17
+ * ```ts
18
+ * useHead({
19
+ * title: 'My Page',
20
+ * meta: [
21
+ * { name: 'description', content: 'Page description' }
22
+ * ]
23
+ * })
24
+ * ```
25
+ *
26
+ * @example Reactive values
27
+ * ```ts
28
+ * const title = ref('Dynamic Title')
29
+ * useHead({
30
+ * title: () => title.value
31
+ * })
32
+ * ```
33
+ */
34
+ function useHead(input) {
35
+ if (!getCurrentInstance()) {
36
+ const app = tryUseKimeshApp();
37
+ if (app?.head) {
38
+ app.head.push(input);
39
+ return;
40
+ }
41
+ console.warn("[Kimesh Head] useHead called outside component context");
42
+ return;
43
+ }
44
+ useHead$1(input);
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/composables/use-seo-meta.ts
49
+ /**
50
+ * @kimesh/head - useSeoMeta composable
51
+ *
52
+ * Flat API for SEO meta tags.
53
+ */
54
+ /**
55
+ * Flat API for SEO meta tags
56
+ *
57
+ * Provides a cleaner, flat interface for setting SEO-related meta tags
58
+ * without needing to manually structure the meta array.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * useSeoMeta({
63
+ * title: 'My Site',
64
+ * description: 'Site description',
65
+ * ogTitle: 'Open Graph Title',
66
+ * ogDescription: 'OG Description',
67
+ * ogImage: 'https://example.com/image.png',
68
+ * twitterCard: 'summary_large_image',
69
+ * })
70
+ * ```
71
+ */
72
+ function useSeoMeta(input) {
73
+ useSeoMeta$1(input);
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/composables/use-head-safe.ts
78
+ /**
79
+ * @kimesh/head - useHeadSafe composable
80
+ *
81
+ * XSS-safe version of useHead that sanitizes all input.
82
+ */
83
+ /**
84
+ * XSS-safe version of useHead - sanitizes all input
85
+ *
86
+ * Use this when head values come from user input or external sources.
87
+ * This prevents XSS attacks by sanitizing potentially malicious content.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const userTitle = ref(userInput) // potentially unsafe
92
+ *
93
+ * useHeadSafe({
94
+ * title: userTitle,
95
+ * meta: [
96
+ * { name: 'description', content: userDescription }
97
+ * ]
98
+ * })
99
+ * ```
100
+ */
101
+ function useHeadSafe(input) {
102
+ try {
103
+ useHeadSafe$1(input);
104
+ } catch (error) {
105
+ console.error("[Kimesh Head] Failed to set head:", error);
106
+ }
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/composables/use-route-head.ts
111
+ /**
112
+ * @kimesh/head - useRouteHead composable
113
+ *
114
+ * Applies head configuration from matched routes.
115
+ */
116
+ /**
117
+ * Get head configuration from route
118
+ */
119
+ function getRouteHead(route) {
120
+ const heads = [];
121
+ for (const record of route.matched) {
122
+ const routeDef = record.meta?.__kimesh;
123
+ if (!routeDef?.head) continue;
124
+ const headConfig = typeof routeDef.head === "function" ? routeDef.head({
125
+ params: route.params,
126
+ loaderData: void 0,
127
+ route
128
+ }) : routeDef.head;
129
+ heads.push(headConfig);
130
+ }
131
+ if (heads.length === 0) return void 0;
132
+ return heads.reduce((merged, head) => ({
133
+ ...merged,
134
+ ...head,
135
+ meta: [...merged.meta || [], ...head.meta || []],
136
+ link: [...merged.link || [], ...head.link || []],
137
+ script: [...merged.script || [], ...head.script || []],
138
+ style: [...merged.style || [], ...head.style || []]
139
+ }), {});
140
+ }
141
+ /**
142
+ * Apply head configuration from current route
143
+ *
144
+ * Call this in your root layout/app component to enable route-based head.
145
+ *
146
+ * @example
147
+ * ```vue
148
+ * <script setup>
149
+ * import { useRouteHead } from '@kimesh/head'
150
+ *
151
+ * useRouteHead()
152
+ * <\/script>
153
+ * ```
154
+ */
155
+ function useRouteHead() {
156
+ const route = useRoute();
157
+ const routeHead = computed(() => getRouteHead(route));
158
+ useHead((() => routeHead.value || {}));
159
+ return routeHead;
160
+ }
161
+
162
+ //#endregion
163
+ //#region src/components/KmHead.ts
164
+ /**
165
+ * KmHead - Container component for head tags
166
+ *
167
+ * This is a wrapper component that renders slot content.
168
+ * Use the specific components (KmTitle, KmMeta, etc.) inside.
169
+ */
170
+ const KmHead = defineComponent({
171
+ name: "KmHead",
172
+ setup(_, { slots }) {
173
+ return () => slots.default ? slots.default() : null;
174
+ }
175
+ });
176
+
177
+ //#endregion
178
+ //#region src/components/KmTitle.ts
179
+ function extractTextContent$2(vnodes) {
180
+ let text = "";
181
+ for (const vnode of vnodes) if (typeof vnode.children === "string") text += vnode.children;
182
+ else if (Array.isArray(vnode.children)) text += extractTextContent$2(vnode.children);
183
+ return text;
184
+ }
185
+ /**
186
+ * KmTitle - Set page title
187
+ *
188
+ * @example
189
+ * ```vue
190
+ * <KmTitle>My Page Title</KmTitle>
191
+ * ```
192
+ */
193
+ const KmTitle = defineComponent({
194
+ name: "KmTitle",
195
+ setup(_, { slots }) {
196
+ useHead({ title: () => {
197
+ const content = slots.default?.();
198
+ if (!content) return "";
199
+ return extractTextContent$2(content);
200
+ } });
201
+ return () => null;
202
+ }
203
+ });
204
+
205
+ //#endregion
206
+ //#region src/components/KmMeta.ts
207
+ /**
208
+ * KmMeta - Set meta tags
209
+ *
210
+ * @example
211
+ * ```vue
212
+ * <KmMeta name="description" content="Page description" />
213
+ * <KmMeta property="og:title" content="OG Title" />
214
+ * <KmMeta charset="utf-8" />
215
+ * ```
216
+ */
217
+ const KmMeta = defineComponent({
218
+ name: "KmMeta",
219
+ props: {
220
+ name: String,
221
+ property: String,
222
+ httpEquiv: String,
223
+ content: String,
224
+ charset: String
225
+ },
226
+ setup(props) {
227
+ useHead({ meta: [{
228
+ ...props.name && { name: props.name },
229
+ ...props.property && { property: props.property },
230
+ ...props.httpEquiv && { "http-equiv": props.httpEquiv },
231
+ ...props.content && { content: props.content },
232
+ ...props.charset && { charset: props.charset }
233
+ }] });
234
+ return () => null;
235
+ }
236
+ });
237
+
238
+ //#endregion
239
+ //#region src/components/KmLink.ts
240
+ /**
241
+ * KmLink - Set link tags (stylesheets, preload, favicon, etc.)
242
+ *
243
+ * @example
244
+ * ```vue
245
+ * <KmLink rel="stylesheet" href="/styles.css" />
246
+ * <KmLink rel="icon" type="image/x-icon" href="/favicon.ico" />
247
+ * <KmLink rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
248
+ * ```
249
+ */
250
+ const KmLink = defineComponent({
251
+ name: "KmLink",
252
+ props: {
253
+ rel: String,
254
+ href: String,
255
+ type: String,
256
+ as: String,
257
+ crossorigin: String,
258
+ media: String,
259
+ sizes: String,
260
+ hreflang: String,
261
+ title: String
262
+ },
263
+ setup(props) {
264
+ useHead({ link: [{
265
+ ...props.rel && { rel: props.rel },
266
+ ...props.href && { href: props.href },
267
+ ...props.type && { type: props.type },
268
+ ...props.as && { as: props.as },
269
+ ...props.crossorigin && { crossorigin: props.crossorigin },
270
+ ...props.media && { media: props.media },
271
+ ...props.sizes && { sizes: props.sizes },
272
+ ...props.hreflang && { hreflang: props.hreflang },
273
+ ...props.title && { title: props.title }
274
+ }] });
275
+ return () => null;
276
+ }
277
+ });
278
+
279
+ //#endregion
280
+ //#region src/components/KmScript.ts
281
+ function extractTextContent$1(vnodes) {
282
+ let text = "";
283
+ for (const vnode of vnodes) if (typeof vnode.children === "string") text += vnode.children;
284
+ else if (Array.isArray(vnode.children)) text += extractTextContent$1(vnode.children);
285
+ return text;
286
+ }
287
+ /**
288
+ * KmScript - Set script tags
289
+ *
290
+ * @example External script
291
+ * ```vue
292
+ * <KmScript src="https://example.com/script.js" async />
293
+ * ```
294
+ *
295
+ * @example Inline script
296
+ * ```vue
297
+ * <KmScript>console.log('Hello')</KmScript>
298
+ * ```
299
+ */
300
+ const KmScript = defineComponent({
301
+ name: "KmScript",
302
+ props: {
303
+ src: String,
304
+ type: String,
305
+ async: Boolean,
306
+ defer: Boolean,
307
+ crossorigin: String,
308
+ integrity: String,
309
+ nomodule: Boolean,
310
+ nonce: String,
311
+ referrerpolicy: String
312
+ },
313
+ setup(props, { slots }) {
314
+ useHead({ script: [{
315
+ ...props.src && { src: props.src },
316
+ ...props.type && { type: props.type },
317
+ ...props.async && { async: props.async },
318
+ ...props.defer && { defer: props.defer },
319
+ ...props.crossorigin && { crossorigin: props.crossorigin },
320
+ ...props.integrity && { integrity: props.integrity },
321
+ ...props.nomodule && { nomodule: props.nomodule },
322
+ ...props.nonce && { nonce: props.nonce },
323
+ ...props.referrerpolicy && { referrerpolicy: props.referrerpolicy },
324
+ innerHTML: () => {
325
+ const content = slots.default?.();
326
+ if (!content) return void 0;
327
+ return extractTextContent$1(content);
328
+ }
329
+ }] });
330
+ return () => null;
331
+ }
332
+ });
333
+
334
+ //#endregion
335
+ //#region src/components/KmStyle.ts
336
+ function extractTextContent(vnodes) {
337
+ let text = "";
338
+ for (const vnode of vnodes) if (typeof vnode.children === "string") text += vnode.children;
339
+ else if (Array.isArray(vnode.children)) text += extractTextContent(vnode.children);
340
+ return text;
341
+ }
342
+ /**
343
+ * KmStyle - Set inline styles
344
+ *
345
+ * @example
346
+ * ```vue
347
+ * <KmStyle>
348
+ * body { background: #fff; }
349
+ * </KmStyle>
350
+ * ```
351
+ *
352
+ * @example With media query
353
+ * ```vue
354
+ * <KmStyle media="print">
355
+ * body { font-size: 12pt; }
356
+ * </KmStyle>
357
+ * ```
358
+ */
359
+ const KmStyle = defineComponent({
360
+ name: "KmStyle",
361
+ props: {
362
+ type: String,
363
+ media: String,
364
+ nonce: String
365
+ },
366
+ setup(props, { slots }) {
367
+ useHead({ style: [{
368
+ innerHTML: () => {
369
+ const content = slots.default?.();
370
+ if (!content) return "";
371
+ return extractTextContent(content);
372
+ },
373
+ ...props.type && { type: props.type },
374
+ ...props.media && { media: props.media },
375
+ ...props.nonce && { nonce: props.nonce }
376
+ }] });
377
+ return () => null;
378
+ }
379
+ });
380
+
381
+ //#endregion
382
+ //#region src/components/KmHtml.ts
383
+ /**
384
+ * KmHtml - Set HTML element attributes
385
+ *
386
+ * @example
387
+ * ```vue
388
+ * <KmHtml lang="en" dir="ltr" />
389
+ * <KmHtml lang="vi" class="dark-mode" />
390
+ * ```
391
+ */
392
+ const KmHtml = defineComponent({
393
+ name: "KmHtml",
394
+ props: {
395
+ lang: String,
396
+ dir: String,
397
+ class: String
398
+ },
399
+ setup(props) {
400
+ useHead({ htmlAttrs: {
401
+ ...props.lang && { lang: props.lang },
402
+ ...props.dir && { dir: props.dir },
403
+ ...props.class && { class: props.class }
404
+ } });
405
+ return () => null;
406
+ }
407
+ });
408
+
409
+ //#endregion
410
+ //#region src/components/KmBody.ts
411
+ /**
412
+ * KmBody - Set body element attributes
413
+ *
414
+ * @example
415
+ * ```vue
416
+ * <KmBody class="dark-mode" />
417
+ * ```
418
+ */
419
+ const KmBody = defineComponent({
420
+ name: "KmBody",
421
+ props: { class: String },
422
+ setup(props) {
423
+ useHead({ bodyAttrs: { ...props.class && { class: props.class } } });
424
+ return () => null;
425
+ }
426
+ });
427
+
428
+ //#endregion
429
+ export { KIMESH_HEAD_KEY, KimeshHeadPlugin, KmBody, KmHead, KmHtml, KmLink, KmMeta, KmScript, KmStyle, KmTitle, useHead, useHeadSafe, useRouteHead, useSeoMeta };
430
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["extractTextContent","extractTextContent"],"sources":["../src/composables/use-head.ts","../src/composables/use-seo-meta.ts","../src/composables/use-head-safe.ts","../src/composables/use-route-head.ts","../src/components/KmHead.ts","../src/components/KmTitle.ts","../src/components/KmMeta.ts","../src/components/KmLink.ts","../src/components/KmScript.ts","../src/components/KmStyle.ts","../src/components/KmHtml.ts","../src/components/KmBody.ts"],"sourcesContent":["/**\n * @kimesh/head - useHead composable\n *\n * Set head meta tags with full reactivity support.\n */\n\nimport { useHead as unheadUseHead, type UseHeadInput } from '@unhead/vue'\nimport { getCurrentInstance } from 'vue'\nimport { tryUseKimeshApp } from '@kimesh/router-runtime'\n\n/**\n * Set head meta tags with full reactivity support\n *\n * @example Basic usage\n * ```ts\n * useHead({\n * title: 'My Page',\n * meta: [\n * { name: 'description', content: 'Page description' }\n * ]\n * })\n * ```\n *\n * @example Reactive values\n * ```ts\n * const title = ref('Dynamic Title')\n * useHead({\n * title: () => title.value\n * })\n * ```\n */\nexport function useHead<T extends UseHeadInput>(input: T): void {\n const instance = getCurrentInstance()\n\n if (!instance) {\n const app = tryUseKimeshApp()\n if (app?.head) {\n app.head.push(input as Parameters<typeof app.head.push>[0])\n return\n }\n console.warn('[Kimesh Head] useHead called outside component context')\n return\n }\n\n unheadUseHead(input)\n}\n","/**\n * @kimesh/head - useSeoMeta composable\n *\n * Flat API for SEO meta tags.\n */\n\nimport { useSeoMeta as unheadUseSeoMeta, type UseSeoMetaInput } from '@unhead/vue'\n\n/**\n * Flat API for SEO meta tags\n *\n * Provides a cleaner, flat interface for setting SEO-related meta tags\n * without needing to manually structure the meta array.\n *\n * @example\n * ```ts\n * useSeoMeta({\n * title: 'My Site',\n * description: 'Site description',\n * ogTitle: 'Open Graph Title',\n * ogDescription: 'OG Description',\n * ogImage: 'https://example.com/image.png',\n * twitterCard: 'summary_large_image',\n * })\n * ```\n */\nexport function useSeoMeta(input: UseSeoMetaInput): void {\n unheadUseSeoMeta(input)\n}\n","/**\n * @kimesh/head - useHeadSafe composable\n *\n * XSS-safe version of useHead that sanitizes all input.\n */\n\nimport { useHeadSafe as unheadUseHeadSafe, type UseHeadSafeInput } from '@unhead/vue'\n\n/**\n * XSS-safe version of useHead - sanitizes all input\n *\n * Use this when head values come from user input or external sources.\n * This prevents XSS attacks by sanitizing potentially malicious content.\n *\n * @example\n * ```ts\n * const userTitle = ref(userInput) // potentially unsafe\n *\n * useHeadSafe({\n * title: userTitle,\n * meta: [\n * { name: 'description', content: userDescription }\n * ]\n * })\n * ```\n */\nexport function useHeadSafe(input: UseHeadSafeInput): void {\n try {\n unheadUseHeadSafe(input)\n } catch (error) {\n console.error('[Kimesh Head] Failed to set head:', error)\n }\n}\n","/**\n * @kimesh/head - useRouteHead composable\n *\n * Applies head configuration from matched routes.\n */\n\nimport { computed, watch, type Ref } from 'vue'\nimport { useRoute, type RouteLocationNormalizedLoaded } from 'vue-router'\nimport { useHead } from './use-head'\nimport type { RouteHeadConfig, RouteHeadFn, RouteHeadContext } from '@kimesh/router-runtime'\n\ninterface RouteDefinitionWithHead {\n head?: RouteHeadFn | RouteHeadConfig\n}\n\n/**\n * Get head configuration from route\n */\nfunction getRouteHead(\n route: RouteLocationNormalizedLoaded\n): RouteHeadConfig | undefined {\n // Collect head from all matched routes (parent -> child)\n const heads: RouteHeadConfig[] = []\n\n for (const record of route.matched) {\n const routeDef = record.meta?.__kimesh as RouteDefinitionWithHead | undefined\n if (!routeDef?.head) continue\n\n const headConfig = typeof routeDef.head === 'function'\n ? routeDef.head({\n params: route.params as Record<string, string>,\n loaderData: undefined, // TODO: integrate with loader data\n route,\n })\n : routeDef.head\n\n heads.push(headConfig)\n }\n\n if (heads.length === 0) return undefined\n\n // Merge all heads (later ones override earlier)\n return heads.reduce((merged, head) => ({\n ...merged,\n ...head,\n meta: [...(merged.meta || []), ...(head.meta || [])],\n link: [...(merged.link || []), ...(head.link || [])],\n script: [...(merged.script || []), ...(head.script || [])],\n style: [...(merged.style || []), ...(head.style || [])],\n }), {} as RouteHeadConfig)\n}\n\n/**\n * Apply head configuration from current route\n *\n * Call this in your root layout/app component to enable route-based head.\n *\n * @example\n * ```vue\n * <script setup>\n * import { useRouteHead } from '@kimesh/head'\n *\n * useRouteHead()\n * </script>\n * ```\n */\nexport function useRouteHead(): Ref<RouteHeadConfig | undefined> {\n const route = useRoute()\n\n const routeHead = computed(() => getRouteHead(route))\n\n // Apply head reactively when route changes\n useHead((() => routeHead.value || {}) as Parameters<typeof useHead>[0])\n\n return routeHead\n}\n","import { defineComponent, h, type Slots } from 'vue'\nimport { useHead } from '../composables/use-head'\n\n/**\n * KmHead - Container component for head tags\n *\n * This is a wrapper component that renders slot content.\n * Use the specific components (KmTitle, KmMeta, etc.) inside.\n */\nexport const KmHead = defineComponent({\n name: 'KmHead',\n\n setup(_, { slots }: { slots: Slots }) {\n return () => (slots.default ? slots.default() : null)\n },\n})\n","import { defineComponent, h, watchEffect, type Slots, type VNode } from 'vue'\nimport { useHead } from '../composables/use-head'\n\nfunction extractTextContent(vnodes: VNode[]): string {\n let text = ''\n for (const vnode of vnodes) {\n if (typeof vnode.children === 'string') {\n text += vnode.children\n } else if (Array.isArray(vnode.children)) {\n text += extractTextContent(vnode.children as VNode[])\n }\n }\n return text\n}\n\n/**\n * KmTitle - Set page title\n *\n * @example\n * ```vue\n * <KmTitle>My Page Title</KmTitle>\n * ```\n */\nexport const KmTitle = defineComponent({\n name: 'KmTitle',\n\n setup(_, { slots }: { slots: Slots }) {\n useHead({\n title: () => {\n const content = slots.default?.()\n if (!content) return ''\n return extractTextContent(content)\n },\n })\n\n return () => null\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport { useHead } from '../composables/use-head'\n\n/**\n * KmMeta - Set meta tags\n *\n * @example\n * ```vue\n * <KmMeta name=\"description\" content=\"Page description\" />\n * <KmMeta property=\"og:title\" content=\"OG Title\" />\n * <KmMeta charset=\"utf-8\" />\n * ```\n */\nexport const KmMeta = defineComponent({\n name: 'KmMeta',\n\n props: {\n name: String,\n property: String,\n httpEquiv: String,\n content: String,\n charset: String,\n },\n\n setup(props) {\n useHead({\n meta: [\n {\n ...(props.name && { name: props.name }),\n ...(props.property && { property: props.property }),\n ...(props.httpEquiv && { 'http-equiv': props.httpEquiv }),\n ...(props.content && { content: props.content }),\n ...(props.charset && { charset: props.charset }),\n },\n ],\n })\n\n return () => null\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport { useHead } from '../composables/use-head'\n\ntype AsValue = 'object' | 'audio' | 'document' | 'embed' | 'fetch' | 'font' | 'image' | 'script' | 'style' | 'track' | 'video' | 'worker'\n\n/**\n * KmLink - Set link tags (stylesheets, preload, favicon, etc.)\n *\n * @example\n * ```vue\n * <KmLink rel=\"stylesheet\" href=\"/styles.css\" />\n * <KmLink rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.ico\" />\n * <KmLink rel=\"preload\" href=\"/font.woff2\" as=\"font\" type=\"font/woff2\" crossorigin=\"anonymous\" />\n * ```\n */\nexport const KmLink = defineComponent({\n name: 'KmLink',\n\n props: {\n rel: String,\n href: String,\n type: String,\n as: String as PropType<AsValue>,\n crossorigin: String as PropType<'' | 'anonymous' | 'use-credentials'>,\n media: String,\n sizes: String,\n hreflang: String,\n title: String,\n },\n\n setup(props) {\n useHead({\n link: [\n {\n ...(props.rel && { rel: props.rel }),\n ...(props.href && { href: props.href }),\n ...(props.type && { type: props.type }),\n ...(props.as && { as: props.as }),\n ...(props.crossorigin && { crossorigin: props.crossorigin }),\n ...(props.media && { media: props.media }),\n ...(props.sizes && { sizes: props.sizes }),\n ...(props.hreflang && { hreflang: props.hreflang }),\n ...(props.title && { title: props.title }),\n },\n ],\n })\n\n return () => null\n },\n})\n","import { defineComponent, type PropType, type Slots, type VNode } from 'vue'\nimport { useHead } from '../composables/use-head'\n\ntype ReferrerPolicy = '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'\n\nfunction extractTextContent(vnodes: VNode[]): string {\n let text = ''\n for (const vnode of vnodes) {\n if (typeof vnode.children === 'string') {\n text += vnode.children\n } else if (Array.isArray(vnode.children)) {\n text += extractTextContent(vnode.children as VNode[])\n }\n }\n return text\n}\n\n/**\n * KmScript - Set script tags\n *\n * @example External script\n * ```vue\n * <KmScript src=\"https://example.com/script.js\" async />\n * ```\n *\n * @example Inline script\n * ```vue\n * <KmScript>console.log('Hello')</KmScript>\n * ```\n */\nexport const KmScript = defineComponent({\n name: 'KmScript',\n\n props: {\n src: String,\n type: String,\n async: Boolean,\n defer: Boolean,\n crossorigin: String as PropType<'' | 'anonymous' | 'use-credentials'>,\n integrity: String,\n nomodule: Boolean,\n nonce: String,\n referrerpolicy: String as PropType<ReferrerPolicy>,\n },\n\n setup(props, { slots }: { slots: Slots }) {\n useHead({\n script: [\n {\n ...(props.src && { src: props.src }),\n ...(props.type && { type: props.type }),\n ...(props.async && { async: props.async }),\n ...(props.defer && { defer: props.defer }),\n ...(props.crossorigin && { crossorigin: props.crossorigin }),\n ...(props.integrity && { integrity: props.integrity }),\n ...(props.nomodule && { nomodule: props.nomodule }),\n ...(props.nonce && { nonce: props.nonce }),\n ...(props.referrerpolicy && { referrerpolicy: props.referrerpolicy }),\n innerHTML: () => {\n const content = slots.default?.()\n if (!content) return undefined\n return extractTextContent(content)\n },\n },\n ],\n })\n\n return () => null\n },\n})\n","import { defineComponent, type Slots, type VNode } from 'vue'\nimport { useHead } from '../composables/use-head'\n\nfunction extractTextContent(vnodes: VNode[]): string {\n let text = ''\n for (const vnode of vnodes) {\n if (typeof vnode.children === 'string') {\n text += vnode.children\n } else if (Array.isArray(vnode.children)) {\n text += extractTextContent(vnode.children as VNode[])\n }\n }\n return text\n}\n\n/**\n * KmStyle - Set inline styles\n *\n * @example\n * ```vue\n * <KmStyle>\n * body { background: #fff; }\n * </KmStyle>\n * ```\n *\n * @example With media query\n * ```vue\n * <KmStyle media=\"print\">\n * body { font-size: 12pt; }\n * </KmStyle>\n * ```\n */\nexport const KmStyle = defineComponent({\n name: 'KmStyle',\n\n props: {\n type: String,\n media: String,\n nonce: String,\n },\n\n setup(props, { slots }: { slots: Slots }) {\n useHead({\n style: [\n {\n innerHTML: () => {\n const content = slots.default?.()\n if (!content) return ''\n return extractTextContent(content)\n },\n ...(props.type && { type: props.type }),\n ...(props.media && { media: props.media }),\n ...(props.nonce && { nonce: props.nonce }),\n },\n ],\n })\n\n return () => null\n },\n})\n","import { defineComponent, type PropType } from 'vue'\nimport { useHead } from '../composables/use-head'\n\n/**\n * KmHtml - Set HTML element attributes\n *\n * @example\n * ```vue\n * <KmHtml lang=\"en\" dir=\"ltr\" />\n * <KmHtml lang=\"vi\" class=\"dark-mode\" />\n * ```\n */\nexport const KmHtml = defineComponent({\n name: 'KmHtml',\n\n props: {\n lang: String,\n dir: String as PropType<'ltr' | 'rtl' | 'auto'>,\n class: String,\n },\n\n setup(props) {\n useHead({\n htmlAttrs: {\n ...(props.lang && { lang: props.lang }),\n ...(props.dir && { dir: props.dir }),\n ...(props.class && { class: props.class }),\n },\n })\n\n return () => null\n },\n})\n","import { defineComponent } from 'vue'\nimport { useHead } from '../composables/use-head'\n\n/**\n * KmBody - Set body element attributes\n *\n * @example\n * ```vue\n * <KmBody class=\"dark-mode\" />\n * ```\n */\nexport const KmBody = defineComponent({\n name: 'KmBody',\n\n props: {\n class: String,\n },\n\n setup(props) {\n useHead({\n bodyAttrs: {\n ...(props.class && { class: props.class }),\n },\n })\n\n return () => null\n },\n})\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { VueHeadClient as HeadClient } from "@unhead/vue/client";
2
+
3
+ //#region src/plugin.d.ts
4
+ /**
5
+ * @kimesh/head - Runtime Plugin
6
+ *
7
+ * CSR-only head management plugin using @unhead/vue.
8
+ */
9
+ declare const KIMESH_HEAD_KEY: unique symbol;
10
+ /**
11
+ * Kimesh Head Plugin
12
+ *
13
+ * Provides head meta management for Kimesh apps.
14
+ * Uses @unhead/vue for CSR-only head management.
15
+ */
16
+ declare const KimeshHeadPlugin: any;
17
+ //#endregion
18
+ export { type HeadClient, KIMESH_HEAD_KEY, KimeshHeadPlugin, KimeshHeadPlugin as default };
19
+ //# sourceMappingURL=plugin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;AAaA;AA6CA;;;cA7Ca,eAAA;AAAA;AA6Cb;;;;;AA7Ca,cA6CA,gBAAA"}
@@ -0,0 +1,71 @@
1
+ import { defineKimeshRuntimePlugin } from "@kimesh/router-runtime";
2
+ import { createHead, renderDOMHead } from "@unhead/vue/client";
3
+ import { nextTick } from "vue";
4
+
5
+ //#region src/plugin.ts
6
+ /**
7
+ * @kimesh/head - Runtime Plugin
8
+ *
9
+ * CSR-only head management plugin using @unhead/vue.
10
+ */
11
+ const KIMESH_HEAD_KEY = Symbol("kimesh:head");
12
+ /**
13
+ * Get merged head configuration from matched routes
14
+ */
15
+ function getRouteHead(route) {
16
+ const heads = [];
17
+ for (const record of route.matched) {
18
+ const routeDef = record.meta?.__kimesh;
19
+ if (!routeDef?.head) continue;
20
+ const headConfig = typeof routeDef.head === "function" ? routeDef.head({
21
+ params: route.params,
22
+ loaderData: void 0,
23
+ route
24
+ }) : routeDef.head;
25
+ heads.push(headConfig);
26
+ }
27
+ if (heads.length === 0) return void 0;
28
+ return heads.reduce((merged, head) => ({
29
+ ...merged,
30
+ ...head,
31
+ meta: [...merged.meta || [], ...head.meta || []],
32
+ link: [...merged.link || [], ...head.link || []],
33
+ script: [...merged.script || [], ...head.script || []],
34
+ style: [...merged.style || [], ...head.style || []]
35
+ }), {});
36
+ }
37
+ /**
38
+ * Kimesh Head Plugin
39
+ *
40
+ * Provides head meta management for Kimesh apps.
41
+ * Uses @unhead/vue for CSR-only head management.
42
+ */
43
+ const KimeshHeadPlugin = defineKimeshRuntimePlugin({
44
+ name: "kimesh:head",
45
+ enforce: "pre",
46
+ setup(app) {
47
+ const headOptions = app.$config.head;
48
+ const head = createHead();
49
+ app.vueApp.use(head);
50
+ if (headOptions) head.push(headOptions);
51
+ let routeHeadEntry = null;
52
+ app.router.afterEach((to) => {
53
+ if (routeHeadEntry) {
54
+ routeHeadEntry.dispose();
55
+ routeHeadEntry = null;
56
+ }
57
+ const routeHead = getRouteHead(to);
58
+ if (routeHead) routeHeadEntry = head.push(routeHead);
59
+ nextTick(() => {
60
+ renderDOMHead(head);
61
+ });
62
+ });
63
+ return { provide: { head } };
64
+ },
65
+ hooks: { "app:mounted": () => {} }
66
+ });
67
+ var plugin_default = KimeshHeadPlugin;
68
+
69
+ //#endregion
70
+ export { KIMESH_HEAD_KEY, KimeshHeadPlugin, plugin_default as default };
71
+ //# sourceMappingURL=plugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * @kimesh/head - Runtime Plugin\n *\n * CSR-only head management plugin using @unhead/vue.\n */\n\nimport { defineKimeshRuntimePlugin } from '@kimesh/router-runtime'\nimport { createHead, renderDOMHead, type VueHeadClient } from '@unhead/vue/client'\nimport { nextTick } from 'vue'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\nimport type { KimeshHeadOptions } from './types'\nimport type { RouteHeadConfig } from '@kimesh/router-runtime'\n\nexport const KIMESH_HEAD_KEY = Symbol('kimesh:head')\n\ninterface RouteDefinitionWithHead {\n head?: ((ctx: { params: Record<string, string>; loaderData: unknown; route: RouteLocationNormalizedLoaded }) => RouteHeadConfig) | RouteHeadConfig\n}\n\n/**\n * Get merged head configuration from matched routes\n */\nfunction getRouteHead(route: RouteLocationNormalizedLoaded): RouteHeadConfig | undefined {\n const heads: RouteHeadConfig[] = []\n\n for (const record of route.matched) {\n const routeDef = record.meta?.__kimesh as RouteDefinitionWithHead | undefined\n if (!routeDef?.head) continue\n\n const headConfig = typeof routeDef.head === 'function'\n ? routeDef.head({\n params: route.params as Record<string, string>,\n loaderData: undefined,\n route,\n })\n : routeDef.head\n\n heads.push(headConfig)\n }\n\n if (heads.length === 0) return undefined\n\n return heads.reduce((merged, head) => ({\n ...merged,\n ...head,\n meta: [...(merged.meta || []), ...(head.meta || [])],\n link: [...(merged.link || []), ...(head.link || [])],\n script: [...(merged.script || []), ...(head.script || [])],\n style: [...(merged.style || []), ...(head.style || [])],\n }), {} as RouteHeadConfig)\n}\n\n/**\n * Kimesh Head Plugin\n *\n * Provides head meta management for Kimesh apps.\n * Uses @unhead/vue for CSR-only head management.\n */\nexport const KimeshHeadPlugin = defineKimeshRuntimePlugin<{\n head: VueHeadClient\n}>({\n name: 'kimesh:head',\n enforce: 'pre',\n\n setup(app) {\n const headOptions = app.$config.head as KimeshHeadOptions | undefined\n\n const head = createHead()\n\n app.vueApp.use(head)\n\n if (headOptions) {\n head.push(headOptions)\n }\n\n let routeHeadEntry: ReturnType<typeof head.push> | null = null\n\n app.router.afterEach((to) => {\n if (routeHeadEntry) {\n routeHeadEntry.dispose()\n routeHeadEntry = null\n }\n\n const routeHead = getRouteHead(to)\n if (routeHead) {\n routeHeadEntry = head.push(routeHead as Parameters<typeof head.push>[0])\n }\n\n nextTick(() => {\n renderDOMHead(head)\n })\n })\n\n return {\n provide: { head },\n }\n },\n\n hooks: {\n 'app:mounted': () => {\n },\n },\n})\n\nexport default KimeshHeadPlugin\n\nexport type { VueHeadClient as HeadClient } from '@unhead/vue/client'\n"],"mappings":";;;;;;;;;;AAaA,MAAa,kBAAkB,OAAO,cAAc;;;;AASpD,SAAS,aAAa,OAAmE;CACvF,MAAM,QAA2B,EAAE;AAEnC,MAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,CAAC,UAAU,KAAM;EAErB,MAAM,aAAa,OAAO,SAAS,SAAS,aACxC,SAAS,KAAK;GACZ,QAAQ,MAAM;GACd,YAAY;GACZ;GACD,CAAC,GACF,SAAS;AAEb,QAAM,KAAK,WAAW;;AAGxB,KAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAO,MAAM,QAAQ,QAAQ,UAAU;EACrC,GAAG;EACH,GAAG;EACH,MAAM,CAAC,GAAI,OAAO,QAAQ,EAAE,EAAG,GAAI,KAAK,QAAQ,EAAE,CAAE;EACpD,MAAM,CAAC,GAAI,OAAO,QAAQ,EAAE,EAAG,GAAI,KAAK,QAAQ,EAAE,CAAE;EACpD,QAAQ,CAAC,GAAI,OAAO,UAAU,EAAE,EAAG,GAAI,KAAK,UAAU,EAAE,CAAE;EAC1D,OAAO,CAAC,GAAI,OAAO,SAAS,EAAE,EAAG,GAAI,KAAK,SAAS,EAAE,CAAE;EACxD,GAAG,EAAE,CAAoB;;;;;;;;AAS5B,MAAa,mBAAmB,0BAE7B;CACD,MAAM;CACN,SAAS;CAET,MAAM,KAAK;EACT,MAAM,cAAc,IAAI,QAAQ;EAEhC,MAAM,OAAO,YAAY;AAEzB,MAAI,OAAO,IAAI,KAAK;AAEpB,MAAI,YACF,MAAK,KAAK,YAAY;EAGxB,IAAI,iBAAsD;AAE1D,MAAI,OAAO,WAAW,OAAO;AAC3B,OAAI,gBAAgB;AAClB,mBAAe,SAAS;AACxB,qBAAiB;;GAGnB,MAAM,YAAY,aAAa,GAAG;AAClC,OAAI,UACF,kBAAiB,KAAK,KAAK,UAA6C;AAG1E,kBAAe;AACb,kBAAc,KAAK;KACnB;IACF;AAEF,SAAO,EACL,SAAS,EAAE,MAAM,EAClB;;CAGH,OAAO,EACL,qBAAqB,IAEtB;CACF,CAAC;AAEF,qBAAe"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@kimesh/head",
3
+ "version": "0.0.1",
4
+ "description": "Head meta management for Kimesh framework",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.mts",
9
+ "import": "./dist/index.mjs"
10
+ },
11
+ "./plugin": {
12
+ "types": "./dist/plugin.d.mts",
13
+ "import": "./dist/plugin.mjs"
14
+ },
15
+ "./augment": {
16
+ "types": "./augment.d.ts"
17
+ }
18
+ },
19
+ "main": "./dist/index.mjs",
20
+ "types": "./dist/index.d.mts",
21
+ "files": [
22
+ "dist",
23
+ "augment.d.ts"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsdown",
27
+ "dev": "tsdown --watch",
28
+ "typecheck": "vue-tsc --noEmit",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "dependencies": {
33
+ "@unhead/vue": "^2.0.8"
34
+ },
35
+ "peerDependencies": {
36
+ "vue": "^3.5.0",
37
+ "@kimesh/router-runtime": "0.0.1"
38
+ },
39
+ "devDependencies": {
40
+ "@kimesh/router-runtime": "0.0.1",
41
+ "@types/node": "^25.0.8",
42
+ "@vue/test-utils": "^2.4.6",
43
+ "jsdom": "^27.4.0",
44
+ "tsdown": "^0.20.0-beta.3",
45
+ "typescript": "^5.9.3",
46
+ "vitest": "^4.0.17",
47
+ "vue": "^3.5.26",
48
+ "vue-router": "^4.6.4",
49
+ "vue-tsc": "^3.2.2"
50
+ }
51
+ }