kmcom-nuxt-layers 1.6.17 → 1.6.20

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.
Files changed (58) hide show
  1. package/layers/content/app/components/Blog/Article.vue +9 -5
  2. package/layers/content/app/components/Blog/Card.vue +11 -8
  3. package/layers/content/app/components/Blog/List.vue +12 -11
  4. package/layers/content/app/components/Gallery/Card.vue +6 -1
  5. package/layers/content/app/components/Gallery/Detail.vue +10 -6
  6. package/layers/content/app/components/Gallery/ImageDetail.vue +9 -3
  7. package/layers/content/app/components/NuxtContent/Detail.vue +7 -3
  8. package/layers/content/app/components/NuxtContent/Toc.vue +3 -1
  9. package/layers/content/app/components/Portfolio/Card.vue +5 -1
  10. package/layers/content/app/components/Portfolio/Detail.vue +32 -11
  11. package/layers/content/app/components/Portfolio/List.vue +4 -2
  12. package/layers/content/app/composables/useCollectionItem.ts +3 -1
  13. package/layers/content/app/composables/useCollectionSurround.ts +4 -6
  14. package/layers/content/app/types/content.ts +1 -1
  15. package/layers/content/package.json +1 -0
  16. package/layers/core/app/app.vue +1 -0
  17. package/layers/core/app/composables/useCache.ts +7 -8
  18. package/layers/core/app/composables/useScrollGuard.ts +0 -12
  19. package/layers/core/app/plugins/init.ts +4 -3
  20. package/layers/core/app/plugins/loading.client.ts +0 -2
  21. package/layers/core/app/plugins/scroll-guard.client.ts +0 -2
  22. package/layers/core/package.json +1 -0
  23. package/layers/core/tsconfig.json +2 -1
  24. package/layers/forms/app/components/Form/Contact.vue +1 -5
  25. package/layers/forms/app/components/Form/Field.vue +34 -22
  26. package/layers/forms/package.json +1 -0
  27. package/layers/layout/app/components/Layout/Page/Container.vue +7 -1
  28. package/layers/layout/app/components/Layout/Page/index.vue +4 -1
  29. package/layers/layout/package.json +1 -0
  30. package/layers/motion/app/components/Motion/Marquee.vue +12 -16
  31. package/layers/motion/app/components/Motion/Parallax.vue +1 -1
  32. package/layers/motion/app/components/Motion/Staggered.vue +1 -1
  33. package/layers/motion/app/components/Motion/Transition.vue +0 -13
  34. package/layers/motion/app/composables/useSmoothScroll.ts +1 -1
  35. package/layers/motion/package.json +1 -0
  36. package/layers/routing/app/middleware/02.governance.global.ts +2 -0
  37. package/layers/routing/app/plugins/feature-flags.client.ts +1 -0
  38. package/layers/routing/nuxt.config.ts +8 -4
  39. package/layers/routing/package.json +4 -1
  40. package/layers/shader/app/composables/useShader.ts +1 -1
  41. package/layers/shader/app/utils/tsl/noise.ts +0 -5
  42. package/layers/shader/nuxt.config.ts +1 -1
  43. package/layers/shader/package.json +3 -0
  44. package/layers/theme/app/plugins/theme.client.ts +0 -3
  45. package/layers/theme/package.json +1 -0
  46. package/layers/ui/app/app.config.ts +5 -0
  47. package/layers/ui/app/components/Accent/Scene.vue +14 -1
  48. package/layers/ui/app/components/Progress/Bar.vue +11 -1
  49. package/layers/ui/app/components/Typography/CodeBlock.vue +5 -4
  50. package/layers/ui/app/components/Typography/Headline.vue +11 -3
  51. package/layers/ui/app/components/Typography/QuoteBlock.vue +5 -1
  52. package/layers/ui/app/components/Typography/index.vue +1 -1
  53. package/layers/ui/app/composables/useSite.ts +6 -6
  54. package/layers/ui/app/types/accent.ts +1 -1
  55. package/layers/ui/app/types/app-config.d.ts +5 -0
  56. package/layers/ui/app/utils/createModal.ts +4 -3
  57. package/layers/ui/package.json +1 -0
  58. package/package.json +8 -8
@@ -1,25 +1,29 @@
1
1
  <script setup lang="ts">
2
+ import type { BlogCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
7
+
8
+ const asBlog = (item: unknown) => item as BlogCollectionItem
5
9
  </script>
6
10
 
7
11
  <template>
8
12
  <NuxtContentDetail collection="blog" :slug not-found-message="Blog post not found">
9
13
  <template #headline="{ item }">
10
14
  <div class="flex items-center gap-3 text-sm text-muted">
11
- <time v-if="item.date">{{ new Date(item.date).toLocaleDateString() }}</time>
12
- <UBadge v-if="item.badge" color="primary" variant="subtle">
13
- {{ item.badge }}
15
+ <time v-if="asBlog(item).date">{{ new Date(asBlog(item).date).toLocaleDateString() }}</time>
16
+ <UBadge v-if="asBlog(item).badge" color="primary" variant="subtle">
17
+ {{ asBlog(item).badge }}
14
18
  </UBadge>
15
19
  </div>
16
20
  </template>
17
21
 
18
22
  <template #after-content="{ item }">
19
- <template v-if="item.tags?.length">
23
+ <template v-if="asBlog(item).tags?.length">
20
24
  <USeparator class="my-8" />
21
25
  <div class="flex flex-wrap gap-2">
22
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle">
26
+ <UBadge v-for="tag in asBlog(item).tags" :key="tag" color="neutral" variant="subtle">
23
27
  {{ tag }}
24
28
  </UBadge>
25
29
  </div>
@@ -17,24 +17,27 @@ const {
17
17
  authors?: { name: string; avatar?: string; url?: string }[]
18
18
  to?: string
19
19
  }>()
20
+
21
+ const postProps = computed(() => ({
22
+ ...(description !== undefined && { description }),
23
+ ...(date !== undefined && { date }),
24
+ ...(image !== undefined && { image }),
25
+ ...(badge !== undefined && { badge }),
26
+ ...(to !== undefined && { to }),
27
+ }))
20
28
  </script>
21
29
 
22
30
  <template>
23
31
  <UBlogPost
24
32
  :title
25
- :description
26
- :date
27
- :image
28
- :badge
33
+ v-bind="postProps"
29
34
  :authors="
30
35
  authors.map((a) => ({
31
36
  name: a.name,
32
- avatar: a.avatar ? { src: a.avatar } : undefined,
33
- to: a.url,
34
- target: a.url ? '_blank' : undefined,
37
+ ...(a.avatar && { avatar: { src: a.avatar } }),
38
+ ...(a.url && { to: a.url, target: '_blank' as const }),
35
39
  }))
36
40
  "
37
- :to
38
41
  variant="outline"
39
42
  />
40
43
  </template>
@@ -19,20 +19,21 @@ const { data: posts, status } = await useBlogPosts(options)
19
19
  v-for="post in posts"
20
20
  :key="post.id"
21
21
  :title="post.title"
22
- :description="post.description"
23
22
  :date="post.date"
24
- :image="post.image"
25
- :badge="post.badge"
26
- :authors="
27
- post.authors?.map((a) => ({
28
- name: a.name,
29
- avatar: a.avatar ? { src: a.avatar } : undefined,
30
- to: a.url,
31
- target: a.url ? '_blank' : undefined,
32
- }))
33
- "
34
23
  :to="post.path"
35
24
  variant="outline"
25
+ v-bind="{
26
+ ...(post.description !== undefined && { description: post.description }),
27
+ ...(post.image !== undefined && { image: post.image }),
28
+ ...(post.badge !== undefined && { badge: post.badge }),
29
+ ...(post.authors && {
30
+ authors: post.authors.map((a) => ({
31
+ name: a.name,
32
+ ...(a.avatar && { avatar: { src: a.avatar } }),
33
+ ...(a.url && { to: a.url, target: '_blank' as const }),
34
+ })),
35
+ }),
36
+ }"
36
37
  />
37
38
  </UBlogPosts>
38
39
  </NuxtContentList>
@@ -15,10 +15,15 @@ const {
15
15
  tags?: string[]
16
16
  to?: string
17
17
  }>()
18
+
19
+ const cardProps = computed(() => ({
20
+ ...(description !== undefined && { description }),
21
+ ...(to !== undefined && { to }),
22
+ }))
18
23
  </script>
19
24
 
20
25
  <template>
21
- <UPageCard :title :description :to variant="outline">
26
+ <UPageCard :title v-bind="cardProps" variant="outline">
22
27
  <img
23
28
  v-if="coverImage"
24
29
  :src="coverImage.src"
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import type { GalleryCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
@@ -7,6 +9,8 @@ const lightboxOpen = ref(false)
7
9
  const lightboxIndex = ref(0)
8
10
  const lightboxImages = ref<{ src: string; alt: string; width?: number; height?: number }[]>([])
9
11
 
12
+ const asGallery = (item: unknown) => item as GalleryCollectionItem
13
+
10
14
  function openLightbox(
11
15
  images: { src: string; alt: string; width?: number; height?: number }[],
12
16
  index: number
@@ -21,21 +25,21 @@ function openLightbox(
21
25
  <NuxtContentDetail collection="gallery" :slug not-found-message="Gallery not found">
22
26
  <template #headline="{ item }">
23
27
  <div class="flex flex-wrap items-center gap-2">
24
- <span v-if="item.images?.length" class="text-sm text-muted">
25
- {{ item.images.length }} image{{ item.images.length === 1 ? '' : 's' }}
28
+ <span v-if="asGallery(item).images?.length" class="text-sm text-muted">
29
+ {{ asGallery(item).images!.length }} image{{ asGallery(item).images!.length === 1 ? '' : 's' }}
26
30
  </span>
27
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle" size="xs">
31
+ <UBadge v-for="tag in asGallery(item).tags" :key="tag" color="neutral" variant="subtle" size="xs">
28
32
  {{ tag }}
29
33
  </UBadge>
30
34
  </div>
31
35
  </template>
32
36
 
33
37
  <template #after-content="{ item }">
34
- <template v-if="item.images?.length">
38
+ <template v-if="asGallery(item).images?.length">
35
39
  <USeparator class="my-8" />
36
40
  <div class="grid grid-cols-2 md:grid-cols-3 gap-4">
37
41
  <div
38
- v-for="(img, idx) in item.images"
42
+ v-for="(img, idx) in asGallery(item).images"
39
43
  :key="img.src"
40
44
  class="overflow-hidden rounded-lg group relative"
41
45
  >
@@ -55,7 +59,7 @@ function openLightbox(
55
59
  </NuxtLink>
56
60
  <button
57
61
  class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 cursor-pointer"
58
- @click.stop="openLightbox(item.images, idx)"
62
+ @click.stop="openLightbox(asGallery(item).images!, idx)"
59
63
  >
60
64
  <UIcon name="i-lucide-expand" class="size-4" />
61
65
  </button>
@@ -1,10 +1,13 @@
1
1
  <script setup lang="ts">
2
+ import type { GalleryCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug, index } = defineProps<{
3
5
  slug: string
4
6
  index: number
5
7
  }>()
6
8
 
7
- const { data: item } = await useCollectionItem('gallery', slug)
9
+ const { data: rawItem } = await useCollectionItem('gallery', slug)
10
+ const item = computed(() => rawItem.value as GalleryCollectionItem | null)
8
11
 
9
12
  const image = computed(() => item.value?.images?.[index])
10
13
  const totalImages = computed(() => item.value?.images?.length ?? 0)
@@ -19,8 +22,11 @@ useSeoMeta({
19
22
  </script>
20
23
 
21
24
  <template>
22
- <div v-if="item && image">
23
- <UPageHeader :title="image.title || image.alt" :description="image.caption">
25
+ <div v-if="item && item.images && image">
26
+ <UPageHeader
27
+ :title="image.title || image.alt"
28
+ v-bind="image.caption ? { description: image.caption } : {}"
29
+ >
24
30
  <template #headline>
25
31
  <UBreadcrumb
26
32
  :items="[
@@ -15,10 +15,14 @@ const {
15
15
  const { data: item, status } = await useCollectionItem(collection, slug)
16
16
  const { data: surround } = await useCollectionSurround(collection, slug)
17
17
 
18
+ const itemImage = computed(
19
+ () => (item.value as { image?: string } | null)?.image
20
+ )
21
+
18
22
  useSeoMeta({
19
23
  title: item.value?.title,
20
24
  description: item.value?.description,
21
- ogImage: item.value?.image,
25
+ ogImage: itemImage,
22
26
  })
23
27
  </script>
24
28
 
@@ -26,7 +30,7 @@ useSeoMeta({
26
30
  <div>
27
31
  <USkeleton v-if="status === 'pending'" class="h-96 w-full" />
28
32
  <UPage v-else-if="item">
29
- <UPageHeader :title="item.title" :description="item.description">
33
+ <UPageHeader :title="item.title" v-bind="item.description ? { description: item.description } : {}">
30
34
  <template v-if="$slots.headline" #headline>
31
35
  <slot name="headline" :item="item" />
32
36
  </template>
@@ -45,7 +49,7 @@ useSeoMeta({
45
49
  </UPageBody>
46
50
 
47
51
  <template v-if="!hideToc" #right>
48
- <NuxtContentToc :links="item.body?.toc?.links" />
52
+ <NuxtContentToc v-bind="item.body?.toc?.links ? { links: item.body.toc.links } : {}" />
49
53
  </template>
50
54
  </UPage>
51
55
  <div v-else class="text-muted text-center py-12">{{ notFoundMessage }}</div>
@@ -1,7 +1,9 @@
1
1
  <!-- eslint-disable vue/require-default-prop -->
2
2
  <script setup lang="ts">
3
+ import type { TocLink } from '@nuxt/content'
4
+
3
5
  const { links, title = 'Table of Contents' } = defineProps<{
4
- links?: { id: string; text: string; children?: { id: string; text: string }[] }[]
6
+ links?: TocLink[]
5
7
  title?: string
6
8
  }>()
7
9
  </script>
@@ -20,7 +20,11 @@ const {
20
20
  </script>
21
21
 
22
22
  <template>
23
- <UPageCard :title :description :to variant="outline">
23
+ <UPageCard
24
+ :title
25
+ variant="outline"
26
+ v-bind="{ ...(description !== undefined && { description }), ...(to !== undefined && { to }) }"
27
+ >
24
28
  <img v-if="image" :src="image" :alt="title" class="w-full h-48 object-cover rounded" />
25
29
  <template #footer>
26
30
  <div class="flex flex-wrap gap-1.5">
@@ -1,7 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import type { PortfolioCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
7
+
8
+ const asPortfolio = (item: unknown) => item as PortfolioCollectionItem
5
9
  </script>
6
10
 
7
11
  <template>
@@ -13,13 +17,19 @@ const { slug } = defineProps<{
13
17
  >
14
18
  <template #headline="{ item }">
15
19
  <div class="flex flex-wrap items-center gap-2">
16
- <UBadge v-if="item.client" color="primary" variant="subtle">
17
- {{ item.client }}
20
+ <UBadge v-if="asPortfolio(item).client" color="primary" variant="subtle">
21
+ {{ asPortfolio(item).client }}
18
22
  </UBadge>
19
- <UBadge v-if="item.year" color="neutral" variant="subtle">
20
- {{ item.year }}
23
+ <UBadge v-if="asPortfolio(item).year" color="neutral" variant="subtle">
24
+ {{ asPortfolio(item).year }}
21
25
  </UBadge>
22
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle" size="xs">
26
+ <UBadge
27
+ v-for="tag in asPortfolio(item).tags"
28
+ :key="tag"
29
+ color="neutral"
30
+ variant="subtle"
31
+ size="xs"
32
+ >
23
33
  {{ tag }}
24
34
  </UBadge>
25
35
  </div>
@@ -27,8 +37,8 @@ const { slug } = defineProps<{
27
37
 
28
38
  <template #links="{ item }">
29
39
  <UButton
30
- v-if="item.url"
31
- :to="item.url"
40
+ v-if="asPortfolio(item).url"
41
+ :to="asPortfolio(item).url"
32
42
  target="_blank"
33
43
  icon="i-lucide-external-link"
34
44
  variant="outline"
@@ -38,15 +48,26 @@ const { slug } = defineProps<{
38
48
  </template>
39
49
 
40
50
  <template #before-content="{ item }">
41
- <img v-if="item.image" :src="item.image" :alt="item.title" class="w-full rounded-lg mb-8" />
51
+ <img
52
+ v-if="asPortfolio(item).image"
53
+ :src="asPortfolio(item).image"
54
+ :alt="item.title"
55
+ class="w-full rounded-lg mb-8"
56
+ />
42
57
  </template>
43
58
 
44
59
  <template #after-content="{ item }">
45
- <template v-if="item.colors?.length || item.typography?.length">
60
+ <template v-if="asPortfolio(item).colors?.length || asPortfolio(item).typography?.length">
46
61
  <USeparator class="my-8" />
47
62
  <div class="space-y-8">
48
- <PortfolioColorPalette v-if="item.colors?.length" :colors="item.colors" />
49
- <PortfolioTypography v-if="item.typography?.length" :typography="item.typography" />
63
+ <PortfolioColorPalette
64
+ v-if="asPortfolio(item).colors?.length"
65
+ :colors="asPortfolio(item).colors!"
66
+ />
67
+ <PortfolioTypography
68
+ v-if="asPortfolio(item).typography?.length"
69
+ :typography="asPortfolio(item).typography!"
70
+ />
50
71
  </div>
51
72
  </template>
52
73
  </template>
@@ -19,9 +19,11 @@ const { data: items, status } = await usePortfolioItems(options)
19
19
  v-for="item in items"
20
20
  :key="item.id"
21
21
  :title="item.title"
22
- :description="item.description"
23
- :to="item.path"
24
22
  variant="outline"
23
+ v-bind="{
24
+ ...(item.description !== undefined && { description: item.description }),
25
+ to: item.path,
26
+ }"
25
27
  >
26
28
  <img
27
29
  v-if="item.image"
@@ -1,5 +1,7 @@
1
+ import type { Collections } from '@nuxt/content'
2
+
1
3
  export function useCollectionItem(collection: string, slug: string) {
2
4
  return useContentData(`${collection}-${slug}`, () =>
3
- queryCollection(collection).path(`/${collection}/${slug}`).first()
5
+ queryCollection(collection as keyof Collections).path(`/${collection}/${slug}`).first()
4
6
  )
5
7
  }
@@ -1,9 +1,7 @@
1
- export function useCollectionSurround(
2
- collection: string,
3
- slug: string,
4
- fields: string[] = ['description']
5
- ) {
1
+ import type { PageCollections } from '@nuxt/content'
2
+
3
+ export function useCollectionSurround(collection: string, slug: string) {
6
4
  return useContentData(`${collection}-${slug}-surround`, () =>
7
- queryCollectionItemSurroundings(collection, `/${collection}/${slug}`, { fields })
5
+ queryCollectionItemSurroundings(collection as keyof PageCollections, `/${collection}/${slug}`)
8
6
  )
9
7
  }
@@ -22,7 +22,7 @@ export interface PortfolioColor {
22
22
 
23
23
  export interface PortfolioFont {
24
24
  name: string
25
- weights: string[]
25
+ weights?: string[]
26
26
  usage?: string
27
27
  }
28
28
 
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:content": "CONTENT_STANDALONE=true nuxi dev",
9
10
  "dev:prepare": "nuxt prepare .playground",
@@ -3,5 +3,6 @@
3
3
  <NuxtLayout>
4
4
  <NuxtPage />
5
5
  </NuxtLayout>
6
+ <UOverlayProvider />
6
7
  </UApp>
7
8
  </template>
@@ -78,15 +78,14 @@ export function useCache() {
78
78
 
79
79
  try {
80
80
  const cacheNames = await caches.keys()
81
- ;(await Promise.all(cacheNames.map((name) => caches.delete(name)))(
82
- process.env.NODE_ENV === 'development'
83
- ))
84
- ? console.log('[useCache] All caches cleared')
85
- : ''
81
+ await Promise.all(cacheNames.map((name) => caches.delete(name)))
82
+ if (process.env.NODE_ENV === 'development') {
83
+ console.log('[useCache] All caches cleared')
84
+ }
86
85
  } catch (error) {
87
- process.env.NODE_ENV === 'development'
88
- ? console.error('[useCache] Failed to clear cache:', error)
89
- : ''
86
+ if (process.env.NODE_ENV === 'development') {
87
+ console.error('[useCache] Failed to clear cache:', error)
88
+ }
90
89
  }
91
90
  }
92
91
 
@@ -77,18 +77,6 @@ function clampElement(el: HTMLElement) {
77
77
  clampedCount.value++
78
78
  }
79
79
 
80
- function restoreElement(el: HTMLElement) {
81
- const saved = clampedElements.get(el)
82
- if (!saved) return
83
-
84
- el.style.transition = saved.transition
85
- el.style.maxWidth = saved.maxWidth
86
- el.style.boxSizing = saved.boxSizing
87
- el.style.overflowX = saved.overflowX
88
-
89
- clampedElements.delete(el)
90
- clampedSet.delete(el)
91
- }
92
80
 
93
81
  function guard() {
94
82
  if (!isEnabled.value || !opts) return
@@ -130,13 +130,14 @@ export default defineNuxtPlugin((nuxtApp) => {
130
130
  }
131
131
 
132
132
  // Test environment access (works on both server and client)
133
- const env = useEnv()
133
+ const env = useEnv() as unknown as Record<string, unknown>
134
134
 
135
135
  if (isDev) {
136
+ const publicConfig = env.public as Record<string, unknown> | undefined
136
137
  // eslint-disable-next-line no-console
137
138
  console.log('[Core Layer] Environment config loaded:', {
138
- hasPublicConfig: !!env.public,
139
- publicKeys: Object.keys(env.public || {}),
139
+ hasPublicConfig: !!publicConfig,
140
+ publicKeys: Object.keys(publicConfig ?? {}),
140
141
  })
141
142
  }
142
143
  } catch (error) {
@@ -29,8 +29,6 @@ export default defineNuxtPlugin((nuxtApp) => {
29
29
 
30
30
  // console.log('[Loading Plugin] Config:', coreLayer?.loading)
31
31
 
32
- if ((config.layers as Record<string, boolean> | undefined)?.core === false) return
33
-
34
32
  // Check if loading is enabled
35
33
  if (coreLayer?.loading?.enabled === false) {
36
34
  // console.log('[Loading Plugin] Disabled')
@@ -24,8 +24,6 @@ export default defineNuxtPlugin(() => {
24
24
  const config = useAppConfig()
25
25
  const coreLayer = config.coreLayer as CoreLayerConfig | undefined
26
26
 
27
- if ((config.layers as Record<string, boolean> | undefined)?.core === false) return
28
-
29
27
  if (coreLayer?.scrollGuard?.enabled === false) {
30
28
  if (import.meta.dev) {
31
29
  // eslint-disable-next-line no-console
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../apps/playground/tsconfig.json",
3
3
  "compilerOptions": {
4
- "strict": true
4
+ "strict": true,
5
+ "ignoreDeprecations": "6.0"
5
6
  }
6
7
  }
@@ -17,11 +17,7 @@ const schema = z.object({
17
17
 
18
18
  type FormState = z.infer<typeof schema>
19
19
 
20
- const state = reactive<Partial<FormState>>({
21
- name: undefined,
22
- email: undefined,
23
- message: undefined,
24
- })
20
+ const state = reactive({ name: '', email: '', message: '' })
25
21
 
26
22
  const toast = useToast()
27
23
  const isLoading = ref(false)
@@ -41,6 +41,36 @@ const resolvedIcon = computed(() => icon ?? config.value.icon)
41
41
  const isTextarea = computed(() => config.value.component === 'UTextarea')
42
42
  const isNumber = computed(() => config.value.component === 'UInputNumber')
43
43
 
44
+ const formFieldProps = computed(() => ({
45
+ name,
46
+ required,
47
+ size,
48
+ ...(label !== undefined && { label }),
49
+ ...(className !== undefined && { class: className }),
50
+ }))
51
+
52
+ const baseInputProps = computed(() => ({
53
+ size,
54
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
55
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
56
+ }))
57
+
58
+ const numberInputProps = computed(() => ({
59
+ size,
60
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
61
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
62
+ ...(currencyOptions.value !== undefined && { formatOptions: currencyOptions.value }),
63
+ }))
64
+
65
+ const textInputProps = computed(() => ({
66
+ type: config.value.inputType,
67
+ size,
68
+ ...(config.value.inputMode !== undefined && { inputmode: config.value.inputMode }),
69
+ ...(config.value.autocomplete !== undefined && { autocomplete: config.value.autocomplete }),
70
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
71
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
72
+ }))
73
+
44
74
  const currencyOptions = computed((): Intl.NumberFormatOptions | undefined => {
45
75
  if (config.value.format === 'currency') {
46
76
  return {
@@ -53,33 +83,15 @@ const currencyOptions = computed((): Intl.NumberFormatOptions | undefined => {
53
83
  </script>
54
84
 
55
85
  <template>
56
- <UFormField :name :label :required :size :class="className">
57
- <UTextarea
58
- v-if="isTextarea"
59
- v-model="model as string"
60
- :placeholder="resolvedPlaceholder"
61
- :size
62
- autoresize
63
- />
86
+ <UFormField v-bind="formFieldProps">
87
+ <UTextarea v-if="isTextarea" v-model="model as string" autoresize v-bind="baseInputProps" />
64
88
 
65
89
  <UInputNumber
66
90
  v-else-if="isNumber"
67
91
  v-model="model as number"
68
- :placeholder="resolvedPlaceholder"
69
- :leading-icon="resolvedIcon"
70
- :size
71
- :format-options="currencyOptions"
92
+ v-bind="numberInputProps"
72
93
  />
73
94
 
74
- <UInput
75
- v-else
76
- v-model="model as string"
77
- :type="config.inputType"
78
- :inputmode="config.inputMode"
79
- :autocomplete="config.autocomplete"
80
- :placeholder="resolvedPlaceholder"
81
- :leading-icon="resolvedIcon"
82
- :size
83
- />
95
+ <UInput v-else v-model="model as string" v-bind="textInputProps" />
84
96
  </UFormField>
85
97
  </template>
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -78,7 +78,13 @@ provide('pageTitle', title)
78
78
  <!-- Optional visible header -->
79
79
  <LayoutSection v-if="showHeader">
80
80
  <LayoutGridItem :preset="headerPreset">
81
- <LayoutPageHeader :title :description :back />
81
+ <LayoutPageHeader
82
+ :title
83
+ v-bind="{
84
+ ...(description !== undefined && { description }),
85
+ ...(back !== undefined && { back }),
86
+ }"
87
+ />
82
88
  </LayoutGridItem>
83
89
  </LayoutSection>
84
90
 
@@ -60,7 +60,10 @@ provide('pageTitle', title)
60
60
  <!-- Optional visible page header — rendered as a grid section -->
61
61
  <LayoutSection v-if="showHeader">
62
62
  <LayoutGridItem preset="centered">
63
- <LayoutPageHeader :title :description />
63
+ <LayoutPageHeader
64
+ :title
65
+ v-bind="description !== undefined ? { description } : {}"
66
+ />
64
67
  </LayoutGridItem>
65
68
  </LayoutSection>
66
69
 
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -142,12 +142,16 @@ function handleMouseLeave() {
142
142
  <template>
143
143
  <div
144
144
  ref="containerRef"
145
- class="motion-marquee"
145
+ class="motion-marquee width-full overflow-hidden"
146
146
  :class="{ 'is-paused': isPaused }"
147
147
  @mouseenter="handleMouseEnter"
148
148
  @mouseleave="handleMouseLeave"
149
149
  >
150
- <div ref="contentRef" class="motion-marquee__content" :style="{ gap }">
150
+ <div
151
+ ref="contentRef"
152
+ class="motion-marquee__content flex will-change-transform w-max"
153
+ :style="{ gap }"
154
+ >
151
155
  <slot />
152
156
  <!-- Duplicate for seamless loop -->
153
157
  <slot />
@@ -155,17 +159,9 @@ function handleMouseLeave() {
155
159
  </div>
156
160
  </template>
157
161
 
158
- <style scoped>
159
- .motion-marquee {
160
- width: 100%;
161
- overflow: hidden;
162
- }
163
-
164
- /* stylelint-disable-next-line selector-class-pattern */
165
- .motion-marquee__content {
166
- display: flex;
167
- will-change: transform;
168
- /* stylelint-disable-next-line plugin/no-unsupported-browser-features */
169
- width: max-content;
170
- }
171
- </style>
162
+ <!-- <style scoped>
163
+ /* .motion-marquee { width: 100%; overflow: hidden; } */ /* stylelint-disable-next-line
164
+ selector-class-pattern */ /* .motion-marquee__content { */ /* display: flex; */ /* will-change:
165
+ transform; */ /* stylelint-disable-next-line plugin/no-unsupported-browser-features */ /* width:
166
+ max-content; */ /* } */
167
+ </style> -->
@@ -36,7 +36,7 @@ const props = withDefaults(
36
36
  }
37
37
  )
38
38
 
39
- const { gsap, ScrollTrigger } = useGsap()
39
+ const { gsap } = useGsap()
40
40
 
41
41
  const containerRef = ref<HTMLElement | null>(null)
42
42
  const contentRef = ref<HTMLElement | null>(null)
@@ -50,7 +50,7 @@ onMounted(() => {
50
50
  <template>
51
51
  <div class="motion-staggered">
52
52
  <slot
53
- v-for="(item, index) in slots.default?.()"
53
+ v-for="(_, index) in slots.default?.()"
54
54
  :key="index"
55
55
  :ref="(el: HTMLElement | null) => setItemRef(el, index)"
56
56
  :class="{ animated: animatedItems[index] }"
@@ -43,20 +43,7 @@ onMounted(() => {
43
43
  }, 50)
44
44
  })
45
45
 
46
- // Methods
47
- const enter = (el: Element) => {
48
- // Handle enter transition
49
- ;(el as HTMLElement).style.opacity = '0'
50
- setTimeout(() => {
51
- ;(el as HTMLElement).style.opacity = '1'
52
- }, 10)
53
- }
54
46
 
55
- const leave = (el: Element, done: () => void) => {
56
- // Handle leave transition
57
- ;(el as HTMLElement).style.opacity = '0'
58
- setTimeout(done, props.duration)
59
- }
60
47
  </script>
61
48
 
62
49
  <template>
@@ -81,7 +81,7 @@ export function useSmoothScroll() {
81
81
  duration: options?.duration ?? 1.2,
82
82
  immediate: options?.immediate ?? false,
83
83
  lock: options?.lock ?? false,
84
- onComplete: options?.onComplete,
84
+ ...(options?.onComplete !== undefined && { onComplete: options.onComplete }),
85
85
  })
86
86
  } else {
87
87
  // Native fallback
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -1,3 +1,5 @@
1
+ import { useFeatures } from '../composables/useFeatures'
2
+
1
3
  export default defineNuxtRouteMiddleware((to) => {
2
4
  const { config, isStrictMode, isLayerDefaultDeny } = useRoutingConfig()
3
5
  const { resolve } = useFeatures()
@@ -1,4 +1,5 @@
1
1
  import type { FeatureValue } from '../types/routing'
2
+ import { useFeatures } from '../composables/useFeatures'
2
3
 
3
4
  export default defineNuxtPlugin(async () => {
4
5
  const { config } = useRoutingConfig()
@@ -1,4 +1,8 @@
1
- import type { NuxtPage } from '@nuxt/schema'
1
+ interface RoutePage {
2
+ file?: string
3
+ meta?: Record<string, unknown>
4
+ children?: RoutePage[]
5
+ }
2
6
 
3
7
  export default defineNuxtConfig({
4
8
  $meta: { name: 'routing' },
@@ -10,9 +14,9 @@ export default defineNuxtConfig({
10
14
  compatibilityDate: '2026-01-30',
11
15
 
12
16
  hooks: {
13
- 'pages:extend'(pages) {
17
+ 'pages:extend'(pages: unknown[]) {
14
18
  const cwd = process.cwd()
15
- const tag = (list: NuxtPage[]) => {
19
+ const tag = (list: RoutePage[]) => {
16
20
  for (const page of list) {
17
21
  if (page.file && !page.file.startsWith(cwd)) {
18
22
  page.meta ??= {}
@@ -21,7 +25,7 @@ export default defineNuxtConfig({
21
25
  if (page.children) tag(page.children)
22
26
  }
23
27
  }
24
- tag(pages)
28
+ tag(pages as unknown as RoutePage[])
25
29
  },
26
30
  },
27
31
  })
@@ -2,5 +2,8 @@
2
2
  "name": "@layers/routing",
3
3
  "version": "0.1.0",
4
4
  "type": "module",
5
- "main": "./nuxt.config.ts"
5
+ "main": "./nuxt.config.ts",
6
+ "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json"
8
+ }
6
9
  }
@@ -143,6 +143,6 @@ export function useBasicShader(
143
143
  export function useStandardShader(config?: NodeMaterialConfig) {
144
144
  return useShader({
145
145
  type: 'standard',
146
- config,
146
+ ...(config !== undefined && { config }),
147
147
  })
148
148
  }
@@ -4,11 +4,6 @@ import type { FBMOptions, TSLNode } from '../../types'
4
4
  /**
5
5
  * Hash function for noise generation
6
6
  */
7
- const hash21 = Fn(([p]: [TSLNode]) => {
8
- const p3 = fract(p.mul(vec2(443.8975, 397.2973)))
9
- const shifted = p3.add(dot(p3, p3.add(19.19)))
10
- return fract(shifted.x.mul(shifted.y))
11
- })
12
7
 
13
8
  const hash22 = Fn(([p]: [TSLNode]) => {
14
9
  const p3 = fract(p.mul(vec2(443.8975, 397.2973)))
@@ -76,7 +76,7 @@ export default {}
76
76
  return {
77
77
  name: 'three-webgpu-ssr-stub',
78
78
  enforce: 'pre',
79
- transform(code, id, options) {
79
+ transform(_code, id, options) {
80
80
  // Only intercept in SSR context
81
81
  if (!options?.ssr) return null
82
82
  // In three r182+, three/webgpu → three.webgpu.js, three/tsl → three.tsl.js
@@ -3,6 +3,9 @@
3
3
  "version": "0.0.1",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
+ "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json"
8
+ },
6
9
  "dependencies": {
7
10
  "@tresjs/cientos": "^5.2.5",
8
11
  "@tresjs/core": "^5.3.3",
@@ -1,7 +1,4 @@
1
1
  export default defineNuxtPlugin(() => {
2
- const config = useAppConfig()
3
- if ((config.layers as Record<string, boolean> | undefined)?.theme === false) return
4
-
5
2
  // Initialize shared composables — applies data-theme-colour, data-theme-contrast,
6
3
  // data-theme-motion, and data-theme-transparency on <html> on first load.
7
4
  useTheme()
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -1,4 +1,9 @@
1
1
  export default {
2
+ site: {
3
+ title: '',
4
+ subtitle: '',
5
+ description: '',
6
+ },
2
7
  mastNav: {
3
8
  links: [],
4
9
  scrollBehaviour: 'router',
@@ -29,7 +29,20 @@ const resolvedBlobs = computed((): BlobConfig[] => {
29
29
  <template>
30
30
  <component :is="tag" class="relative overflow-clip isolate">
31
31
  <div class="absolute inset-0 pointer-events-none" aria-hidden="true">
32
- <AccentBlob v-for="(blob, i) in resolvedBlobs" :key="i" v-bind="blob" />
32
+ <AccentBlob
33
+ v-for="(blob, i) in resolvedBlobs"
34
+ :key="i"
35
+ :x="blob.x"
36
+ :y="blob.y"
37
+ v-bind="{
38
+ ...(blob.size !== undefined && { size: blob.size }),
39
+ ...(blob.blur !== undefined && { blur: blob.blur }),
40
+ ...(blob.opacity !== undefined && { opacity: blob.opacity }),
41
+ ...(blob.color !== undefined && { color: blob.color }),
42
+ ...(blob.shade !== undefined && { shade: blob.shade }),
43
+ ...(blob.customColor !== undefined && { customColor: blob.customColor }),
44
+ }"
45
+ />
33
46
  </div>
34
47
  <div class="relative z-10">
35
48
  <slot />
@@ -18,8 +18,18 @@ const {
18
18
  }>()
19
19
 
20
20
  const modelValue = computed(() => (progress != null ? progress * 100 : undefined))
21
+
22
+ const progressProps = computed(() => ({
23
+ ...(modelValue.value !== undefined && { modelValue: modelValue.value }),
24
+ ...(color !== undefined && { color }),
25
+ ...(size !== undefined && { size }),
26
+ ...(orientation !== undefined && { orientation }),
27
+ ...(status !== undefined && { status }),
28
+ ...(animation !== undefined && { animation }),
29
+ ...(inverted !== undefined && { inverted }),
30
+ }))
21
31
  </script>
22
32
 
23
33
  <template>
24
- <UProgress :model-value="modelValue" :color :size :orientation :status :animation :inverted />
34
+ <UProgress v-bind="progressProps" />
25
35
  </template>
@@ -13,7 +13,6 @@ const props = withDefaults(
13
13
  class?: string
14
14
  }>(),
15
15
  {
16
- language: undefined,
17
16
  class: '',
18
17
  color: 'default',
19
18
  }
@@ -24,11 +23,13 @@ const colorClass = useColor(props.color, 'text')
24
23
  <template>
25
24
  <Typography
26
25
  tag="pre"
27
- v-bind="$attrs"
28
- :size="props.size"
26
+ v-bind="{
27
+ ...$attrs,
28
+ ...(props.size !== undefined && { size: props.size }),
29
+ ...(props.language !== undefined && { 'data-language': props.language }),
30
+ }"
29
31
  class="overflow-x-auto"
30
32
  :class="[props.class]"
31
- :data-language="props.language"
32
33
  >
33
34
  <Typography
34
35
  tag="code"
@@ -57,7 +57,16 @@ const sizeClass = computed(() => {
57
57
  return sizes[props.level]
58
58
  })
59
59
 
60
- const { classes } = useTypography(props)
60
+ const { classes } = useTypography({
61
+ weight: props.weight,
62
+ width: props.width,
63
+ slant: props.slant,
64
+ leading: props.leading,
65
+ tracking: props.tracking,
66
+ align: props.align,
67
+ transform: props.transform,
68
+ ...(props.size !== undefined && { size: props.size }),
69
+ })
61
70
  const colorClass = useColor(props.color, 'text')
62
71
  </script>
63
72
 
@@ -71,9 +80,8 @@ const colorClass = useColor(props.color, 'text')
71
80
  :tracking="props.tracking"
72
81
  :align="props.align"
73
82
  :transform="props.transform"
74
- :size="props.size"
75
83
  :class="[sizeClass, classes, colorClass, props.class]"
76
- v-bind="$attrs"
84
+ v-bind="{ ...(props.size !== undefined && { size: props.size }), ...$attrs }"
77
85
  >
78
86
  <slot />
79
87
  </Typography>
@@ -10,7 +10,11 @@ const colorClass = useColor(props.color, 'text')
10
10
  </script>
11
11
 
12
12
  <template>
13
- <Typography tag="blockquote" :size="props.size" :class="colorClass" v-bind="$attrs">
13
+ <Typography
14
+ tag="blockquote"
15
+ :class="colorClass"
16
+ v-bind="{ ...(props.size !== undefined && { size: props.size }), ...$attrs }"
17
+ >
14
18
  <slot />
15
19
  </Typography>
16
20
  </template>
@@ -32,7 +32,7 @@ const props = withDefaults(
32
32
  {
33
33
  tag: 'p',
34
34
  weight: 'font-normal',
35
- width: 'normal',
35
+ width: 'font-stretch-normal',
36
36
  slant: 'normal',
37
37
  leading: 'leading-normal',
38
38
  tracking: 'tracking-normal',
@@ -1,13 +1,13 @@
1
1
  import { splitSpaces } from '../utils/regex'
2
2
 
3
3
  export function useSite() {
4
- const config = useAppConfig().site
4
+ const config = useAppConfig().site ?? {}
5
5
 
6
6
  return {
7
- title: config.title as string,
8
- titleWords: splitSpaces(config.title as string) as string[],
9
- subtitle: config.subtitle as string,
10
- subtitleWords: splitSpaces(config.subtitle as string) as string[],
11
- description: config.description as string,
7
+ title: (config.title ?? '') as string,
8
+ titleWords: splitSpaces((config.title ?? '') as string) as string[],
9
+ subtitle: (config.subtitle ?? '') as string,
10
+ subtitleWords: splitSpaces((config.subtitle ?? '') as string) as string[],
11
+ description: (config.description ?? '') as string,
12
12
  }
13
13
  }
@@ -9,7 +9,7 @@ export interface BlobConfig {
9
9
  opacity?: number
10
10
  color?: AccentBlobColor
11
11
  shade?: 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950
12
- customColor?: string
12
+ customColor?: string | undefined
13
13
  }
14
14
 
15
15
  export interface AccentSceneConfig {
@@ -1,5 +1,10 @@
1
1
  declare module '@nuxt/schema' {
2
2
  interface AppConfigInput {
3
+ site?: {
4
+ title?: string
5
+ subtitle?: string
6
+ description?: string
7
+ }
3
8
  mastNav?: {
4
9
  links?: Array<{
5
10
  id: string
@@ -24,11 +24,12 @@ export function createModal<P extends Record<string, unknown>>(component: Compon
24
24
  if (import.meta.server) return { open: () => {}, close: () => {}, patch: () => {} }
25
25
 
26
26
  const overlay = useOverlay()
27
- const modal = overlay.create<P>(component)
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const modal = overlay.create(component as any)
28
29
  return {
29
- open: (props?: Partial<P>) => modal.open(props),
30
+ open: (props?: Partial<P>) => modal.open(props as never),
30
31
  close: () => modal.close(),
31
- patch: (props: Partial<P>) => modal.patch(props),
32
+ patch: (props: Partial<P>) => modal.patch(props as never),
32
33
  }
33
34
  })
34
35
  }
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:ui": "UI_STANDALONE=true nuxi dev",
9
10
  "dev:prepare": "nuxt prepare .playground",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kmcom-nuxt-layers",
3
3
  "private": false,
4
- "version": "1.6.17",
4
+ "version": "1.6.20",
5
5
  "description": "Composable Nuxt 4 layers for building scalable Vue applications",
6
6
  "files": [
7
7
  "layers/*/nuxt.config.ts",
@@ -41,7 +41,7 @@
41
41
  "tailwindcss": "^4.2.4",
42
42
  "three": "^0.183.2",
43
43
  "v-gsap-nuxt": ">=1.0.0",
44
- "zod": "^4.3.6"
44
+ "zod": "^4.4.3"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "@nuxtjs/device": {
@@ -107,8 +107,8 @@
107
107
  "@perplex-digital/stylelint-config": "^17.4.0",
108
108
  "@pinia/nuxt": "^0.11.3",
109
109
  "@types/node": "^25.6.0",
110
- "@typescript-eslint/eslint-plugin": "^8.59.1",
111
- "@typescript-eslint/parser": "^8.59.1",
110
+ "@typescript-eslint/eslint-plugin": "^8.59.2",
111
+ "@typescript-eslint/parser": "^8.59.2",
112
112
  "@vue/eslint-config-typescript": "^14.7.0",
113
113
  "@vueuse/core": "^14.3.0",
114
114
  "@vueuse/nuxt": "^14.3.0",
@@ -124,7 +124,7 @@
124
124
  "eslint-plugin-prettier": "^5.5.5",
125
125
  "eslint-plugin-unicorn": "^64.0.0",
126
126
  "eslint-plugin-unused-imports": "^4.4.1",
127
- "eslint-plugin-vue": "^10.9.0",
127
+ "eslint-plugin-vue": "^10.9.1",
128
128
  "npm-check-updates": "^21.0.3",
129
129
  "nuxt": "latest",
130
130
  "pinia": "^3.0.4",
@@ -144,14 +144,14 @@
144
144
  "stylelint-no-unsupported-browser-features": "^8.1.1",
145
145
  "stylelint-prettier": "^5.0.3",
146
146
  "tailwindcss": "^4.2.4",
147
- "turbo": "^2.9.8",
147
+ "turbo": "^2.9.9",
148
148
  "typescript": "^6.0.3",
149
149
  "vite-plugin-checker": "^0.13.0",
150
150
  "vitest": "^4.1.5",
151
151
  "vue": "latest",
152
152
  "vue-tsc": "^3.2.8",
153
- "zod": "^4.3.6",
154
- "zod-to-json-schema": "^3.25.1"
153
+ "zod": "^4.4.3",
154
+ "zod-to-json-schema": "^3.25.2"
155
155
  },
156
156
  "browserslist": [
157
157
  "last 2 Chrome major versions",