ngx-kit 6.0.2 → 6.2.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.
Files changed (51) hide show
  1. package/README.md +3 -3
  2. package/angle-selector/README.md +34 -34
  3. package/box-shadow/README.md +33 -33
  4. package/color-picker/README.md +49 -49
  5. package/data-table/README.md +113 -113
  6. package/date-picker/README.md +69 -69
  7. package/dialog/README.md +92 -92
  8. package/drawer-menu/README.md +77 -77
  9. package/dropzone/README.md +422 -60
  10. package/fesm2022/ngx-kit-angle-selector.mjs +2 -2
  11. package/fesm2022/ngx-kit-angle-selector.mjs.map +1 -1
  12. package/fesm2022/ngx-kit-box-shadow.mjs +2 -2
  13. package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
  14. package/fesm2022/ngx-kit-color-picker.mjs +4 -4
  15. package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
  16. package/fesm2022/ngx-kit-data-table.mjs +2 -2
  17. package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
  18. package/fesm2022/ngx-kit-date-picker.mjs +1322 -200
  19. package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
  20. package/fesm2022/ngx-kit-dialog.mjs +2 -2
  21. package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
  22. package/fesm2022/ngx-kit-drawer-menu.mjs +2 -2
  23. package/fesm2022/ngx-kit-drawer-menu.mjs.map +1 -1
  24. package/fesm2022/ngx-kit-dropzone.mjs +273 -28
  25. package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
  26. package/fesm2022/ngx-kit-form-field.mjs.map +1 -1
  27. package/fesm2022/ngx-kit-gallery.mjs +2 -2
  28. package/fesm2022/ngx-kit-gallery.mjs.map +1 -1
  29. package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
  30. package/fesm2022/ngx-kit-image-editor.mjs +101 -14
  31. package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
  32. package/fesm2022/ngx-kit-menu.mjs +8 -8
  33. package/fesm2022/ngx-kit-menu.mjs.map +1 -1
  34. package/fesm2022/ngx-kit-message.mjs +5 -0
  35. package/fesm2022/ngx-kit-message.mjs.map +1 -1
  36. package/fesm2022/ngx-kit-notify.mjs.map +1 -1
  37. package/fesm2022/ngx-kit-shared.mjs +2 -2
  38. package/fesm2022/ngx-kit-shared.mjs.map +1 -1
  39. package/fesm2022/ngx-kit-time-picker.mjs +2 -2
  40. package/fesm2022/ngx-kit-time-picker.mjs.map +1 -1
  41. package/gallery/README.md +71 -71
  42. package/gradient-picker/README.md +41 -41
  43. package/menu/README.md +85 -85
  44. package/message/README.md +70 -70
  45. package/notify/README.md +58 -58
  46. package/package.json +1 -1
  47. package/shared/README.md +117 -117
  48. package/time-picker/README.md +95 -95
  49. package/types/ngx-kit-date-picker.d.ts +302 -36
  50. package/types/ngx-kit-dropzone.d.ts +129 -18
  51. package/types/ngx-kit-image-editor.d.ts +50 -5
@@ -1,5 +1,61 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, signal, viewChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { inject, Injectable, InjectionToken, input, output, signal, viewChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { filter, map, finalize } from 'rxjs';
4
+ import { HttpClient, HttpHeaders, HttpEventType } from '@angular/common/http';
5
+
6
+ class NgxDropzoneHttpUploadService {
7
+ http = inject(HttpClient);
8
+ upload(request) {
9
+ const body = new FormData();
10
+ body.append(request.alias, request.file, request.file.name);
11
+ for (const [key, value] of Object.entries(request.data ?? {})) {
12
+ body.append(key, value instanceof Blob ? value : String(value));
13
+ }
14
+ const headers = request.headers instanceof HttpHeaders ? request.headers : new HttpHeaders(request.headers);
15
+ return this.http
16
+ .post(request.url, body, {
17
+ headers,
18
+ withCredentials: request.withCredentials ?? false,
19
+ observe: 'events',
20
+ reportProgress: true,
21
+ })
22
+ .pipe(filter((event) => event.type === HttpEventType.UploadProgress || event.type === HttpEventType.Response), map((event) => {
23
+ if (event.type === HttpEventType.UploadProgress) {
24
+ const total = event.total;
25
+ return {
26
+ type: 'progress',
27
+ loaded: event.loaded,
28
+ total,
29
+ percent: total ? Math.min(100, Math.round((event.loaded / total) * 100)) : 0,
30
+ };
31
+ }
32
+ return {
33
+ type: 'response',
34
+ response: event.body,
35
+ status: event.status,
36
+ headers: event.headers,
37
+ };
38
+ }));
39
+ }
40
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneHttpUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
41
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneHttpUploadService, providedIn: 'root' });
42
+ }
43
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneHttpUploadService, decorators: [{
44
+ type: Injectable,
45
+ args: [{ providedIn: 'root' }]
46
+ }] });
47
+
48
+ /**
49
+ * Upload adapter used by ngx-dropzone.
50
+ *
51
+ * Default: Angular HttpClient.
52
+ * Override this token at application/component level to use Axios or any
53
+ * other upload implementation.
54
+ */
55
+ const NGX_DROPZONE_UPLOAD_SERVICE = new InjectionToken('NGX_DROPZONE_UPLOAD_SERVICE', {
56
+ providedIn: 'root',
57
+ factory: () => inject(NgxDropzoneHttpUploadService),
58
+ });
3
59
 
4
60
  /**
5
61
  * چک می‌کنه یه فایل با pattern رشته‌ای accept (مثل input[type=file])
@@ -91,19 +147,6 @@ let uidCounter = 0;
91
147
  function nextId() {
92
148
  return `ngx-dz-${Date.now().toString(36)}-${uidCounter++}`;
93
149
  }
94
- /**
95
- * ناحیه‌ی درگ‌اند‌دراپِ فایل — کاملاً مستقل (بدون وابستگی به هیچ lib
96
- * دیگه‌ای، حتی shared)، signal-based و OnPush.
97
- *
98
- * ویژگی‌های مهمی که معمولاً پیاده‌سازی‌های ساده‌ی dropzone ندارن:
99
- * - شمارنده‌ی dragenter/dragleave: چون dragleave برای فرزندهای داخل
100
- * dropzone هم شلیک می‌شه، پیاده‌سازی‌های ساده موقع درگ‌کردن روی متن/آیکون
101
- * داخل ناحیه، حالت "درگ" رو اشتباهی خاموش می‌کنن (چشمک‌زدن). این‌جا با
102
- * شمارنده حل شده.
103
- * - پیمایش بازگشتیِ پوشه‌ها (وقتی کاربر یه پوشه رو درگ کنه، نه فقط فایل).
104
- * - قابل‌دسترسی با کیبورد (focusable + Enter/Space).
105
- * - پاک‌سازی object URL پیش‌نمایش‌ها روی حذف/destroy (وگرنه نشتی حافظه‌ست).
106
- */
107
150
  class NgxDropzoneComponent {
108
151
  /** الگوی accept مثل input[type=file]: "image/*", ".pdf,.docx" و ... */
109
152
  accept = input('', /* @ts-ignore */
@@ -121,17 +164,61 @@ class NgxDropzoneComponent {
121
164
  /** نمایش لیست/پیش‌نمایش فایل‌های انتخاب‌شده داخل خودِ کامپوننت */
122
165
  showFileList = input(true, /* @ts-ignore */
123
166
  ...(ngDevMode ? [{ debugName: "showFileList" }] : /* istanbul ignore next */ []));
167
+ /** Upload endpoint. */
168
+ uploadUrl = input('', /* @ts-ignore */
169
+ ...(ngDevMode ? [{ debugName: "uploadUrl" }] : /* istanbul ignore next */ []));
170
+ /** Multipart form-data field name. */
171
+ uploadAlias = input('file', /* @ts-ignore */
172
+ ...(ngDevMode ? [{ debugName: "uploadAlias" }] : /* istanbul ignore next */ []));
173
+ /** Automatically enqueue newly accepted files for upload. */
174
+ autoUpload = input(false, /* @ts-ignore */
175
+ ...(ngDevMode ? [{ debugName: "autoUpload" }] : /* istanbul ignore next */ []));
176
+ /** Maximum number of simultaneous uploads. */
177
+ maxParallel = input(3, /* @ts-ignore */
178
+ ...(ngDevMode ? [{ debugName: "maxParallel" }] : /* istanbul ignore next */ []));
179
+ /**
180
+ * Manual upload behavior:
181
+ * - all: one "Upload all" action uploads all pending files.
182
+ * - single: every pending row gets its own Upload button.
183
+ */
184
+ uploadMode = input('all', /* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "uploadMode" }] : /* istanbul ignore next */ []));
186
+ /** Extra multipart form fields sent with every file. */
187
+ uploadData = input({}, /* @ts-ignore */
188
+ ...(ngDevMode ? [{ debugName: "uploadData" }] : /* istanbul ignore next */ []));
189
+ /** Optional request headers. */
190
+ uploadHeaders = input({}, /* @ts-ignore */
191
+ ...(ngDevMode ? [{ debugName: "uploadHeaders" }] : /* istanbul ignore next */ []));
192
+ /** Send credentials/cookies with the upload request. */
193
+ uploadWithCredentials = input(false, /* @ts-ignore */
194
+ ...(ngDevMode ? [{ debugName: "uploadWithCredentials" }] : /* istanbul ignore next */ []));
124
195
  filesAdded = output();
125
196
  filesRejected = output();
126
- /** کل لیست فعلی (بعد از هر تغییر) — برای binding راحت‌تر */
127
197
  filesChange = output();
198
+ /** Emitted when an individual upload starts. */
199
+ uploadStarted = output();
200
+ /** Emitted for every upload progress update. */
201
+ uploadProgress = output();
202
+ /** Emitted after a file is successfully uploaded. */
203
+ uploadSuccess = output();
204
+ /** Emitted when an upload fails. */
205
+ uploadError = output();
128
206
  files = signal([], /* @ts-ignore */
129
207
  ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
130
208
  isDragging = signal(false, /* @ts-ignore */
131
209
  ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
132
210
  dragCounter = 0;
211
+ uploadService = inject(NGX_DROPZONE_UPLOAD_SERVICE);
133
212
  fileInput = viewChild('fileInput', /* @ts-ignore */
134
213
  ...(ngDevMode ? [{ debugName: "fileInput" }] : /* istanbul ignore next */ []));
214
+ uploadSubscriptions = new Map();
215
+ uploadQueue = [];
216
+ activeUploads = 0;
217
+ activeUploadIds = new Set();
218
+ pendingCount = computed(() => this.files().filter((item) => this.isPending(item)).length, /* @ts-ignore */
219
+ ...(ngDevMode ? [{ debugName: "pendingCount" }] : /* istanbul ignore next */ []));
220
+ hasUploadConfiguration = computed(() => this.uploadUrl().trim().length > 0, /* @ts-ignore */
221
+ ...(ngDevMode ? [{ debugName: "hasUploadConfiguration" }] : /* istanbul ignore next */ []));
135
222
  hint = computed(() => {
136
223
  const parts = [];
137
224
  const accept = this.accept();
@@ -146,12 +233,20 @@ class NgxDropzoneComponent {
146
233
  formatSize(bytes) {
147
234
  return formatBytes(bytes);
148
235
  }
236
+ isPending(item) {
237
+ return item.status === 'pending' || item.status === 'accepted';
238
+ }
239
+ trackById(_index, item) {
240
+ return item.id;
241
+ }
149
242
  ngOnDestroy() {
150
- // همه‌ی object URLهای پیش‌نمایش باید revoke بشن، وگرنه تا وقتی تب باز
151
- // باشه، بلاب‌های تصویر توی حافظه‌ی مرورگر می‌مونن.
152
- for (const f of this.files()) {
153
- if (f.previewUrl)
154
- URL.revokeObjectURL(f.previewUrl);
243
+ for (const subscription of this.uploadSubscriptions.values()) {
244
+ subscription.unsubscribe();
245
+ }
246
+ this.uploadSubscriptions.clear();
247
+ for (const item of this.files()) {
248
+ if (item.previewUrl)
249
+ URL.revokeObjectURL(item.previewUrl);
155
250
  }
156
251
  }
157
252
  onDragEnter(ev) {
@@ -162,10 +257,10 @@ class NgxDropzoneComponent {
162
257
  this.isDragging.set(true);
163
258
  }
164
259
  onDragOver(ev) {
165
- // بدون preventDefault روی dragover، رویداد drop اصلاً شلیک نمی‌شه.
166
260
  ev.preventDefault();
167
- if (ev.dataTransfer)
261
+ if (ev.dataTransfer) {
168
262
  ev.dataTransfer.dropEffect = this.disabled() ? 'none' : 'copy';
263
+ }
169
264
  }
170
265
  onDragLeave(ev) {
171
266
  ev.preventDefault();
@@ -199,22 +294,46 @@ class NgxDropzoneComponent {
199
294
  const input = ev.target;
200
295
  const files = Array.from(input.files ?? []);
201
296
  this.handleIncomingFiles(files);
202
- // اجازه بده همون فایل دوباره انتخاب بشه (input تغییر نمی‌کنه وگرنه)
203
297
  input.value = '';
204
298
  }
205
299
  removeFile(id) {
206
300
  const target = this.files().find((f) => f.id === id);
301
+ this.uploadSubscriptions.get(id)?.unsubscribe();
302
+ this.uploadSubscriptions.delete(id);
303
+ // Unsubscribing an HttpClient upload cancels the request. Release its
304
+ // concurrency slot immediately; finalize() will see that the id is no
305
+ // longer active and won't decrement twice.
306
+ if (this.activeUploadIds.delete(id)) {
307
+ this.activeUploads = Math.max(0, this.activeUploads - 1);
308
+ }
207
309
  if (target?.previewUrl)
208
310
  URL.revokeObjectURL(target.previewUrl);
209
311
  this.files.update((list) => list.filter((f) => f.id !== id));
312
+ // Remove stale queue entries. The queue is intentionally lazy, so an
313
+ // entry can safely remain until pumpQueue() sees that it no longer exists.
210
314
  this.emitCurrentFiles();
315
+ this.pumpQueue();
316
+ }
317
+ uploadAll() {
318
+ this.enqueueUploads(this.files()
319
+ .filter((item) => this.isPending(item))
320
+ .map((item) => item.id));
321
+ }
322
+ uploadSingle(id) {
323
+ this.enqueueUploads([id]);
324
+ }
325
+ retryUpload(id) {
326
+ this.files.update((list) => list.map((item) => item.id === id ? { ...item, status: 'pending', progress: 0, error: undefined } : item));
327
+ this.enqueueUploads([id]);
211
328
  }
212
329
  handleIncomingFiles(incoming) {
213
330
  if (!incoming.length)
214
331
  return;
215
332
  const accepted = [];
216
333
  const rejected = [];
217
- const existingCount = this.files().filter((f) => f.status === 'accepted').length;
334
+ // A single-file dropzone behaves like a native single-select input:
335
+ // selecting another file replaces the previous one.
336
+ const existingCount = this.multiple() ? this.files().length : 0;
218
337
  const maxFiles = this.maxFiles();
219
338
  const maxSize = this.maxFileSize();
220
339
  const isMultiple = this.multiple();
@@ -240,33 +359,159 @@ class NgxDropzoneComponent {
240
359
  file,
241
360
  previewUrl: isImage ? URL.createObjectURL(file) : undefined,
242
361
  status: 'accepted',
362
+ progress: 0,
243
363
  });
244
364
  }
245
365
  if (accepted.length) {
366
+ if (!isMultiple) {
367
+ for (const previous of this.files()) {
368
+ this.uploadSubscriptions.get(previous.id)?.unsubscribe();
369
+ this.uploadSubscriptions.delete(previous.id);
370
+ this.activeUploadIds.delete(previous.id);
371
+ if (previous.previewUrl)
372
+ URL.revokeObjectURL(previous.previewUrl);
373
+ }
374
+ this.activeUploads = this.activeUploadIds.size;
375
+ }
246
376
  this.files.update((list) => (isMultiple ? [...list, ...accepted] : accepted));
247
377
  this.filesAdded.emit(accepted.map((a) => a.file));
248
378
  this.emitCurrentFiles();
379
+ if (this.autoUpload()) {
380
+ this.enqueueUploads(accepted.map((item) => item.id));
381
+ }
249
382
  }
250
383
  if (rejected.length) {
251
384
  this.filesRejected.emit(rejected);
252
385
  }
253
386
  }
387
+ enqueueUploads(ids) {
388
+ if (!this.uploadUrl().trim()) {
389
+ const error = new Error('ngx-dropzone: uploadUrl is required for uploading files.');
390
+ for (const id of ids)
391
+ this.markUploadError(id, error, false);
392
+ return;
393
+ }
394
+ const queued = new Set(this.uploadQueue);
395
+ for (const id of ids) {
396
+ const item = this.files().find((file) => file.id === id);
397
+ if (!item || !this.isPending(item) || queued.has(id))
398
+ continue;
399
+ this.uploadQueue.push(id);
400
+ queued.add(id);
401
+ }
402
+ this.pumpQueue();
403
+ }
404
+ pumpQueue() {
405
+ const limit = Math.max(1, Math.floor(this.maxParallel() || 1));
406
+ while (this.activeUploads < limit && this.uploadQueue.length) {
407
+ const id = this.uploadQueue.shift();
408
+ const item = this.files().find((file) => file.id === id);
409
+ if (!item || !this.isPending(item))
410
+ continue;
411
+ this.startUpload(item);
412
+ }
413
+ }
414
+ startUpload(item) {
415
+ this.activeUploads++;
416
+ this.activeUploadIds.add(item.id);
417
+ this.updateFile(item.id, {
418
+ status: 'uploading',
419
+ progress: 0,
420
+ error: undefined,
421
+ });
422
+ const current = this.files().find((file) => file.id === item.id);
423
+ if (current)
424
+ this.uploadStarted.emit(current);
425
+ let subscription;
426
+ try {
427
+ subscription = this.uploadService
428
+ .upload({
429
+ url: this.uploadUrl(),
430
+ alias: this.uploadAlias() || 'file',
431
+ file: item.file,
432
+ data: this.uploadData(),
433
+ headers: this.uploadHeaders(),
434
+ withCredentials: this.uploadWithCredentials(),
435
+ })
436
+ .pipe(finalize(() => this.finishUpload(item.id)))
437
+ .subscribe({
438
+ next: (event) => {
439
+ if (event.type === 'progress') {
440
+ this.updateFile(item.id, {
441
+ progress: event.percent,
442
+ });
443
+ const updated = this.files().find((file) => file.id === item.id);
444
+ if (updated) {
445
+ this.uploadProgress.emit({
446
+ item: updated,
447
+ loaded: event.loaded,
448
+ total: event.total,
449
+ percent: event.percent,
450
+ });
451
+ }
452
+ return;
453
+ }
454
+ this.updateFile(item.id, {
455
+ status: 'uploaded',
456
+ progress: 100,
457
+ response: event.response,
458
+ error: undefined,
459
+ });
460
+ const updated = this.files().find((file) => file.id === item.id);
461
+ if (updated)
462
+ this.uploadSuccess.emit(updated);
463
+ },
464
+ error: (error) => {
465
+ this.markUploadError(item.id, error, true);
466
+ },
467
+ });
468
+ }
469
+ catch (error) {
470
+ this.markUploadError(item.id, error, true);
471
+ this.finishUpload(item.id);
472
+ return;
473
+ }
474
+ if (!subscription.closed)
475
+ this.uploadSubscriptions.set(item.id, subscription);
476
+ }
477
+ markUploadError(id, error, releaseActiveSlot) {
478
+ this.updateFile(id, {
479
+ status: 'error',
480
+ error,
481
+ });
482
+ const item = this.files().find((file) => file.id === id);
483
+ if (item)
484
+ this.uploadError.emit({ item, error });
485
+ if (!releaseActiveSlot) {
486
+ this.pumpQueue();
487
+ }
488
+ }
489
+ finishUpload(id) {
490
+ this.uploadSubscriptions.delete(id);
491
+ if (this.activeUploadIds.delete(id)) {
492
+ this.activeUploads = Math.max(0, this.activeUploads - 1);
493
+ }
494
+ this.pumpQueue();
495
+ }
496
+ updateFile(id, patch) {
497
+ this.files.update((list) => list.map((item) => (item.id === id ? { ...item, ...patch } : item)));
498
+ }
254
499
  emitCurrentFiles() {
255
500
  this.filesChange.emit(this.files().map((f) => f.file));
256
501
  }
257
502
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
258
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxDropzoneComponent, isStandalone: true, selector: "ngx-dropzone", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesAdded: "filesAdded", filesRejected: "filesRejected", filesChange: "filesChange" }, host: { classAttribute: "ngx-dropzone-host" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"ngx-dropzone\"\n [class.dragging]=\"isDragging()\"\n [class.disabled]=\"disabled()\"\n [class.has-files]=\"files().length > 0\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"onBrowseClick()\"\n (keydown)=\"onKeydown($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n #fileInput\n type=\"file\"\n class=\"ngx-dropzone-input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onFileInputChange($event)\" />\n\n <div class=\"ngx-dropzone-icon-wrap\">\n <ng-content select=\"[dropzone-icon]\">\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </ng-content>\n </div>\n\n <div class=\"ngx-dropzone-text\">\n <ng-content>\n <strong>Drop files here</strong>\n <span>or click to browse</span>\n </ng-content>\n </div>\n\n @if (hint()) {\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\n }\n</div>\n\n@if (showFileList() && files().length) {\n <ul class=\"ngx-dropzone-list\">\n @for (item of files(); track item.id) {\n <li class=\"ngx-dropzone-item\">\n <div class=\"ngx-dropzone-thumb-wrap\">\n @if (item.previewUrl) {\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\n } @else {\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\n }\n </div>\n <div class=\"ngx-dropzone-meta\">\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n <span class=\"ngx-dropzone-status\" aria-hidden=\"true\">\u2713</span>\n <button\n type=\"button\"\n class=\"ngx-dropzone-remove\"\n [attr.aria-label]=\"'Remove ' + item.file.name\"\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
503
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxDropzoneComponent, isStandalone: true, selector: "ngx-dropzone", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null }, uploadUrl: { classPropertyName: "uploadUrl", publicName: "uploadUrl", isSignal: true, isRequired: false, transformFunction: null }, uploadAlias: { classPropertyName: "uploadAlias", publicName: "uploadAlias", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, maxParallel: { classPropertyName: "maxParallel", publicName: "maxParallel", isSignal: true, isRequired: false, transformFunction: null }, uploadMode: { classPropertyName: "uploadMode", publicName: "uploadMode", isSignal: true, isRequired: false, transformFunction: null }, uploadData: { classPropertyName: "uploadData", publicName: "uploadData", isSignal: true, isRequired: false, transformFunction: null }, uploadHeaders: { classPropertyName: "uploadHeaders", publicName: "uploadHeaders", isSignal: true, isRequired: false, transformFunction: null }, uploadWithCredentials: { classPropertyName: "uploadWithCredentials", publicName: "uploadWithCredentials", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesAdded: "filesAdded", filesRejected: "filesRejected", filesChange: "filesChange", uploadStarted: "uploadStarted", uploadProgress: "uploadProgress", uploadSuccess: "uploadSuccess", uploadError: "uploadError" }, host: { classAttribute: "ngx-dropzone-host" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"ngx-dropzone\"\r\n [class.dragging]=\"isDragging()\"\r\n [class.disabled]=\"disabled()\"\r\n [class.has-files]=\"files().length > 0\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n (click)=\"onBrowseClick()\"\r\n (keydown)=\"onKeydown($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n class=\"ngx-dropzone-input\"\r\n [accept]=\"accept()\"\r\n [multiple]=\"multiple()\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"onFileInputChange($event)\" />\r\n\r\n <div class=\"ngx-dropzone-icon-wrap\">\r\n <ng-content select=\"[dropzone-icon]\">\r\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\r\n <path\r\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" />\r\n </svg>\r\n </ng-content>\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-text\">\r\n <ng-content>\r\n <strong>Drop files here</strong>\r\n <span>or click to browse</span>\r\n </ng-content>\r\n </div>\r\n\r\n @if (hint()) {\r\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n\r\n@if (!autoUpload() && uploadMode() === 'all' && pendingCount() > 0) {\r\n <div class=\"ngx-dropzone-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-upload-all\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n (click)=\"uploadAll(); $event.stopPropagation()\">\r\n Upload all\r\n <span>{{ pendingCount() }}</span>\r\n </button>\r\n </div>\r\n}\r\n\r\n@if (showFileList() && files().length) {\r\n <ul class=\"ngx-dropzone-list\">\r\n @for (item of files(); track item.id) {\r\n <li class=\"ngx-dropzone-item\" [class.is-uploading]=\"item.status === 'uploading'\">\r\n <div class=\"ngx-dropzone-thumb-wrap\">\r\n @if (item.previewUrl) {\r\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\r\n } @else {\r\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-meta\">\r\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\r\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\r\n\r\n @if (item.status === 'uploading' || item.status === 'uploaded') {\r\n <div\r\n class=\"ngx-dropzone-progress\"\r\n role=\"progressbar\"\r\n [attr.aria-valuenow]=\"item.progress\"\r\n aria-valuemin=\"0\"\r\n aria-valuemax=\"100\"\r\n [attr.aria-label]=\"'Upload progress for ' + item.file.name\">\r\n <span class=\"ngx-dropzone-progress-bar\" [style.width.%]=\"item.progress\"></span>\r\n </div>\r\n <span class=\"ngx-dropzone-progress-text\">{{ item.progress }}%</span>\r\n }\r\n\r\n @if (item.status === 'error') {\r\n <span class=\"ngx-dropzone-error\">Upload failed</span>\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-row-actions\">\r\n @if (!autoUpload() && uploadMode() === 'single' && isPending(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-upload\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n [attr.aria-label]=\"'Upload ' + item.file.name\"\r\n (click)=\"uploadSingle(item.id); $event.stopPropagation()\">\r\n Upload\r\n </button>\r\n }\r\n\r\n @if (item.status === 'error') {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-retry\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n [attr.aria-label]=\"'Retry upload for ' + item.file.name\"\r\n (click)=\"retryUpload(item.id); $event.stopPropagation()\">\r\n Retry\r\n </button>\r\n }\r\n\r\n @if (item.status === 'uploaded') {\r\n <span class=\"ngx-dropzone-status success\" aria-label=\"Uploaded\">\u2713</span>\r\n } @else if (item.status === 'uploading') {\r\n <span class=\"ngx-dropzone-status uploading\" aria-label=\"Uploading\"></span>\r\n } @else if (item.status === 'error') {\r\n <span class=\"ngx-dropzone-status error\" aria-label=\"Upload failed\">!</span>\r\n } @else {\r\n <span class=\"ngx-dropzone-status pending\" aria-label=\"Pending\">\u2022</span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-remove\"\r\n [attr.aria-label]=\"'Remove ' + item.file.name\"\r\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\r\n \u2715\r\n </button>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}.ngx-dropzone-actions{display:flex;justify-content:flex-end;margin-top:.7rem}.ngx-dropzone-upload-all,.ngx-dropzone-upload,.ngx-dropzone-retry{appearance:none;border:1px solid light-dark(#dbe3f0,#3b3b45);border-radius:7px;background:light-dark(#2563eb,#3b82f6);color:#fff;cursor:pointer;font:inherit;font-size:.75rem;font-weight:600;padding:.35rem .65rem;transition:opacity .14s ease,transform .14s ease}.ngx-dropzone-upload:hover:not(:disabled),.ngx-dropzone-upload-all:hover:not(:disabled),.ngx-dropzone-retry:hover:not(:disabled){transform:translateY(-1px)}.ngx-dropzone-upload-all:disabled,.ngx-dropzone-upload:disabled,.ngx-dropzone-retry:disabled{cursor:not-allowed;opacity:.5}.ngx-dropzone-upload-all{display:inline-flex;align-items:center;gap:.45rem}.ngx-dropzone-upload-all span{min-inline-size:1.15rem;block-size:1.15rem;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:#ffffff2e}.ngx-dropzone-retry{background:transparent;color:light-dark(#dc2626,#f87171)}.ngx-dropzone-row-actions{flex-shrink:0;display:flex;align-items:center;gap:.35rem}.ngx-dropzone-item.is-uploading .ngx-dropzone-status.uploading{animation:ngx-dropzone-upload-pulse 1.1s ease-in-out infinite}.ngx-dropzone-status.pending{background:light-dark(#f4f4f5,#29292f);color:light-dark(#a1a1aa,#71717a)}.ngx-dropzone-status.uploading{background:light-dark(#dbeafe,#172554);border:3px solid light-dark(#93c5fd,#3b82f6)}.ngx-dropzone-status.error{background:light-dark(#fee2e2,#450a0a);color:light-dark(#dc2626,#f87171);font-weight:700}.ngx-dropzone-status.success{background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80)}.ngx-dropzone-progress{position:relative;inline-size:100%;block-size:4px;margin-top:.35rem;overflow:hidden;border-radius:999px;background:light-dark(#e5e7eb,#303038)}.ngx-dropzone-progress-bar{display:block;block-size:100%;border-radius:inherit;background:light-dark(#2563eb,#60a5fa);transition:width .12s linear}.ngx-dropzone-progress-text{font-size:.65rem;color:light-dark(#6b7280,#a1a1aa)}.ngx-dropzone-error{font-size:.65rem;color:light-dark(#dc2626,#f87171)}@keyframes ngx-dropzone-upload-pulse{50%{opacity:.45}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
259
504
  }
260
505
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneComponent, decorators: [{
261
506
  type: Component,
262
507
  args: [{ selector: 'ngx-dropzone', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
263
508
  class: 'ngx-dropzone-host',
264
- }, template: "<div\n class=\"ngx-dropzone\"\n [class.dragging]=\"isDragging()\"\n [class.disabled]=\"disabled()\"\n [class.has-files]=\"files().length > 0\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"onBrowseClick()\"\n (keydown)=\"onKeydown($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n #fileInput\n type=\"file\"\n class=\"ngx-dropzone-input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onFileInputChange($event)\" />\n\n <div class=\"ngx-dropzone-icon-wrap\">\n <ng-content select=\"[dropzone-icon]\">\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </ng-content>\n </div>\n\n <div class=\"ngx-dropzone-text\">\n <ng-content>\n <strong>Drop files here</strong>\n <span>or click to browse</span>\n </ng-content>\n </div>\n\n @if (hint()) {\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\n }\n</div>\n\n@if (showFileList() && files().length) {\n <ul class=\"ngx-dropzone-list\">\n @for (item of files(); track item.id) {\n <li class=\"ngx-dropzone-item\">\n <div class=\"ngx-dropzone-thumb-wrap\">\n @if (item.previewUrl) {\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\n } @else {\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\n }\n </div>\n <div class=\"ngx-dropzone-meta\">\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n <span class=\"ngx-dropzone-status\" aria-hidden=\"true\">\u2713</span>\n <button\n type=\"button\"\n class=\"ngx-dropzone-remove\"\n [attr.aria-label]=\"'Remove ' + item.file.name\"\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}\n"] }]
265
- }], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], filesAdded: [{ type: i0.Output, args: ["filesAdded"] }], filesRejected: [{ type: i0.Output, args: ["filesRejected"] }], filesChange: [{ type: i0.Output, args: ["filesChange"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
509
+ }, template: "<div\r\n class=\"ngx-dropzone\"\r\n [class.dragging]=\"isDragging()\"\r\n [class.disabled]=\"disabled()\"\r\n [class.has-files]=\"files().length > 0\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n (click)=\"onBrowseClick()\"\r\n (keydown)=\"onKeydown($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n class=\"ngx-dropzone-input\"\r\n [accept]=\"accept()\"\r\n [multiple]=\"multiple()\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"onFileInputChange($event)\" />\r\n\r\n <div class=\"ngx-dropzone-icon-wrap\">\r\n <ng-content select=\"[dropzone-icon]\">\r\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\r\n <path\r\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" />\r\n </svg>\r\n </ng-content>\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-text\">\r\n <ng-content>\r\n <strong>Drop files here</strong>\r\n <span>or click to browse</span>\r\n </ng-content>\r\n </div>\r\n\r\n @if (hint()) {\r\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\r\n }\r\n</div>\r\n\r\n@if (!autoUpload() && uploadMode() === 'all' && pendingCount() > 0) {\r\n <div class=\"ngx-dropzone-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-upload-all\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n (click)=\"uploadAll(); $event.stopPropagation()\">\r\n Upload all\r\n <span>{{ pendingCount() }}</span>\r\n </button>\r\n </div>\r\n}\r\n\r\n@if (showFileList() && files().length) {\r\n <ul class=\"ngx-dropzone-list\">\r\n @for (item of files(); track item.id) {\r\n <li class=\"ngx-dropzone-item\" [class.is-uploading]=\"item.status === 'uploading'\">\r\n <div class=\"ngx-dropzone-thumb-wrap\">\r\n @if (item.previewUrl) {\r\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\r\n } @else {\r\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-meta\">\r\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\r\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\r\n\r\n @if (item.status === 'uploading' || item.status === 'uploaded') {\r\n <div\r\n class=\"ngx-dropzone-progress\"\r\n role=\"progressbar\"\r\n [attr.aria-valuenow]=\"item.progress\"\r\n aria-valuemin=\"0\"\r\n aria-valuemax=\"100\"\r\n [attr.aria-label]=\"'Upload progress for ' + item.file.name\">\r\n <span class=\"ngx-dropzone-progress-bar\" [style.width.%]=\"item.progress\"></span>\r\n </div>\r\n <span class=\"ngx-dropzone-progress-text\">{{ item.progress }}%</span>\r\n }\r\n\r\n @if (item.status === 'error') {\r\n <span class=\"ngx-dropzone-error\">Upload failed</span>\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-dropzone-row-actions\">\r\n @if (!autoUpload() && uploadMode() === 'single' && isPending(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-upload\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n [attr.aria-label]=\"'Upload ' + item.file.name\"\r\n (click)=\"uploadSingle(item.id); $event.stopPropagation()\">\r\n Upload\r\n </button>\r\n }\r\n\r\n @if (item.status === 'error') {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-retry\"\r\n [disabled]=\"disabled() || !hasUploadConfiguration()\"\r\n [attr.aria-label]=\"'Retry upload for ' + item.file.name\"\r\n (click)=\"retryUpload(item.id); $event.stopPropagation()\">\r\n Retry\r\n </button>\r\n }\r\n\r\n @if (item.status === 'uploaded') {\r\n <span class=\"ngx-dropzone-status success\" aria-label=\"Uploaded\">\u2713</span>\r\n } @else if (item.status === 'uploading') {\r\n <span class=\"ngx-dropzone-status uploading\" aria-label=\"Uploading\"></span>\r\n } @else if (item.status === 'error') {\r\n <span class=\"ngx-dropzone-status error\" aria-label=\"Upload failed\">!</span>\r\n } @else {\r\n <span class=\"ngx-dropzone-status pending\" aria-label=\"Pending\">\u2022</span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-dropzone-remove\"\r\n [attr.aria-label]=\"'Remove ' + item.file.name\"\r\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\r\n \u2715\r\n </button>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}.ngx-dropzone-actions{display:flex;justify-content:flex-end;margin-top:.7rem}.ngx-dropzone-upload-all,.ngx-dropzone-upload,.ngx-dropzone-retry{appearance:none;border:1px solid light-dark(#dbe3f0,#3b3b45);border-radius:7px;background:light-dark(#2563eb,#3b82f6);color:#fff;cursor:pointer;font:inherit;font-size:.75rem;font-weight:600;padding:.35rem .65rem;transition:opacity .14s ease,transform .14s ease}.ngx-dropzone-upload:hover:not(:disabled),.ngx-dropzone-upload-all:hover:not(:disabled),.ngx-dropzone-retry:hover:not(:disabled){transform:translateY(-1px)}.ngx-dropzone-upload-all:disabled,.ngx-dropzone-upload:disabled,.ngx-dropzone-retry:disabled{cursor:not-allowed;opacity:.5}.ngx-dropzone-upload-all{display:inline-flex;align-items:center;gap:.45rem}.ngx-dropzone-upload-all span{min-inline-size:1.15rem;block-size:1.15rem;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:#ffffff2e}.ngx-dropzone-retry{background:transparent;color:light-dark(#dc2626,#f87171)}.ngx-dropzone-row-actions{flex-shrink:0;display:flex;align-items:center;gap:.35rem}.ngx-dropzone-item.is-uploading .ngx-dropzone-status.uploading{animation:ngx-dropzone-upload-pulse 1.1s ease-in-out infinite}.ngx-dropzone-status.pending{background:light-dark(#f4f4f5,#29292f);color:light-dark(#a1a1aa,#71717a)}.ngx-dropzone-status.uploading{background:light-dark(#dbeafe,#172554);border:3px solid light-dark(#93c5fd,#3b82f6)}.ngx-dropzone-status.error{background:light-dark(#fee2e2,#450a0a);color:light-dark(#dc2626,#f87171);font-weight:700}.ngx-dropzone-status.success{background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80)}.ngx-dropzone-progress{position:relative;inline-size:100%;block-size:4px;margin-top:.35rem;overflow:hidden;border-radius:999px;background:light-dark(#e5e7eb,#303038)}.ngx-dropzone-progress-bar{display:block;block-size:100%;border-radius:inherit;background:light-dark(#2563eb,#60a5fa);transition:width .12s linear}.ngx-dropzone-progress-text{font-size:.65rem;color:light-dark(#6b7280,#a1a1aa)}.ngx-dropzone-error{font-size:.65rem;color:light-dark(#dc2626,#f87171)}@keyframes ngx-dropzone-upload-pulse{50%{opacity:.45}}\n"] }]
510
+ }], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], uploadUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadUrl", required: false }] }], uploadAlias: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadAlias", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], maxParallel: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxParallel", required: false }] }], uploadMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadMode", required: false }] }], uploadData: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadData", required: false }] }], uploadHeaders: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadHeaders", required: false }] }], uploadWithCredentials: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadWithCredentials", required: false }] }], filesAdded: [{ type: i0.Output, args: ["filesAdded"] }], filesRejected: [{ type: i0.Output, args: ["filesRejected"] }], filesChange: [{ type: i0.Output, args: ["filesChange"] }], uploadStarted: [{ type: i0.Output, args: ["uploadStarted"] }], uploadProgress: [{ type: i0.Output, args: ["uploadProgress"] }], uploadSuccess: [{ type: i0.Output, args: ["uploadSuccess"] }], uploadError: [{ type: i0.Output, args: ["uploadError"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
266
511
 
267
512
  /**
268
513
  * Generated bundle index. Do not edit.
269
514
  */
270
515
 
271
- export { NgxDropzoneComponent, extractFilesFromDataTransfer, formatBytes, matchesAccept };
516
+ export { NGX_DROPZONE_UPLOAD_SERVICE, NgxDropzoneComponent, NgxDropzoneHttpUploadService, extractFilesFromDataTransfer, formatBytes, matchesAccept };
272
517
  //# sourceMappingURL=ngx-kit-dropzone.mjs.map