nuxt-ui-elements 0.1.25 → 0.1.27

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 +61 -65
  4. package/dist/runtime/composables/{useUploader → useUploadManager}/index.js +124 -29
  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.27",
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,152 +1,148 @@
1
- import type { PluginFn, UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn } from "./types.js";
2
- export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions) => {
3
- files: Readonly<import("vue").Ref<readonly {
4
- readonly id: string;
5
- readonly name: string;
6
- readonly size: number;
7
- readonly mimeType: string;
8
- readonly data: {
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
+ files: import("vue").Ref<{
4
+ id: string;
5
+ name: string;
6
+ size: number;
7
+ mimeType: string;
8
+ data: {
9
9
  readonly lastModified: number;
10
10
  readonly name: string;
11
11
  readonly webkitRelativePath: string;
12
12
  readonly size: number;
13
13
  readonly type: string;
14
- readonly arrayBuffer: {
14
+ arrayBuffer: {
15
15
  (): Promise<ArrayBuffer>;
16
16
  (): Promise<ArrayBuffer>;
17
17
  };
18
- readonly bytes: {
18
+ bytes: {
19
19
  (): Promise<Uint8Array<ArrayBuffer>>;
20
20
  (): Promise<Uint8Array<ArrayBuffer>>;
21
21
  };
22
- readonly slice: {
22
+ slice: {
23
23
  (start?: number, end?: number, contentType?: string): Blob;
24
24
  (start?: number, end?: number, contentType?: string): Blob;
25
25
  };
26
- readonly stream: {
26
+ stream: {
27
27
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
28
28
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
29
29
  };
30
- readonly text: {
30
+ text: {
31
31
  (): Promise<string>;
32
32
  (): Promise<string>;
33
33
  };
34
34
  } | {
35
35
  readonly size: number;
36
36
  readonly type: string;
37
- readonly arrayBuffer: {
37
+ arrayBuffer: {
38
38
  (): Promise<ArrayBuffer>;
39
39
  (): Promise<ArrayBuffer>;
40
40
  };
41
- readonly bytes: {
41
+ bytes: {
42
42
  (): Promise<Uint8Array<ArrayBuffer>>;
43
43
  (): Promise<Uint8Array<ArrayBuffer>>;
44
44
  };
45
- readonly slice: {
45
+ slice: {
46
46
  (start?: number, end?: number, contentType?: string): Blob;
47
47
  (start?: number, end?: number, contentType?: string): Blob;
48
48
  };
49
- readonly stream: {
49
+ stream: {
50
50
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
51
51
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
52
52
  };
53
- readonly text: {
53
+ text: {
54
54
  (): Promise<string>;
55
55
  (): Promise<string>;
56
56
  };
57
57
  };
58
- readonly status: import("./types.js").FileStatus;
59
- readonly preview?: string | undefined;
60
- readonly progress: {
61
- readonly percentage: number;
58
+ status: import("./types.js").FileStatus;
59
+ preview?: string | undefined;
60
+ progress: {
61
+ percentage: number;
62
62
  };
63
- readonly error?: {
64
- readonly message: string;
65
- readonly details?: Readonly<unknown> | undefined;
63
+ error?: {
64
+ message: string;
65
+ details?: unknown;
66
66
  } | undefined;
67
- readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
68
- readonly isRemote?: boolean | undefined;
69
- readonly remoteUrl?: string | undefined;
70
- readonly meta: {
71
- readonly [x: string]: Readonly<unknown>;
72
- };
73
- }[], readonly {
74
- readonly id: string;
75
- readonly name: string;
76
- readonly size: number;
77
- readonly mimeType: string;
78
- readonly data: {
67
+ uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
68
+ isRemote?: boolean | undefined;
69
+ remoteUrl?: string | undefined;
70
+ meta: Record<string, unknown>;
71
+ }[], UploadFile<TUploadResult>[] | {
72
+ id: string;
73
+ name: string;
74
+ size: number;
75
+ mimeType: string;
76
+ data: {
79
77
  readonly lastModified: number;
80
78
  readonly name: string;
81
79
  readonly webkitRelativePath: string;
82
80
  readonly size: number;
83
81
  readonly type: string;
84
- readonly arrayBuffer: {
82
+ arrayBuffer: {
85
83
  (): Promise<ArrayBuffer>;
86
84
  (): Promise<ArrayBuffer>;
87
85
  };
88
- readonly bytes: {
86
+ bytes: {
89
87
  (): Promise<Uint8Array<ArrayBuffer>>;
90
88
  (): Promise<Uint8Array<ArrayBuffer>>;
91
89
  };
92
- readonly slice: {
90
+ slice: {
93
91
  (start?: number, end?: number, contentType?: string): Blob;
94
92
  (start?: number, end?: number, contentType?: string): Blob;
95
93
  };
96
- readonly stream: {
94
+ stream: {
97
95
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
98
96
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
99
97
  };
100
- readonly text: {
98
+ text: {
101
99
  (): Promise<string>;
102
100
  (): Promise<string>;
103
101
  };
104
102
  } | {
105
103
  readonly size: number;
106
104
  readonly type: string;
107
- readonly arrayBuffer: {
105
+ arrayBuffer: {
108
106
  (): Promise<ArrayBuffer>;
109
107
  (): Promise<ArrayBuffer>;
110
108
  };
111
- readonly bytes: {
109
+ bytes: {
112
110
  (): Promise<Uint8Array<ArrayBuffer>>;
113
111
  (): Promise<Uint8Array<ArrayBuffer>>;
114
112
  };
115
- readonly slice: {
113
+ slice: {
116
114
  (start?: number, end?: number, contentType?: string): Blob;
117
115
  (start?: number, end?: number, contentType?: string): Blob;
118
116
  };
119
- readonly stream: {
117
+ stream: {
120
118
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
121
119
  (): ReadableStream<Uint8Array<ArrayBuffer>>;
122
120
  };
123
- readonly text: {
121
+ text: {
124
122
  (): Promise<string>;
125
123
  (): Promise<string>;
126
124
  };
127
125
  };
128
- readonly status: import("./types.js").FileStatus;
129
- readonly preview?: string | undefined;
130
- readonly progress: {
131
- readonly percentage: number;
126
+ status: import("./types.js").FileStatus;
127
+ preview?: string | undefined;
128
+ progress: {
129
+ percentage: number;
132
130
  };
133
- readonly error?: {
134
- readonly message: string;
135
- readonly details?: Readonly<unknown> | undefined;
131
+ error?: {
132
+ message: string;
133
+ details?: unknown;
136
134
  } | undefined;
137
- readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
138
- readonly isRemote?: boolean | undefined;
139
- readonly remoteUrl?: string | undefined;
140
- readonly meta: {
141
- readonly [x: string]: Readonly<unknown>;
142
- };
143
- }[]>>;
135
+ uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
136
+ isRemote?: boolean | undefined;
137
+ remoteUrl?: string | undefined;
138
+ meta: Record<string, unknown>;
139
+ }[]>;
144
140
  totalProgress: import("vue").ComputedRef<number>;
145
141
  addFiles: (newFiles: File[]) => Promise<(UploadFile<any> | null)[]>;
146
142
  addFile: (file: File) => Promise<UploadFile<any> | null>;
147
143
  onGetRemoteFile: (fn: GetRemoteFileFn) => void;
148
144
  onUpload: (fn: UploadFn<TUploadResult>) => void;
149
- removeFile: (fileId: string) => void;
145
+ removeFile: (fileId: string) => Promise<void>;
150
146
  removeFiles: (fileIds: string[]) => {
151
147
  id: string;
152
148
  name: string;
@@ -360,9 +356,9 @@ export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions
360
356
  status: import("vue").Ref<UploadStatus, UploadStatus>;
361
357
  updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
362
358
  initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
363
- addPlugin: <TOptions>(fn: PluginFn<TOptions>, pluginOptions: TOptions) => void;
359
+ addPlugin: (plugin: UploaderPlugin<any, any>) => void;
364
360
  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;
361
+ <K extends keyof UploaderEvents<TUploadResult>>(type: K, handler: (event: UploaderEvents<TUploadResult>[K]) => void): void;
362
+ (type: string, handler: (event: any) => void): void;
367
363
  };
368
364
  };
@@ -1,5 +1,5 @@
1
1
  import mitt from "mitt";
2
- import { computed, readonly, ref } from "vue";
2
+ import { computed, ref } from "vue";
3
3
  import { ValidatorAllowedFileTypes, ValidatorMaxfileSize, ValidatorMaxFiles } from "./validators/index.js";
4
4
  import { PluginThumbnailGenerator, PluginImageCompressor } from "./plugins/index.js";
5
5
  function getExtension(fullFileName) {
@@ -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) {
@@ -249,7 +344,7 @@ export const useUploader = (_options = {}) => {
249
344
  }
250
345
  return {
251
346
  // State
252
- files: readonly(files),
347
+ files,
253
348
  totalProgress,
254
349
  // Core Methods
255
350
  addFiles,
@@ -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 {};