nuxt-ui-elements 0.1.25 → 0.1.26

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 (37) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +2 -2
  3. package/dist/runtime/composables/{useUploader → useUploadManager}/index.d.ts +6 -6
  4. package/dist/runtime/composables/{useUploader → useUploadManager}/index.js +122 -27
  5. package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.d.ts +23 -16
  6. package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.js +27 -5
  7. package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/index.d.ts +1 -0
  8. package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/index.js +1 -0
  9. package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.d.ts +45 -0
  10. package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.js +108 -0
  11. package/dist/runtime/composables/useUploadManager/plugins/storage/index.d.ts +10 -0
  12. package/dist/runtime/composables/useUploadManager/plugins/storage/index.js +1 -0
  13. package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.d.ts +7 -0
  14. package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/thumbnail-generator.js +4 -3
  15. package/dist/runtime/composables/useUploadManager/types.d.ts +220 -0
  16. package/dist/runtime/composables/useUploadManager/types.js +3 -0
  17. package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.d.ts +5 -0
  18. package/dist/runtime/composables/{useUploader → useUploadManager}/validators/allowed-file-types.js +4 -3
  19. package/dist/runtime/composables/useUploadManager/validators/duplicate-file.d.ts +13 -0
  20. package/dist/runtime/composables/{useUploader → useUploadManager}/validators/duplicate-file.js +5 -4
  21. package/dist/runtime/composables/useUploadManager/validators/max-file-size.d.ts +5 -0
  22. package/dist/runtime/composables/{useUploader → useUploadManager}/validators/max-file-size.js +4 -3
  23. package/dist/runtime/composables/useUploadManager/validators/max-files.d.ts +5 -0
  24. package/dist/runtime/composables/useUploadManager/validators/max-files.js +13 -0
  25. package/dist/runtime/types/index.d.ts +3 -1
  26. package/dist/runtime/types/index.js +3 -1
  27. package/package.json +7 -1
  28. package/dist/runtime/composables/useUploader/plugins/thumbnail-generator.d.ts +0 -8
  29. package/dist/runtime/composables/useUploader/types.d.ts +0 -155
  30. package/dist/runtime/composables/useUploader/types.js +0 -0
  31. package/dist/runtime/composables/useUploader/validators/allowed-file-types.d.ts +0 -6
  32. package/dist/runtime/composables/useUploader/validators/duplicate-file.d.ts +0 -27
  33. package/dist/runtime/composables/useUploader/validators/max-file-size.d.ts +0 -6
  34. package/dist/runtime/composables/useUploader/validators/max-files.d.ts +0 -6
  35. package/dist/runtime/composables/useUploader/validators/max-files.js +0 -12
  36. /package/dist/runtime/composables/{useUploader → useUploadManager}/validators/index.d.ts +0 -0
  37. /package/dist/runtime/composables/{useUploader → useUploadManager}/validators/index.js +0 -0
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ui-elements",
3
3
  "configKey": "uiElements",
4
- "version": "0.1.25",
4
+ "version": "0.1.26",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -253,8 +253,8 @@ const module$1 = defineNuxtModule({
253
253
  addImportsDir(resolver.resolve("./runtime/composables"));
254
254
  addImports([
255
255
  {
256
- name: "useUploader",
257
- from: resolver.resolve("./runtime/composables/useUploader")
256
+ name: "useUploadManager",
257
+ from: resolver.resolve("./runtime/composables/useUploadManager")
258
258
  }
259
259
  ]);
260
260
  nuxt.options.alias["#std"] = resolver.resolve("./runtime/utils/std");
@@ -1,5 +1,5 @@
1
- import type { PluginFn, UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn } from "./types.js";
2
- export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions) => {
1
+ import type { UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn, Plugin as UploaderPlugin } from "./types.js";
2
+ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOptions) => {
3
3
  files: Readonly<import("vue").Ref<readonly {
4
4
  readonly id: string;
5
5
  readonly name: string;
@@ -146,7 +146,7 @@ export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions
146
146
  addFile: (file: File) => Promise<UploadFile<any> | null>;
147
147
  onGetRemoteFile: (fn: GetRemoteFileFn) => void;
148
148
  onUpload: (fn: UploadFn<TUploadResult>) => void;
149
- removeFile: (fileId: string) => void;
149
+ removeFile: (fileId: string) => Promise<void>;
150
150
  removeFiles: (fileIds: string[]) => {
151
151
  id: string;
152
152
  name: string;
@@ -360,9 +360,9 @@ export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions
360
360
  status: import("vue").Ref<UploadStatus, UploadStatus>;
361
361
  updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
362
362
  initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
363
- addPlugin: <TOptions>(fn: PluginFn<TOptions>, pluginOptions: TOptions) => void;
363
+ addPlugin: (plugin: UploaderPlugin<any, any>) => void;
364
364
  on: {
365
- <Key extends keyof UploaderEvents<TUploadResult>>(type: Key, handler: import("mitt").Handler<UploaderEvents<TUploadResult>[Key]>): void;
366
- (type: "*", handler: import("mitt").WildcardHandler<UploaderEvents<TUploadResult>>): void;
365
+ <K extends keyof UploaderEvents<TUploadResult>>(type: K, handler: (event: UploaderEvents<TUploadResult>[K]) => void): void;
366
+ (type: string, handler: (event: any) => void): void;
367
367
  };
368
368
  };
@@ -18,11 +18,23 @@ const defaultOptions = {
18
18
  imageCompression: false,
19
19
  autoProceed: false
20
20
  };
21
- export const useUploader = (_options = {}) => {
21
+ export const useUploadManager = (_options = {}) => {
22
22
  const options = { ...defaultOptions, ..._options };
23
23
  const files = ref([]);
24
24
  const emitter = mitt();
25
25
  const status = ref("waiting");
26
+ const pluginEmitFunctions = /* @__PURE__ */ new Map();
27
+ const getPluginEmitFn = (pluginId) => {
28
+ let emitFn = pluginEmitFunctions.get(pluginId);
29
+ if (!emitFn) {
30
+ emitFn = (event, payload) => {
31
+ const prefixedEvent = `${pluginId}:${String(event)}`;
32
+ emitter.emit(prefixedEvent, payload);
33
+ };
34
+ pluginEmitFunctions.set(pluginId, emitFn);
35
+ }
36
+ return emitFn;
37
+ };
26
38
  let uploadFn = async () => {
27
39
  throw new Error("No uploader configured");
28
40
  };
@@ -34,37 +46,71 @@ export const useUploader = (_options = {}) => {
34
46
  const sum = files.value.reduce((acc, file) => acc + file.progress.percentage, 0);
35
47
  return Math.round(sum / files.value.length);
36
48
  });
37
- const addPlugin = (fn, pluginOptions) => {
38
- const plugin = fn({ files: files.value, options }, pluginOptions);
49
+ const isStoragePlugin = (plugin) => {
50
+ return !!(plugin.hooks.upload || plugin.hooks.getRemoteFile || plugin.hooks.remove);
51
+ };
52
+ const getStoragePlugin = () => {
53
+ if (!options.plugins) return null;
54
+ for (let i = options.plugins.length - 1; i >= 0; i--) {
55
+ const plugin = options.plugins[i];
56
+ if (plugin && isStoragePlugin(plugin)) {
57
+ return plugin;
58
+ }
59
+ }
60
+ return null;
61
+ };
62
+ const addPlugin = (plugin) => {
63
+ if (isStoragePlugin(plugin)) {
64
+ const existingStorage = options.plugins?.find((p) => isStoragePlugin(p));
65
+ if (existingStorage && import.meta.dev) {
66
+ console.warn(
67
+ `[useUploadManager] Multiple storage plugins detected!
68
+
69
+ You're trying to add "${plugin.id}" but "${existingStorage.id}" is already registered.
70
+ The LAST storage plugin ("${plugin.id}") will be used for uploads.
71
+
72
+ \u{1F4A1} If you need multiple storage destinations, create separate uploader instances:
73
+
74
+ const s3Uploader = useUploadManager({ plugins: [PluginS3Storage({ ... })] })
75
+ const azureUploader = useUploadManager({ plugins: [PluginAzureStorage({ ... })] })
76
+ `
77
+ );
78
+ }
79
+ }
80
+ ;
39
81
  options.plugins?.push(plugin);
40
82
  };
41
83
  if (options.maxFiles !== false && options.maxFiles !== void 0) {
42
- addPlugin(ValidatorMaxFiles, { maxFiles: options.maxFiles });
84
+ addPlugin(ValidatorMaxFiles({ maxFiles: options.maxFiles }));
43
85
  }
44
86
  if (options.maxFileSize !== false && options.maxFileSize !== void 0) {
45
- addPlugin(ValidatorMaxfileSize, { maxFileSize: options.maxFileSize });
87
+ addPlugin(ValidatorMaxfileSize({ maxFileSize: options.maxFileSize }));
46
88
  }
47
89
  if (options.allowedFileTypes !== false && options.allowedFileTypes !== void 0 && options.allowedFileTypes.length > 0) {
48
- addPlugin(ValidatorAllowedFileTypes, { allowedFileTypes: options.allowedFileTypes });
90
+ addPlugin(ValidatorAllowedFileTypes({ allowedFileTypes: options.allowedFileTypes }));
49
91
  }
50
92
  if (options.thumbnails !== false && options.thumbnails !== void 0) {
51
93
  const thumbOpts = options.thumbnails === true ? {} : options.thumbnails || {};
52
- addPlugin(PluginThumbnailGenerator, {
53
- width: thumbOpts.width ?? 128,
54
- height: thumbOpts.height ?? 128,
55
- quality: thumbOpts.quality ?? 1
56
- });
94
+ addPlugin(
95
+ PluginThumbnailGenerator({
96
+ width: thumbOpts.width ?? 128,
97
+ height: thumbOpts.height ?? 128,
98
+ quality: thumbOpts.quality ?? 1
99
+ })
100
+ );
57
101
  }
58
102
  if (options.imageCompression !== false && options.imageCompression !== void 0) {
59
103
  const compressionOpts = options.imageCompression === true ? {} : options.imageCompression || {};
60
- addPlugin(PluginImageCompressor, {
61
- maxWidth: compressionOpts.maxWidth ?? 1920,
62
- maxHeight: compressionOpts.maxHeight ?? 1920,
63
- quality: compressionOpts.quality ?? 0.85,
64
- outputFormat: compressionOpts.outputFormat ?? "auto",
65
- minSizeToCompress: compressionOpts.minSizeToCompress ?? 1e5,
66
- preserveMetadata: compressionOpts.preserveMetadata ?? true
67
- });
104
+ addPlugin(
105
+ PluginImageCompressor({
106
+ maxWidth: compressionOpts.maxWidth ?? 1920,
107
+ maxHeight: compressionOpts.maxHeight ?? 1920,
108
+ quality: compressionOpts.quality ?? 0.85,
109
+ outputFormat: compressionOpts.outputFormat ?? "auto",
110
+ minSizeToCompress: compressionOpts.minSizeToCompress ?? 1e5,
111
+ preserveMetadata: compressionOpts.preserveMetadata ?? true
112
+ })
113
+ );
68
114
  }
69
115
  emitter.on("upload:progress", ({ file, progress }) => {
70
116
  updateFile(file.id, { progress: { percentage: progress } });
@@ -82,7 +128,21 @@ export const useUploader = (_options = {}) => {
82
128
  const initializedfiles = await Promise.all(
83
129
  initialFiles.map(async (file) => {
84
130
  if (!file.id) return null;
85
- const { mimeType, size, remoteUrl } = await getRemoteFileFn(file.id);
131
+ const storagePlugin = getStoragePlugin();
132
+ let remoteFileData;
133
+ if (storagePlugin?.hooks.getRemoteFile) {
134
+ const context = {
135
+ files: files.value,
136
+ options,
137
+ emit: (event, payload) => {
138
+ const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
139
+ emitter.emit(prefixedEvent, payload);
140
+ }
141
+ };
142
+ remoteFileData = await storagePlugin.hooks.getRemoteFile(file.id, context);
143
+ } else {
144
+ remoteFileData = await getRemoteFileFn(file.id);
145
+ }
86
146
  const existingFile = {
87
147
  ...file,
88
148
  id: file.id,
@@ -91,9 +151,9 @@ export const useUploader = (_options = {}) => {
91
151
  status: "complete",
92
152
  progress: { percentage: 100 },
93
153
  meta: {},
94
- size,
95
- mimeType,
96
- remoteUrl
154
+ size: remoteFileData.size,
155
+ mimeType: remoteFileData.mimeType,
156
+ remoteUrl: remoteFileData.remoteUrl
97
157
  };
98
158
  const processedFile = await runPluginStage("process", existingFile);
99
159
  if (!processedFile) return null;
@@ -134,9 +194,25 @@ export const useUploader = (_options = {}) => {
134
194
  const addFiles = (newFiles) => {
135
195
  return Promise.all(newFiles.map((file) => addFile(file)));
136
196
  };
137
- const removeFile = (fileId) => {
197
+ const removeFile = async (fileId) => {
138
198
  const file = files.value.find((f) => f.id === fileId);
139
199
  if (!file) return;
200
+ const storagePlugin = getStoragePlugin();
201
+ if (storagePlugin?.hooks.remove) {
202
+ try {
203
+ const context = {
204
+ files: files.value,
205
+ options,
206
+ emit: (event, payload) => {
207
+ const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
208
+ emitter.emit(prefixedEvent, payload);
209
+ }
210
+ };
211
+ await storagePlugin.hooks.remove(file, context);
212
+ } catch (error) {
213
+ console.error(`Storage plugin remove error:`, error);
214
+ }
215
+ }
140
216
  files.value = files.value.filter((f) => f.id !== fileId);
141
217
  emitter.emit("file:removed", file);
142
218
  };
@@ -188,7 +264,22 @@ export const useUploader = (_options = {}) => {
188
264
  updateFile(file.id, { progress: { percentage: progress } });
189
265
  emitter.emit("upload:progress", { file, progress });
190
266
  };
191
- const uploadResult = await uploadFn(file, onProgress);
267
+ const storagePlugin = getStoragePlugin();
268
+ let uploadResult;
269
+ if (storagePlugin?.hooks.upload) {
270
+ const context = {
271
+ files: files.value,
272
+ options,
273
+ onProgress,
274
+ emit: (event, payload) => {
275
+ const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
276
+ emitter.emit(prefixedEvent, payload);
277
+ }
278
+ };
279
+ uploadResult = await storagePlugin.hooks.upload(file, context);
280
+ } else {
281
+ uploadResult = await uploadFn(file, onProgress);
282
+ }
192
283
  updateFile(file.id, { status: "complete", uploadResult });
193
284
  } catch (err) {
194
285
  const error = {
@@ -225,12 +316,16 @@ export const useUploader = (_options = {}) => {
225
316
  };
226
317
  async function runPluginStage(stage, file) {
227
318
  if (!options.plugins) return file;
228
- const context = { files: files.value, options };
229
319
  let currentFile = file;
230
320
  for (const plugin of options.plugins) {
231
321
  const hook = plugin.hooks[stage];
232
322
  if (hook) {
233
323
  try {
324
+ const context = {
325
+ files: files.value,
326
+ options,
327
+ emit: getPluginEmitFn(plugin.id)
328
+ };
234
329
  const result = await callPluginHook(hook, stage, currentFile, context);
235
330
  if (!result) continue;
236
331
  if (currentFile && "id" in result) {
@@ -268,7 +363,7 @@ export const useUploader = (_options = {}) => {
268
363
  initializeExistingFiles,
269
364
  // Utilities
270
365
  addPlugin,
271
- // Events
366
+ // Events - autocomplete for core events, allow arbitrary strings for plugin events
272
367
  on: emitter.on
273
368
  };
274
369
  };
@@ -1,4 +1,25 @@
1
- import type { PluginFn } from "../types.js";
1
+ import type { UploadFile } from "../types.js";
2
+ /**
3
+ * Events emitted by the image compressor plugin
4
+ * Note: These are automatically prefixed with "image-compressor:" by the plugin system
5
+ * e.g., "start" becomes "image-compressor:start"
6
+ */
7
+ type ImageCompressorEvents = {
8
+ start: {
9
+ file: UploadFile;
10
+ originalSize: number;
11
+ };
12
+ complete: {
13
+ file: UploadFile;
14
+ originalSize: number;
15
+ compressedSize: number;
16
+ savedBytes: number;
17
+ };
18
+ skip: {
19
+ file: UploadFile;
20
+ reason: string;
21
+ };
22
+ };
2
23
  interface ImageCompressorOptions {
3
24
  /**
4
25
  * Maximum width in pixels. Images wider than this will be scaled down.
@@ -31,19 +52,5 @@ interface ImageCompressorOptions {
31
52
  */
32
53
  preserveMetadata?: boolean;
33
54
  }
34
- /**
35
- * Compresses images before upload to reduce bandwidth and storage costs.
36
- * Particularly useful for social media scheduling where images are often large.
37
- *
38
- * @example
39
- * ```ts
40
- * const uploader = useUpload()
41
- * uploader.addPlugin(PluginImageCompressor, {
42
- * maxWidth: 2048,
43
- * quality: 0.85,
44
- * outputFormat: 'webp'
45
- * })
46
- * ```
47
- */
48
- export declare const PluginImageCompressor: PluginFn<ImageCompressorOptions>;
55
+ export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").Plugin<any, ImageCompressorEvents>;
49
56
  export {};
@@ -1,29 +1,36 @@
1
- export const PluginImageCompressor = (_, pluginOptions) => {
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const PluginImageCompressor = defineUploaderPlugin((pluginOptions) => {
2
3
  const {
3
4
  maxWidth = 1920,
4
5
  maxHeight = 1920,
5
6
  quality = 0.85,
6
7
  outputFormat = "auto",
7
- minSizeToCompress = 1e5,
8
+ minSizeToCompress = 1e5
8
9
  // 100KB
9
- preserveMetadata = true
10
10
  } = pluginOptions;
11
11
  return {
12
12
  id: "image-compressor",
13
13
  hooks: {
14
- process: async (file) => {
14
+ process: async (file, context) => {
15
15
  if (!file.mimeType.startsWith("image/")) {
16
16
  return file;
17
17
  }
18
18
  if (file.mimeType === "image/gif") {
19
+ context.emit("skip", { file, reason: "GIF format not supported" });
19
20
  return file;
20
21
  }
21
22
  if (file.mimeType === "image/svg+xml") {
23
+ context.emit("skip", { file, reason: "SVG already optimized" });
22
24
  return file;
23
25
  }
24
26
  if (file.size < minSizeToCompress) {
27
+ context.emit("skip", {
28
+ file,
29
+ reason: `File size (${file.size} bytes) below minimum threshold`
30
+ });
25
31
  return file;
26
32
  }
33
+ context.emit("start", { file, originalSize: file.size });
27
34
  try {
28
35
  const sourceUrl = URL.createObjectURL(file.data);
29
36
  const image = new Image();
@@ -35,6 +42,10 @@ export const PluginImageCompressor = (_, pluginOptions) => {
35
42
  const needsResize = image.width > maxWidth || image.height > maxHeight;
36
43
  if (!needsResize && outputFormat === "auto") {
37
44
  URL.revokeObjectURL(sourceUrl);
45
+ context.emit("skip", {
46
+ file,
47
+ reason: "Image within size limits and format is auto"
48
+ });
38
49
  return file;
39
50
  }
40
51
  let targetWidth = image.width;
@@ -79,6 +90,13 @@ export const PluginImageCompressor = (_, pluginOptions) => {
79
90
  });
80
91
  URL.revokeObjectURL(sourceUrl);
81
92
  if (compressedBlob.size < file.size) {
93
+ const savedBytes = file.size - compressedBlob.size;
94
+ context.emit("complete", {
95
+ file,
96
+ originalSize: file.size,
97
+ compressedSize: compressedBlob.size,
98
+ savedBytes
99
+ });
82
100
  let newId = file.id;
83
101
  if (outputFormat !== "auto" && outputFormat !== file.meta.extension) {
84
102
  const extension = outputFormat === "jpeg" ? "jpg" : outputFormat;
@@ -100,6 +118,10 @@ export const PluginImageCompressor = (_, pluginOptions) => {
100
118
  }
101
119
  };
102
120
  }
121
+ context.emit("skip", {
122
+ file,
123
+ reason: "Compressed version larger than original"
124
+ });
103
125
  return file;
104
126
  } catch (error) {
105
127
  console.warn(`Image compression failed for ${file.name}:`, error);
@@ -108,4 +130,4 @@ export const PluginImageCompressor = (_, pluginOptions) => {
108
130
  }
109
131
  }
110
132
  };
111
- };
133
+ });
@@ -1,2 +1,3 @@
1
1
  export * from "./thumbnail-generator.js";
2
2
  export * from "./image-compressor.js";
3
+ export * from "./storage/index.js";
@@ -1,2 +1,3 @@
1
1
  export * from "./thumbnail-generator.js";
2
2
  export * from "./image-compressor.js";
3
+ export * from "./storage/index.js";
@@ -0,0 +1,45 @@
1
+ type PathHttpHeaders = import("@azure/storage-file-datalake").PathHttpHeaders;
2
+ export interface AzureDataLakeOptions {
3
+ /**
4
+ * Static SAS URL for Azure Data Lake Storage
5
+ */
6
+ sasURL?: string;
7
+ /**
8
+ * Function to dynamically fetch SAS URL
9
+ * Use this to handle token expiration/refreshing.
10
+ * If provided, it will be called before every file operation.
11
+ */
12
+ getSASUrl?: () => Promise<string>;
13
+ /**
14
+ * Optional subdirectory path within the container
15
+ * @example "uploads/images"
16
+ */
17
+ path?: string;
18
+ /**
19
+ * Custom metadata to attach to uploaded files
20
+ */
21
+ metadata?: Record<string, string>;
22
+ /**
23
+ * Custom HTTP headers for uploaded files
24
+ */
25
+ pathHttpHeaders?: Omit<PathHttpHeaders, "contentType">;
26
+ /**
27
+ * Automatically try to create the directory if it doesn't exist.
28
+ * Disable this if your SAS token only has 'Write' (Blob) permissions
29
+ * and not 'Create' (Directory) permissions.
30
+ * @default true
31
+ */
32
+ autoCreateDirectory?: boolean;
33
+ }
34
+ export interface AzureUploadResult {
35
+ /**
36
+ * Full URL to the uploaded file
37
+ */
38
+ url: string;
39
+ /**
40
+ * File name/path in the storage
41
+ */
42
+ blobPath: string;
43
+ }
44
+ export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").Plugin<any, Record<string, never>>;
45
+ export {};
@@ -0,0 +1,108 @@
1
+ import { ref } from "vue";
2
+ import { defineUploaderPlugin } from "../../types.js";
3
+ export const PluginAzureDataLake = defineUploaderPlugin((options) => {
4
+ let DataLakeDirectoryClient;
5
+ const sasURL = ref(options.sasURL || "");
6
+ let refreshPromise = null;
7
+ const directoryCheckedCache = /* @__PURE__ */ new Set();
8
+ if (options.getSASUrl && !options.sasURL) {
9
+ options.getSASUrl().then((url) => {
10
+ sasURL.value = url;
11
+ });
12
+ }
13
+ const isTokenExpired = (urlStr, bufferMinutes = 5) => {
14
+ if (!urlStr) return true;
15
+ try {
16
+ const url = new URL(urlStr);
17
+ const expiryStr = url.searchParams.get("se");
18
+ if (!expiryStr) return true;
19
+ const expiry = new Date(expiryStr);
20
+ const now = /* @__PURE__ */ new Date();
21
+ return now.getTime() + bufferMinutes * 60 * 1e3 > expiry.getTime();
22
+ } catch {
23
+ return true;
24
+ }
25
+ };
26
+ const getFileClient = async (blobName) => {
27
+ if (!DataLakeDirectoryClient) {
28
+ const module = await import("@azure/storage-file-datalake");
29
+ DataLakeDirectoryClient = module.DataLakeDirectoryClient;
30
+ }
31
+ if (options.getSASUrl && isTokenExpired(sasURL.value)) {
32
+ if (!refreshPromise) {
33
+ refreshPromise = options.getSASUrl().then((url) => {
34
+ refreshPromise = null;
35
+ return url;
36
+ });
37
+ }
38
+ sasURL.value = await refreshPromise;
39
+ }
40
+ let dir = new DataLakeDirectoryClient(sasURL.value);
41
+ if (options.path) {
42
+ const cleanPath = options.path.replace(/^\/+|\/+$/g, "");
43
+ dir = dir.getSubdirectoryClient(cleanPath);
44
+ const shouldCreateDir = options.autoCreateDirectory ?? true;
45
+ if (shouldCreateDir && !directoryCheckedCache.has(cleanPath)) {
46
+ try {
47
+ await dir.createIfNotExists();
48
+ directoryCheckedCache.add(cleanPath);
49
+ } catch (error) {
50
+ if (import.meta.dev) {
51
+ console.debug(`Azure directory already exists or couldn't be created: ${cleanPath}`, error);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return dir.getFileClient(blobName);
57
+ };
58
+ return {
59
+ id: "azure-datalake-storage",
60
+ hooks: {
61
+ /**
62
+ * Upload file to Azure Blob Storage
63
+ */
64
+ async upload(file, context) {
65
+ const fileClient = await getFileClient(file.id);
66
+ await fileClient.upload(file.data, {
67
+ metadata: {
68
+ ...options.metadata,
69
+ mimeType: file.mimeType,
70
+ size: String(file.size),
71
+ originalName: file.name
72
+ },
73
+ pathHttpHeaders: {
74
+ ...options.pathHttpHeaders,
75
+ contentType: file.mimeType
76
+ },
77
+ onProgress: ({ loadedBytes }) => {
78
+ const uploadedPercentage = Math.round(loadedBytes / file.size * 100);
79
+ context.onProgress(uploadedPercentage);
80
+ }
81
+ });
82
+ return {
83
+ url: fileClient.url,
84
+ blobPath: fileClient.name
85
+ };
86
+ },
87
+ /**
88
+ * Get remote file metadata from Azure
89
+ */
90
+ async getRemoteFile(fileId, _context) {
91
+ const fileClient = await getFileClient(fileId);
92
+ const properties = await fileClient.getProperties();
93
+ return {
94
+ size: properties.contentLength || 0,
95
+ mimeType: properties.contentType || "application/octet-stream",
96
+ remoteUrl: fileClient.url
97
+ };
98
+ },
99
+ /**
100
+ * Delete file from Azure Blob Storage
101
+ */
102
+ async remove(file, _context) {
103
+ const fileClient = await getFileClient(file.id);
104
+ await fileClient.deleteIfExists();
105
+ }
106
+ }
107
+ };
108
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Storage Adapter Plugins
3
+ *
4
+ * These plugins handle the actual upload, download, and deletion of files
5
+ * to/from various cloud storage providers.
6
+ *
7
+ * Only ONE storage plugin should be active per uploader instance.
8
+ * If you need multiple storage destinations, create multiple uploader instances.
9
+ */
10
+ export { PluginAzureDataLake, type AzureDataLakeOptions, type AzureUploadResult } from "./azure-datalake.js";
@@ -0,0 +1 @@
1
+ export { PluginAzureDataLake } from "./azure-datalake.js";
@@ -0,0 +1,7 @@
1
+ interface ThumbnailGeneratorOptions {
2
+ width?: number;
3
+ height?: number;
4
+ quality?: number;
5
+ }
6
+ export declare const PluginThumbnailGenerator: (options: ThumbnailGeneratorOptions) => import("../types.js").Plugin<any, Record<string, never>>;
7
+ export {};
@@ -1,8 +1,9 @@
1
- export const PluginThumbnailGenerator = (_options, pluginOptions) => {
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const PluginThumbnailGenerator = defineUploaderPlugin((pluginOptions) => {
2
3
  return {
3
4
  id: "thumbnail-generator",
4
5
  hooks: {
5
- process: async (file) => {
6
+ process: async (file, _context) => {
6
7
  const { width = 100, height = 100, quality = 0.7 } = pluginOptions;
7
8
  const sourceUrl = file.isRemote ? file.remoteUrl : URL.createObjectURL(file.data);
8
9
  if (file.mimeType.startsWith("image/")) {
@@ -62,4 +63,4 @@ export const PluginThumbnailGenerator = (_options, pluginOptions) => {
62
63
  }
63
64
  }
64
65
  };
65
- };
66
+ });
@@ -0,0 +1,220 @@
1
+ import type { Emitter } from "mitt";
2
+ /**
3
+ * PUBLIC API - Types users commonly need
4
+ * These are exported from the main package
5
+ */
6
+ export type FileStatus = "waiting" | "preprocessing" | "uploading" | "postprocessing" | "complete" | "error";
7
+ export type UploadStatus = "waiting" | "uploading";
8
+ export interface FileProgress {
9
+ percentage: number;
10
+ }
11
+ export interface FileError {
12
+ message: string;
13
+ details?: unknown;
14
+ }
15
+ export interface UploadFile<TUploadResult = any> {
16
+ id: string;
17
+ name: string;
18
+ size: number;
19
+ mimeType: string;
20
+ data: File | Blob;
21
+ status: FileStatus;
22
+ preview?: string;
23
+ progress: FileProgress;
24
+ error?: FileError;
25
+ uploadResult?: TUploadResult;
26
+ isRemote?: boolean;
27
+ remoteUrl?: string;
28
+ meta: Record<string, unknown>;
29
+ }
30
+ export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
31
+ export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
32
+ export interface UploadOptions {
33
+ /**
34
+ * Custom plugins to add (in addition to built-in plugins)
35
+ */
36
+ plugins?: Plugin<any, any>[];
37
+ /**
38
+ * Validate maximum number of files
39
+ * - false: disabled
40
+ * - number: enabled with limit
41
+ * @default false
42
+ */
43
+ maxFiles?: false | number;
44
+ /**
45
+ * Validate maximum file size in bytes
46
+ * - false: disabled
47
+ * - number: enabled with limit
48
+ * @default false
49
+ */
50
+ maxFileSize?: false | number;
51
+ /**
52
+ * Validate allowed file MIME types
53
+ * - false: disabled
54
+ * - string[]: enabled with allowed types
55
+ * @default false
56
+ */
57
+ allowedFileTypes?: false | string[];
58
+ /**
59
+ * Generate thumbnail previews for images/videos
60
+ * - false: disabled
61
+ * - true: enabled with defaults
62
+ * - object: enabled with custom options
63
+ * @default false
64
+ */
65
+ thumbnails?: false | true | ThumbnailOptions;
66
+ /**
67
+ * Compress images before upload
68
+ * - false: disabled
69
+ * - true: enabled with defaults
70
+ * - object: enabled with custom options
71
+ * @default false
72
+ */
73
+ imageCompression?: false | true | ImageCompressionOptions;
74
+ /**
75
+ * Automatically start upload after files are added
76
+ * @default false
77
+ */
78
+ autoProceed?: boolean;
79
+ }
80
+ export interface ThumbnailOptions {
81
+ width?: number;
82
+ height?: number;
83
+ quality?: number;
84
+ }
85
+ export interface ImageCompressionOptions {
86
+ maxWidth?: number;
87
+ maxHeight?: number;
88
+ quality?: number;
89
+ outputFormat?: "jpeg" | "webp" | "png" | "auto";
90
+ minSizeToCompress?: number;
91
+ preserveMetadata?: boolean;
92
+ }
93
+ type CoreUploaderEvents<TUploadResult = any> = {
94
+ "file:added": Readonly<UploadFile<TUploadResult>>;
95
+ "file:removed": Readonly<UploadFile<TUploadResult>>;
96
+ "file:processing": Readonly<UploadFile<TUploadResult>>;
97
+ "file:error": {
98
+ file: Readonly<UploadFile<TUploadResult>>;
99
+ error: FileError;
100
+ };
101
+ "upload:start": Array<Readonly<UploadFile<TUploadResult>>>;
102
+ "upload:complete": Array<Required<Readonly<UploadFile<TUploadResult>>>>;
103
+ "upload:error": FileError;
104
+ "upload:progress": {
105
+ file: Readonly<UploadFile<TUploadResult>>;
106
+ progress: number;
107
+ };
108
+ "files:reorder": {
109
+ oldIndex: number;
110
+ newIndex: number;
111
+ };
112
+ };
113
+ export type UploaderEvents<TUploadResult = any> = CoreUploaderEvents<TUploadResult>;
114
+ /**
115
+ * PLUGIN API - Types for building custom plugins
116
+ * Only needed if users want to create custom validators/processors
117
+ */
118
+ export type PluginContext<TPluginEvents extends Record<string, any> = Record<string, never>> = {
119
+ files: UploadFile[];
120
+ options: UploadOptions;
121
+ /**
122
+ * Emit custom plugin events
123
+ * Events are automatically prefixed with the plugin ID
124
+ */
125
+ emit: <K extends keyof TPluginEvents>(event: K, payload: TPluginEvents[K]) => void;
126
+ };
127
+ export type ValidationHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<true | UploadFile>;
128
+ export type ProcessingHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<UploadFile>;
129
+ export type SetupHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (context: PluginContext<TPluginEvents>) => Promise<void>;
130
+ /**
131
+ * Storage hooks for handling upload/download/deletion operations
132
+ */
133
+ export type UploadHook<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile<TUploadResult>, context: PluginContext<TPluginEvents> & {
134
+ onProgress: (progress: number) => void;
135
+ }) => Promise<TUploadResult>;
136
+ export type GetRemoteFileHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (fileId: string, context: PluginContext<TPluginEvents>) => Promise<MinimumRemoteFileAttributes>;
137
+ export type RemoveHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<void>;
138
+ export type PluginLifecycleStage = "validate" | "process" | "upload" | "complete";
139
+ export type PluginHooks<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = {
140
+ validate?: ValidationHook<TPluginEvents>;
141
+ process?: ProcessingHook<TPluginEvents>;
142
+ upload?: UploadHook<TUploadResult, TPluginEvents>;
143
+ getRemoteFile?: GetRemoteFileHook<TPluginEvents>;
144
+ remove?: RemoveHook<TPluginEvents>;
145
+ complete?: ProcessingHook<TPluginEvents>;
146
+ };
147
+ export interface Plugin<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> {
148
+ id: string;
149
+ hooks: PluginHooks<TUploadResult, TPluginEvents>;
150
+ options?: UploadOptions;
151
+ events?: TPluginEvents;
152
+ }
153
+ /**
154
+ * Define an uploader plugin with type safety, context access, and custom events.
155
+ * This is the universal plugin factory for all plugin types (storage, validators, processors).
156
+ *
157
+ * Hooks receive context as a parameter, making it clear when context is available.
158
+ * Context includes current files, options, and an emit function for custom events.
159
+ *
160
+ * @example Basic Plugin
161
+ * ```typescript
162
+ * export const ValidatorMaxFiles = defineUploaderPlugin<ValidatorOptions>((options) => ({
163
+ * id: 'validator-max-files',
164
+ * hooks: {
165
+ * validate: async (file, context) => {
166
+ * if (context.files.length >= options.maxFiles) {
167
+ * throw { message: 'Too many files' }
168
+ * }
169
+ * return file
170
+ * }
171
+ * }
172
+ * }))
173
+ * ```
174
+ *
175
+ * @example Plugin with Custom Events
176
+ * ```typescript
177
+ * type CompressionEvents = {
178
+ * start: { file: UploadFile; originalSize: number }
179
+ * complete: { file: UploadFile; savedBytes: number }
180
+ * }
181
+ *
182
+ * export const PluginImageCompressor = defineUploaderPlugin<
183
+ * ImageCompressorOptions,
184
+ * CompressionEvents
185
+ * >((options) => ({
186
+ * id: 'image-compressor',
187
+ * hooks: {
188
+ * process: async (file, context) => {
189
+ * context.emit('start', { file, originalSize: file.size })
190
+ * // ... compression logic ...
191
+ * context.emit('complete', { file, savedBytes: 1000 })
192
+ * return file
193
+ * }
194
+ * }
195
+ * }))
196
+ *
197
+ * // Usage - events are automatically prefixed with plugin ID
198
+ * uploader.on('image-compressor:complete', ({ file, savedBytes }) => {
199
+ * console.log(`Saved ${savedBytes} bytes`)
200
+ * })
201
+ * ```
202
+ */
203
+ export declare function defineUploaderPlugin<TPluginOptions = unknown, TPluginEvents extends Record<string, any> = Record<string, never>>(factory: (options: TPluginOptions) => Plugin<any, TPluginEvents>): (options: TPluginOptions) => Plugin<any, TPluginEvents>;
204
+ /**
205
+ * INTERNAL TYPES - Not commonly needed by users
206
+ * Kept exported for edge cases but not primary API
207
+ */
208
+ export type PreProcessor = (file: UploadFile) => Promise<UploadFile>;
209
+ export type Uploader = (file: Readonly<UploadFile>, emiter: Emitter<Pick<UploaderEvents, "upload:error" | "upload:progress">>) => Promise<string>;
210
+ export type Validator = (file: UploadFile) => Promise<boolean | FileError>;
211
+ export type Processor = (file: UploadFile) => Promise<File | Blob>;
212
+ export interface UploadBlob {
213
+ blobPath: string;
214
+ }
215
+ type MinimumRemoteFileAttributes = {
216
+ size: number;
217
+ mimeType: string;
218
+ remoteUrl: string;
219
+ };
220
+ export {};
@@ -0,0 +1,3 @@
1
+ export function defineUploaderPlugin(factory) {
2
+ return factory;
3
+ }
@@ -0,0 +1,5 @@
1
+ interface ValidatorAllowedFileTypesOptions {
2
+ allowedFileTypes?: string[];
3
+ }
4
+ export declare const ValidatorAllowedFileTypes: (options: ValidatorAllowedFileTypesOptions) => import("../types.js").Plugin<any, Record<string, never>>;
5
+ export {};
@@ -1,12 +1,13 @@
1
- export const ValidatorAllowedFileTypes = (_, options) => {
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const ValidatorAllowedFileTypes = defineUploaderPlugin((options) => {
2
3
  return {
3
4
  id: "validator-allowed-file-types",
4
5
  hooks: {
5
- validate: async (file) => {
6
+ validate: async (file, _context) => {
6
7
  if (options.allowedFileTypes && options.allowedFileTypes.includes(file.mimeType) || (options.allowedFileTypes && options.allowedFileTypes.length) === 0)
7
8
  return file;
8
9
  throw { message: `File type ${file.mimeType} is not allowed` };
9
10
  }
10
11
  }
11
12
  };
12
- };
13
+ });
@@ -0,0 +1,13 @@
1
+ interface ValidatorDuplicateFileOptions {
2
+ /**
3
+ * Whether to allow duplicate files
4
+ * @default false
5
+ */
6
+ allowDuplicates?: boolean;
7
+ /**
8
+ * Custom error message for duplicates
9
+ */
10
+ errorMessage?: string;
11
+ }
12
+ export declare const ValidatorDuplicateFile: (options: ValidatorDuplicateFileOptions) => import("../types.js").Plugin<any, Record<string, never>>;
13
+ export {};
@@ -1,13 +1,14 @@
1
- export const ValidatorDuplicateFile = ({ files }, options) => {
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const ValidatorDuplicateFile = defineUploaderPlugin((options) => {
2
3
  const { allowDuplicates = false, errorMessage = "This file has already been added" } = options;
3
4
  return {
4
5
  id: "validator-duplicate-file",
5
6
  hooks: {
6
- validate: async (file) => {
7
+ validate: async (file, context) => {
7
8
  if (allowDuplicates) {
8
9
  return file;
9
10
  }
10
- const isDuplicate = files.some((existingFile) => {
11
+ const isDuplicate = context.files.some((existingFile) => {
11
12
  const sameSize = existingFile.size === file.size;
12
13
  const sameName = existingFile.name === file.name;
13
14
  let sameDate = true;
@@ -23,4 +24,4 @@ export const ValidatorDuplicateFile = ({ files }, options) => {
23
24
  }
24
25
  }
25
26
  };
26
- };
27
+ });
@@ -0,0 +1,5 @@
1
+ interface ValidatorMaxfileSizeOptions {
2
+ maxFileSize?: number;
3
+ }
4
+ export declare const ValidatorMaxfileSize: (options: ValidatorMaxfileSizeOptions) => import("../types.js").Plugin<any, Record<string, never>>;
5
+ export {};
@@ -1,12 +1,13 @@
1
- export const ValidatorMaxfileSize = (_, options) => {
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const ValidatorMaxfileSize = defineUploaderPlugin((options) => {
2
3
  return {
3
4
  id: "validator-max-file-size",
4
5
  hooks: {
5
- validate: async (file) => {
6
+ validate: async (file, _context) => {
6
7
  if (options.maxFileSize && options.maxFileSize !== Infinity && file.size <= options.maxFileSize || options.maxFileSize && options.maxFileSize === Infinity)
7
8
  return file;
8
9
  throw { message: `File size exceeds the maximum limit of ${options.maxFileSize} bytes` };
9
10
  }
10
11
  }
11
12
  };
12
- };
13
+ });
@@ -0,0 +1,5 @@
1
+ interface ValidatorMaxFilesOptions {
2
+ maxFiles?: number;
3
+ }
4
+ export declare const ValidatorMaxFiles: (options: ValidatorMaxFilesOptions) => import("../types.js").Plugin<any, Record<string, never>>;
5
+ export {};
@@ -0,0 +1,13 @@
1
+ import { defineUploaderPlugin } from "../types.js";
2
+ export const ValidatorMaxFiles = defineUploaderPlugin((options) => {
3
+ return {
4
+ id: "validator-max-files",
5
+ hooks: {
6
+ validate: async (file, context) => {
7
+ if (options.maxFiles && options.maxFiles !== Infinity && context.files.length < options.maxFiles || options.maxFiles && options.maxFiles === Infinity)
8
+ return file;
9
+ throw { message: `Maximum number of files (${options.maxFiles}) exceeded` };
10
+ }
11
+ }
12
+ };
13
+ });
@@ -1,4 +1,6 @@
1
1
  export * from "../components/DialogConfirm.vue.js";
2
2
  export * from "../components/DialogAlert.vue.js";
3
3
  export * from "./tv.js";
4
- export * from "../composables/useUploader/types.js";
4
+ export * from "../composables/useUploadManager/types.js";
5
+ export * from "../composables/useUploadManager/plugins/index.js";
6
+ export * from "../composables/useUploadManager/validators/index.js";
@@ -1,4 +1,6 @@
1
1
  export * from "../components/DialogConfirm.vue";
2
2
  export * from "../components/DialogAlert.vue";
3
3
  export * from "./tv.js";
4
- export * from "../composables/useUploader/types.js";
4
+ export * from "../composables/useUploadManager/types.js";
5
+ export * from "../composables/useUploadManager/plugins/index.js";
6
+ export * from "../composables/useUploadManager/validators/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-ui-elements",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "A collection of beautiful, animated UI components for Nuxt applications",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/genu/nuxt-ui-elements.git",
@@ -34,6 +34,7 @@
34
34
  "tailwind-variants": "^3.2.2"
35
35
  },
36
36
  "devDependencies": {
37
+ "@azure/storage-file-datalake": "^12.28.1",
37
38
  "@nuxt/devtools": "^3.1.1",
38
39
  "@nuxt/eslint-config": "^1.12.1",
39
40
  "@nuxt/module-builder": "^1.0.2",
@@ -55,6 +56,11 @@
55
56
  "peerDependencies": {
56
57
  "@nuxt/ui": "^4.0.0"
57
58
  },
59
+ "peerDependenciesMeta": {
60
+ "@azure/storage-file-datalake": {
61
+ "optional": true
62
+ }
63
+ },
58
64
  "scripts": {
59
65
  "dev": "pnpm dev:prepare && nuxi dev playground",
60
66
  "dev:build": "nuxi build playground",
@@ -1,8 +0,0 @@
1
- import type { PluginFn } from "../types.js";
2
- interface ThumbnailGeneratorOptions {
3
- width?: number;
4
- height?: number;
5
- quality?: number;
6
- }
7
- export declare const PluginThumbnailGenerator: PluginFn<ThumbnailGeneratorOptions>;
8
- export {};
@@ -1,155 +0,0 @@
1
- import type { Emitter } from "mitt";
2
- /**
3
- * PUBLIC API - Types users commonly need
4
- * These are exported from the main package
5
- */
6
- export type FileStatus = "waiting" | "preprocessing" | "uploading" | "postprocessing" | "complete" | "error";
7
- export type UploadStatus = "waiting" | "uploading";
8
- export interface FileProgress {
9
- percentage: number;
10
- }
11
- export interface FileError {
12
- message: string;
13
- details?: unknown;
14
- }
15
- export interface UploadFile<TUploadResult = any> {
16
- id: string;
17
- name: string;
18
- size: number;
19
- mimeType: string;
20
- data: File | Blob;
21
- status: FileStatus;
22
- preview?: string;
23
- progress: FileProgress;
24
- error?: FileError;
25
- uploadResult?: TUploadResult;
26
- isRemote?: boolean;
27
- remoteUrl?: string;
28
- meta: Record<string, unknown>;
29
- }
30
- export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
31
- export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
32
- export interface UploadOptions {
33
- /**
34
- * Custom plugins to add (in addition to built-in plugins)
35
- */
36
- plugins?: Plugin[];
37
- /**
38
- * Validate maximum number of files
39
- * - false: disabled
40
- * - number: enabled with limit
41
- * @default false
42
- */
43
- maxFiles?: false | number;
44
- /**
45
- * Validate maximum file size in bytes
46
- * - false: disabled
47
- * - number: enabled with limit
48
- * @default false
49
- */
50
- maxFileSize?: false | number;
51
- /**
52
- * Validate allowed file MIME types
53
- * - false: disabled
54
- * - string[]: enabled with allowed types
55
- * @default false
56
- */
57
- allowedFileTypes?: false | string[];
58
- /**
59
- * Generate thumbnail previews for images/videos
60
- * - false: disabled
61
- * - true: enabled with defaults
62
- * - object: enabled with custom options
63
- * @default false
64
- */
65
- thumbnails?: false | true | ThumbnailOptions;
66
- /**
67
- * Compress images before upload
68
- * - false: disabled
69
- * - true: enabled with defaults
70
- * - object: enabled with custom options
71
- * @default false
72
- */
73
- imageCompression?: false | true | ImageCompressionOptions;
74
- /**
75
- * Automatically start upload after files are added
76
- * @default false
77
- */
78
- autoProceed?: boolean;
79
- }
80
- export interface ThumbnailOptions {
81
- width?: number;
82
- height?: number;
83
- quality?: number;
84
- }
85
- export interface ImageCompressionOptions {
86
- maxWidth?: number;
87
- maxHeight?: number;
88
- quality?: number;
89
- outputFormat?: "jpeg" | "webp" | "png" | "auto";
90
- minSizeToCompress?: number;
91
- preserveMetadata?: boolean;
92
- }
93
- export type UploaderEvents<TUploadResult = any> = {
94
- "file:added": Readonly<UploadFile<TUploadResult>>;
95
- "file:removed": Readonly<UploadFile<TUploadResult>>;
96
- "file:processing": Readonly<UploadFile<TUploadResult>>;
97
- "file:error": {
98
- file: Readonly<UploadFile<TUploadResult>>;
99
- error: FileError;
100
- };
101
- "upload:start": Array<Readonly<UploadFile<TUploadResult>>>;
102
- "upload:complete": Array<Required<Readonly<UploadFile<TUploadResult>>>>;
103
- "upload:error": FileError;
104
- "upload:progress": {
105
- file: Readonly<UploadFile<TUploadResult>>;
106
- progress: number;
107
- };
108
- "files:reorder": {
109
- oldIndex: number;
110
- newIndex: number;
111
- };
112
- };
113
- /**
114
- * PLUGIN API - Types for building custom plugins
115
- * Only needed if users want to create custom validators/processors
116
- */
117
- export type PluginContext = {
118
- files: UploadFile[];
119
- options: UploadOptions;
120
- };
121
- export type ValidationHook = (file: UploadFile, context: PluginContext) => Promise<true | UploadFile>;
122
- export type ProcessingHook = (file: UploadFile, context: PluginContext) => Promise<UploadFile>;
123
- export type SetupHook = (context: PluginContext) => Promise<void>;
124
- export type PluginLifecycleStage = "validate" | "process" | "complete";
125
- export type PluginHooks = {
126
- validate?: ValidationHook;
127
- process?: ProcessingHook;
128
- complete?: ProcessingHook;
129
- };
130
- export interface Plugin {
131
- id: string;
132
- hooks: PluginHooks;
133
- options?: UploadOptions;
134
- }
135
- export type PluginFn<TPluginOptions = unknown> = {
136
- (context: PluginContext, pluginOptions: TPluginOptions): Plugin;
137
- __pluginOptions?: TPluginOptions;
138
- };
139
- /**
140
- * INTERNAL TYPES - Not commonly needed by users
141
- * Kept exported for edge cases but not primary API
142
- */
143
- export type PreProcessor = (file: UploadFile) => Promise<UploadFile>;
144
- export type Uploader = (file: Readonly<UploadFile>, emiter: Emitter<Pick<UploaderEvents, "upload:error" | "upload:progress">>) => Promise<string>;
145
- export type Validator = (file: UploadFile) => Promise<boolean | FileError>;
146
- export type Processor = (file: UploadFile) => Promise<File | Blob>;
147
- export interface UploadBlob {
148
- blobPath: string;
149
- }
150
- type MinimumRemoteFileAttributes = {
151
- size: number;
152
- mimeType: string;
153
- remoteUrl: string;
154
- };
155
- export {};
File without changes
@@ -1,6 +0,0 @@
1
- import type { PluginFn } from "../types.js";
2
- interface ValidatorAllowedFileTypesOptions {
3
- allowedFileTypes?: string[];
4
- }
5
- export declare const ValidatorAllowedFileTypes: PluginFn<ValidatorAllowedFileTypesOptions>;
6
- export {};
@@ -1,27 +0,0 @@
1
- import type { PluginFn } from "../types.js";
2
- interface ValidatorDuplicateFileOptions {
3
- /**
4
- * Whether to allow duplicate files
5
- * @default false
6
- */
7
- allowDuplicates?: boolean;
8
- /**
9
- * Custom error message for duplicates
10
- */
11
- errorMessage?: string;
12
- }
13
- /**
14
- * Prevents uploading duplicate files based on name, size, and last modified date.
15
- * Useful for preventing accidental double-uploads in social media scheduling.
16
- *
17
- * @example
18
- * ```ts
19
- * const uploader = useUpload()
20
- * uploader.addPlugin(ValidatorDuplicateFile, {
21
- * allowDuplicates: false,
22
- * errorMessage: 'This file has already been added'
23
- * })
24
- * ```
25
- */
26
- export declare const ValidatorDuplicateFile: PluginFn<ValidatorDuplicateFileOptions>;
27
- export {};
@@ -1,6 +0,0 @@
1
- import type { PluginFn } from "../types.js";
2
- interface ValidatorMaxfileSizeOptions {
3
- maxFileSize?: number;
4
- }
5
- export declare const ValidatorMaxfileSize: PluginFn<ValidatorMaxfileSizeOptions>;
6
- export {};
@@ -1,6 +0,0 @@
1
- import type { PluginFn } from "../types.js";
2
- interface ValidatorMaxFilesOptions {
3
- maxFiles?: number;
4
- }
5
- export declare const ValidatorMaxFiles: PluginFn<ValidatorMaxFilesOptions>;
6
- export {};
@@ -1,12 +0,0 @@
1
- export const ValidatorMaxFiles = ({ files }, options) => {
2
- return {
3
- id: "validator-max-files",
4
- hooks: {
5
- validate: async (file) => {
6
- if (options.maxFiles && options.maxFiles !== Infinity && files.length < options.maxFiles || options.maxFiles && options.maxFiles === Infinity)
7
- return file;
8
- throw { message: `Maximum number of files (${options.maxFiles}) exceeded` };
9
- }
10
- }
11
- };
12
- };