@falcondev-oss/nuxt-layers-base 0.23.2 → 0.25.0

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.
@@ -84,7 +84,7 @@ const inputProps = computed(() => {
84
84
  <UPopover
85
85
  v-if="!!field.errors"
86
86
  mode="hover"
87
- :delay-duration="0"
87
+ :open-delay="0"
88
88
  :ui="{
89
89
  content: 'bg-error-50 ring-error-200! rounded py-1 px-2',
90
90
  }"
@@ -0,0 +1,128 @@
1
+ <script setup lang="ts" generic="Multiple extends boolean = false">
2
+ import type { FileUploadEmits, FileUploadProps } from '@nuxt/ui'
3
+ import { useForwardPropsEmits } from 'reka-ui'
4
+ import { omit } from 'remeda'
5
+
6
+ const props = defineProps<
7
+ FileUploadProps<Multiple> & {
8
+ /**
9
+ * Set to `false` to disable compression
10
+ */
11
+ compression?:
12
+ | boolean
13
+ | {
14
+ /**
15
+ * @default 1920
16
+ */
17
+ maxDimension?: number
18
+ /**
19
+ * @default 0.85
20
+ */
21
+ quality?: number
22
+ /**
23
+ * @default 'image/webp'
24
+ */
25
+ outputType?: string
26
+ }
27
+ }
28
+ >()
29
+ const emit = defineEmits<
30
+ FileUploadEmits & {
31
+ compressed: [
32
+ event: {
33
+ original: File
34
+ compressed: File
35
+ savedBytes: number
36
+ savedPercentage: number
37
+ },
38
+ ]
39
+ }
40
+ >()
41
+ const forwarded = useForwardPropsEmits(props, emit)
42
+
43
+ type Files = Multiple extends true ? File[] : File
44
+ const model = defineModel<Files | null>()
45
+
46
+ const compressedFiles = new WeakMap<File, File>()
47
+ async function forwardFiles(files: File | File[] | null | undefined) {
48
+ if (!files) {
49
+ model.value = files
50
+ return
51
+ }
52
+
53
+ const filesArray = Array.isArray(files) ? files : [files]
54
+ const compressed = await Promise.all(
55
+ filesArray.map(async (file) => {
56
+ if (!file.type.startsWith('image/')) return file
57
+ return await compressImage(file)
58
+ }),
59
+ )
60
+
61
+ model.value = (forwarded.value.multiple ? compressed : compressed[0]!) as Files
62
+ }
63
+
64
+ async function compressImage(file: File) {
65
+ if (forwarded.value.compression === false) return file
66
+ if (compressedFiles.has(file)) return compressedFiles.get(file)!
67
+
68
+ const maxDimension =
69
+ typeof forwarded.value.compression === 'object'
70
+ ? (forwarded.value.compression?.maxDimension ?? 1920)
71
+ : 1920
72
+ const quality =
73
+ typeof forwarded.value.compression === 'object'
74
+ ? (forwarded.value.compression?.quality ?? 0.85)
75
+ : 0.85
76
+ const outputType =
77
+ typeof forwarded.value.compression === 'object'
78
+ ? (forwarded.value.compression?.outputType ?? 'image/webp')
79
+ : 'image/webp'
80
+
81
+ const img = new Image()
82
+ await new Promise((resolve, reject) => {
83
+ img.addEventListener('load', resolve)
84
+ img.addEventListener('error', reject)
85
+ img.src = URL.createObjectURL(file)
86
+ })
87
+
88
+ const scale = Math.min(1, maxDimension / Math.max(img.width, img.height))
89
+ const canvas = document.createElement('canvas')
90
+ canvas.width = img.width * scale
91
+ canvas.height = img.height * scale
92
+ const ctx = canvas.getContext('2d')!
93
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
94
+ URL.revokeObjectURL(img.src)
95
+
96
+ return new Promise<File>((resolve, reject) => {
97
+ canvas.toBlob(
98
+ (blob) => {
99
+ if (!blob) return reject(new Error('Canvas is empty'))
100
+
101
+ const name = file.name.replace(/\.\w+$/, `.${outputType.split('/')[1]}`)
102
+ const compressedFile = new File([blob], name, { type: outputType })
103
+
104
+ compressedFiles.set(file, compressedFile)
105
+ compressedFiles.set(compressedFile, compressedFile) // required since we set model value to the compressed file
106
+ resolve(compressedFile)
107
+
108
+ emit('compressed', {
109
+ original: file,
110
+ compressed: compressedFile,
111
+ savedBytes: file.size - compressedFile.size,
112
+ savedPercentage: ((file.size - compressedFile.size) / file.size) * 100,
113
+ })
114
+ },
115
+ outputType,
116
+ quality,
117
+ )
118
+ canvas.remove()
119
+ })
120
+ }
121
+ </script>
122
+
123
+ <template>
124
+ <UFileUpload
125
+ v-bind="omit(forwarded, ['compression'])"
126
+ @update:model-value="(files) => forwardFiles(files)"
127
+ />
128
+ </template>
package/nuxt.config.ts CHANGED
@@ -11,6 +11,12 @@ export default defineNuxtConfig({
11
11
  typescript: {
12
12
  strict: true,
13
13
  tsConfig: {
14
+ vueCompilerOptions: {
15
+ strictTemplates: true,
16
+ strictVModel: false,
17
+ htmlAttributes: ['aria-*'],
18
+ dataAttributes: ['data-*'],
19
+ },
14
20
  compilerOptions: {
15
21
  allowArbitraryExtensions: true,
16
22
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@falcondev-oss/nuxt-layers-base",
3
3
  "type": "module",
4
- "version": "0.23.2",
4
+ "version": "0.25.0",
5
5
  "description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
6
6
  "license": "MIT",
7
7
  "repository": "github:falcondev-oss/nuxt-layers",
@@ -13,15 +13,18 @@
13
13
  "node": "24",
14
14
  "pnpm": "10"
15
15
  },
16
+ "peerDependencies": {
17
+ "@nuxt/ui": "~4.5.0"
18
+ },
16
19
  "dependencies": {
17
- "@falcondev-oss/form-core": "^0.21.1",
18
- "@falcondev-oss/form-vue": "^0.21.1",
20
+ "@falcondev-oss/form-core": "^0.21.2",
21
+ "@falcondev-oss/form-vue": "^0.21.2",
19
22
  "@falcondev-oss/trpc-typed-form-data": "^0.4.1",
20
23
  "@falcondev-oss/trpc-vue-query": "^0.5.2",
21
24
  "@iconify-json/lucide": "^1.2.94",
22
25
  "@internationalized/date": "^3.11.0",
23
26
  "@nuxt/icon": "^2.2.1",
24
- "@nuxt/ui": "4.4.0",
27
+ "@nuxt/ui": "~4.5.0",
25
28
  "@nuxtjs/color-mode": "^4.0.0",
26
29
  "@tanstack/vue-query": "^5.92.9",
27
30
  "@trpc/client": "^11.10.0",
@@ -52,7 +55,7 @@
52
55
  "scripts": {
53
56
  "dev": "nuxi dev .playground",
54
57
  "dev:prepare": "nuxt prepare .playground",
55
- "type-check": "vue-tsc --noEmit",
58
+ "type-check": "vue-tsc --build",
56
59
  "lint": "pnpm -w eslint:cmd \"$PWD\" && pnpm -w prettier:cmd \"$PWD\" --check",
57
60
  "lint:fix": "pnpm -w eslint:cmd \"$PWD\" --fix && pnpm -w prettier:cmd \"$PWD\" --write"
58
61
  }