@acorex/cdk 21.0.0-next.9 → 21.0.0-next51

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 (46) hide show
  1. package/accordion/index.d.ts +1 -0
  2. package/drag-drop/index.d.ts +57 -44
  3. package/drawer/index.d.ts +12 -9
  4. package/fesm2022/acorex-cdk-accordion.mjs +52 -67
  5. package/fesm2022/acorex-cdk-accordion.mjs.map +1 -1
  6. package/fesm2022/acorex-cdk-carousel.mjs +13 -10
  7. package/fesm2022/acorex-cdk-carousel.mjs.map +1 -1
  8. package/fesm2022/acorex-cdk-clipboard.mjs +6 -6
  9. package/fesm2022/acorex-cdk-clipboard.mjs.map +1 -1
  10. package/fesm2022/acorex-cdk-common.mjs +104 -104
  11. package/fesm2022/acorex-cdk-common.mjs.map +1 -1
  12. package/fesm2022/acorex-cdk-dom.mjs +3 -3
  13. package/fesm2022/acorex-cdk-dom.mjs.map +1 -1
  14. package/fesm2022/acorex-cdk-drag-drop.mjs +278 -85
  15. package/fesm2022/acorex-cdk-drag-drop.mjs.map +1 -1
  16. package/fesm2022/acorex-cdk-drawer.mjs +44 -33
  17. package/fesm2022/acorex-cdk-drawer.mjs.map +1 -1
  18. package/fesm2022/acorex-cdk-focus-trap.mjs +3 -3
  19. package/fesm2022/acorex-cdk-focus-trap.mjs.map +1 -1
  20. package/fesm2022/acorex-cdk-input-mask.mjs +5 -3
  21. package/fesm2022/acorex-cdk-input-mask.mjs.map +1 -1
  22. package/fesm2022/acorex-cdk-list-navigation.mjs +39 -21
  23. package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
  24. package/fesm2022/acorex-cdk-outline.mjs +6 -6
  25. package/fesm2022/acorex-cdk-outline.mjs.map +1 -1
  26. package/fesm2022/acorex-cdk-overlay.mjs +3 -3
  27. package/fesm2022/acorex-cdk-overlay.mjs.map +1 -1
  28. package/fesm2022/acorex-cdk-pan-view.mjs +3 -3
  29. package/fesm2022/acorex-cdk-pan-view.mjs.map +1 -1
  30. package/fesm2022/acorex-cdk-resizable.mjs +166 -118
  31. package/fesm2022/acorex-cdk-resizable.mjs.map +1 -1
  32. package/fesm2022/acorex-cdk-selection.mjs +10 -10
  33. package/fesm2022/acorex-cdk-selection.mjs.map +1 -1
  34. package/fesm2022/acorex-cdk-sliding-item.mjs +3 -3
  35. package/fesm2022/acorex-cdk-sliding-item.mjs.map +1 -1
  36. package/fesm2022/acorex-cdk-sticky.mjs +3 -3
  37. package/fesm2022/acorex-cdk-sticky.mjs.map +1 -1
  38. package/fesm2022/acorex-cdk-uploader.mjs +746 -0
  39. package/fesm2022/acorex-cdk-uploader.mjs.map +1 -0
  40. package/fesm2022/acorex-cdk-virtual-scroll.mjs +10 -10
  41. package/fesm2022/acorex-cdk-virtual-scroll.mjs.map +1 -1
  42. package/list-navigation/index.d.ts +1 -0
  43. package/package.json +6 -2
  44. package/resizable/index.d.ts +19 -7
  45. package/uploader/README.md +3 -0
  46. package/uploader/index.d.ts +378 -0
@@ -0,0 +1,746 @@
1
+ import { isPlatformBrowser } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { signal, computed, inject, Injectable, ElementRef, ViewContainerRef, input, ChangeDetectorRef, DOCUMENT, PLATFORM_ID, output, Directive } from '@angular/core';
4
+ import { AXTranslationService } from '@acorex/core/translation';
5
+ import { AXUnsubscriber } from '@acorex/core/utils';
6
+ import { AXFileService } from '@acorex/core/file';
7
+ import { sumBy } from 'lodash-es';
8
+ import { Subject, BehaviorSubject, map } from 'rxjs';
9
+
10
+ class AXUploadRequest {
11
+ get name() {
12
+ return this.file.name;
13
+ }
14
+ get ext() {
15
+ const parts = this.name.split('.');
16
+ if (parts.length > 1) {
17
+ return parts[parts.length - 1];
18
+ }
19
+ else {
20
+ // No extension found
21
+ return '';
22
+ }
23
+ }
24
+ get size() {
25
+ return this.file.size;
26
+ }
27
+ get file() {
28
+ return this.uploadFile;
29
+ }
30
+ constructor(uploadFile) {
31
+ this.uploadFile = uploadFile;
32
+ this._progress = signal(0, ...(ngDevMode ? [{ debugName: "_progress" }] : []));
33
+ this.progress = computed(() => this._progress(), ...(ngDevMode ? [{ debugName: "progress" }] : []));
34
+ this._estimateTime = signal(0, ...(ngDevMode ? [{ debugName: "_estimateTime" }] : []));
35
+ this.estimateTime = computed(() => this._estimateTime(), ...(ngDevMode ? [{ debugName: "estimateTime" }] : []));
36
+ this._status = signal('new', ...(ngDevMode ? [{ debugName: "_status" }] : []));
37
+ this.status = computed(() => this._status(), ...(ngDevMode ? [{ debugName: "status" }] : []));
38
+ this._message = signal(null, ...(ngDevMode ? [{ debugName: "_message" }] : []));
39
+ this.message = computed(() => this._message(), ...(ngDevMode ? [{ debugName: "message" }] : []));
40
+ this._isDetermined = signal(false, ...(ngDevMode ? [{ debugName: "_isDetermined" }] : []));
41
+ this.isDetermined = computed(() => this._isDetermined(), ...(ngDevMode ? [{ debugName: "isDetermined" }] : []));
42
+ this.bytesTransferred = 0;
43
+ this.onCancel = new Subject();
44
+ this.onStart = new Subject();
45
+ this.onFailed = new Subject();
46
+ this.onComplete = new BehaviorSubject(null);
47
+ }
48
+ estimateTimeRemaining(bytesTransferred) {
49
+ const now = Date.now();
50
+ const elapsed = now - this.startTime; // Time in milliseconds
51
+ if (isNaN(elapsed) || elapsed === 0) {
52
+ return null; // Avoid division by zero
53
+ }
54
+ const speed = (bytesTransferred || 1) / elapsed; // Bytes per millisecond
55
+ const remainingBytes = this.size - bytesTransferred;
56
+ const estimatedTime = Math.ceil(remainingBytes / speed); // Time in milliseconds
57
+ return estimatedTime; // Return the estimated time in milliseconds
58
+ }
59
+ setTransferredBytes(value) {
60
+ this.bytesTransferred = value;
61
+ if (value > 0 && !this._isDetermined() && (this.status() == 'new' || this.status() == 'inprogress')) {
62
+ this._isDetermined.set(true);
63
+ }
64
+ this.updateEstimateTime();
65
+ }
66
+ updateEstimateTime() {
67
+ this._estimateTime.set(this.estimateTimeRemaining(this.bytesTransferred));
68
+ const progress = Math.floor((this.bytesTransferred / this.size) * 100);
69
+ this._progress.set(progress);
70
+ }
71
+ async upload() {
72
+ this.startTime = Date.now();
73
+ this._progress.set(0);
74
+ this._status.set('inprogress');
75
+ this.onStart.next();
76
+ }
77
+ cancel() {
78
+ this._status.set('canceled');
79
+ this.bytesTransferred = 0;
80
+ this._estimateTime.set(0);
81
+ this._progress.set(0);
82
+ this.onCancel.next();
83
+ }
84
+ redo() {
85
+ // this.startTime = Date.now();
86
+ this._progress.set(0);
87
+ this._status.set('inprogress');
88
+ this._message.set(null);
89
+ this.onStart.next();
90
+ }
91
+ error(message) {
92
+ this._status.set('failed');
93
+ this.bytesTransferred = 0;
94
+ this._estimateTime.set(0);
95
+ this._progress.set(0);
96
+ this._message.set(message);
97
+ this.onFailed.next();
98
+ }
99
+ finish(data) {
100
+ this._status.set('completed');
101
+ this.bytesTransferred = this.size;
102
+ this._estimateTime.set(0);
103
+ this._progress.set(100);
104
+ this.onComplete.next(data);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Service for managing file uploads with drag-and-drop support, progress tracking, and dialog management.
110
+ * @category Services
111
+ */
112
+ class AXUploaderService {
113
+ constructor() {
114
+ /**
115
+ * Translation service for localized text.
116
+ * @ignore
117
+ */
118
+ this.translateService = inject(AXTranslationService);
119
+ /**
120
+ * File service for file operations.
121
+ * @ignore
122
+ */
123
+ this.fileService = inject(AXFileService);
124
+ /**
125
+ * Behavior subject for managing upload requests.
126
+ * @ignore
127
+ */
128
+ this._files$ = new BehaviorSubject([]);
129
+ /**
130
+ * Gets the files behavior subject for observing upload requests.
131
+ */
132
+ this.files = this._files$.asObservable();
133
+ /**
134
+ * Subject for file upload start events.
135
+ */
136
+ this.onFileUploadStart = new Subject();
137
+ /**
138
+ * Subject for file upload complete events.
139
+ */
140
+ this.onFileUploadComplete = new Subject();
141
+ /**
142
+ * Subject for all files upload complete events.
143
+ */
144
+ this.onFilesUploadComplete = new Subject();
145
+ /**
146
+ * Subject for file upload canceled events.
147
+ */
148
+ this.onFileUploadCanceled = new Subject();
149
+ /**
150
+ * Signal indicating if any upload has determined progress.
151
+ */
152
+ this.isAnyDetermined = computed(() => this._files$.value.some((file) => file.isDetermined()), ...(ngDevMode ? [{ debugName: "isAnyDetermined" }] : []));
153
+ /**
154
+ * Observable for total estimated upload time.
155
+ */
156
+ this.totalEstimateTime = this._files$.pipe(map((files) => sumBy(files, (file) => (file.status() === 'inprogress' ? file.estimateTime() : 0))));
157
+ }
158
+ /**
159
+ * Converts a File object to an AXUploadRequest.
160
+ * @private
161
+ */
162
+ convertFileToRequest(file) {
163
+ return new AXUploadRequest(file);
164
+ }
165
+ /**
166
+ * Starts uploading files that are in 'new' status.
167
+ * @private
168
+ */
169
+ async startUpload() {
170
+ const newFiles = this._files$.value.filter((c) => c.status() === 'new');
171
+ for (const file of newFiles) {
172
+ await this.bindEvents(file);
173
+ }
174
+ }
175
+ /**
176
+ * Binds event handlers to an upload request.
177
+ * @private
178
+ */
179
+ async bindEvents(c) {
180
+ c.onStart.subscribe(() => {
181
+ this.onFileUploadStart.next({
182
+ component: this,
183
+ uploadedFile: c,
184
+ isUserInteraction: false,
185
+ });
186
+ });
187
+ c.onComplete.subscribe((data) => {
188
+ Object.assign(c, { metaData: data });
189
+ this.onFileUploadComplete.next({
190
+ component: this,
191
+ uploadedFile: c,
192
+ isUserInteraction: false,
193
+ });
194
+ const isAllDone = this._files$.value.every((f) => f.status() === 'completed' || f.status() === 'canceled' || f.status() === 'failed');
195
+ if (isAllDone) {
196
+ this.onFilesUploadComplete.next({
197
+ component: this,
198
+ uploadedFiles: this._files$.value,
199
+ isUserInteraction: false,
200
+ });
201
+ }
202
+ });
203
+ c.onCancel.subscribe(() => {
204
+ this.onFileUploadCanceled.next({
205
+ component: this,
206
+ uploadedFile: c,
207
+ isUserInteraction: false,
208
+ });
209
+ });
210
+ }
211
+ /**
212
+ * Opens the file browser dialog and returns selected files as upload requests.
213
+ * @param options - Configuration options for file selection
214
+ * @returns Promise that resolves to an array of upload requests
215
+ */
216
+ async browse(options = { multiple: false }) {
217
+ try {
218
+ const files = await this.fileService.choose({ multiple: options?.multiple || false, accept: options.accept });
219
+ if (files.length) {
220
+ return this.add(files);
221
+ }
222
+ return [];
223
+ }
224
+ catch (error) {
225
+ // It's good practice to log the error.
226
+ console.error('File browse failed:', error);
227
+ return [];
228
+ }
229
+ }
230
+ /**
231
+ * Adds files to the upload queue and starts the upload process.
232
+ * @param files - Files to add to the upload queue
233
+ * @returns Promise that resolves to an array of upload requests
234
+ */
235
+ async add(files) {
236
+ const list = Array.from(files).map((f) => this.convertFileToRequest(f));
237
+ const newFiles = [...this._files$.value, ...list];
238
+ this._files$.next(newFiles);
239
+ this.startUpload();
240
+ return list;
241
+ }
242
+ /**
243
+ * Cancels all pending and in-progress uploads.
244
+ */
245
+ async cancelAll() {
246
+ await Promise.all(this._files$.value.filter((c) => c.status() !== 'completed').map((c) => c.cancel()));
247
+ }
248
+ /**
249
+ * Clears all completed uploads from the queue.
250
+ */
251
+ clearAll() {
252
+ const remainingFiles = this._files$.value.filter((c) => c.status() === 'inprogress');
253
+ this._files$.next(remainingFiles);
254
+ }
255
+ /**
256
+ * Removes a specific upload request from the queue.
257
+ * @param item - The upload request to remove
258
+ */
259
+ remove(item) {
260
+ const updatedFiles = this._files$.value.filter((c) => c !== item);
261
+ this._files$.next(updatedFiles);
262
+ }
263
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
264
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
265
+ }
266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderService, decorators: [{
267
+ type: Injectable,
268
+ args: [{ providedIn: 'root' }]
269
+ }] });
270
+
271
+ /**
272
+ * A directive that provides drag-and-drop and file upload functionality.
273
+ * When applied to an element, it enables drag-and-drop file uploads and file browsing.
274
+ * @category Directives
275
+ */
276
+ class AXUploaderZoneDirective {
277
+ /**
278
+ * Initializes the directive with event listeners and service subscriptions.
279
+ */
280
+ constructor() {
281
+ /**
282
+ * The element reference for the directive host.
283
+ * @ignore
284
+ */
285
+ this.elementRef = inject(ElementRef);
286
+ /**
287
+ * View container reference for rendering custom templates.
288
+ * @ignore
289
+ */
290
+ this.viewContainerRef = inject(ViewContainerRef);
291
+ /**
292
+ * Whether multiple files can be selected.
293
+ * @defaultValue true
294
+ */
295
+ this.multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
296
+ /**
297
+ * File types that are accepted for upload.
298
+ * @defaultValue null
299
+ */
300
+ this.accept = input(null, ...(ngDevMode ? [{ debugName: "accept" }] : []));
301
+ /**
302
+ * Custom template for the drag overlay. If provided, this will be used instead of the default overlay.
303
+ */
304
+ this.overlayTemplate = input(...(ngDevMode ? [undefined, { debugName: "overlayTemplate" }] : []));
305
+ /**
306
+ * Whether browsing files by clicking on the container is disabled.
307
+ * @defaultValue false
308
+ */
309
+ this.disableBrowse = input(false, ...(ngDevMode ? [{ debugName: "disableBrowse" }] : []));
310
+ /**
311
+ * Whether drag and drop functionality is disabled.
312
+ * @defaultValue false
313
+ */
314
+ this.disableDragDrop = input(false, ...(ngDevMode ? [{ debugName: "disableDragDrop" }] : []));
315
+ /**
316
+ * Change detector reference.
317
+ * @ignore
318
+ */
319
+ this.cdr = inject(ChangeDetectorRef);
320
+ /**
321
+ * Document reference.
322
+ * @ignore
323
+ */
324
+ this.document = inject(DOCUMENT);
325
+ /**
326
+ * Platform ID for browser detection.
327
+ * @ignore
328
+ */
329
+ this.platformID = inject(PLATFORM_ID);
330
+ /**
331
+ * Upload service instance.
332
+ * @ignore
333
+ */
334
+ this.uploadService = inject(AXUploaderService);
335
+ /**
336
+ * Unsubscriber service instance.
337
+ * @ignore
338
+ */
339
+ this.unsubscriber = inject(AXUnsubscriber);
340
+ /**
341
+ * Translation service instance.
342
+ * @ignore
343
+ */
344
+ this.translateService = inject(AXTranslationService);
345
+ /**
346
+ * Embedded view reference for custom template.
347
+ * @ignore
348
+ */
349
+ this.templateViewRef = null;
350
+ /**
351
+ * Emitted when files are selected or dropped (like HTML file input).
352
+ * Emits an event with files property containing the selected files.
353
+ */
354
+ this.fileChange = output();
355
+ /**
356
+ * Emitted when files are changed (added, removed, etc.).
357
+ * @deprecated Use fileChange instead for simpler file input-like behavior
358
+ */
359
+ this.onChanged = output();
360
+ /**
361
+ * Emitted on drag enter event.
362
+ */
363
+ this.dragEnter = output();
364
+ /**
365
+ * Emitted on drag leave event.
366
+ */
367
+ this.dragLeave = output();
368
+ /**
369
+ * Emitted on drag over event.
370
+ */
371
+ this.dragOver = output();
372
+ /**
373
+ * Emitted when a single file upload is completed.
374
+ */
375
+ this.onFileUploadComplete = output();
376
+ /**
377
+ * Emitted when all files upload is completed.
378
+ */
379
+ this.onFilesUploadComplete = output();
380
+ /**
381
+ * CSS class for the overlay state.
382
+ * @ignore
383
+ */
384
+ this.stateClass = 'ax-uploader-overlay-state';
385
+ /**
386
+ * The overlay element for drag and drop visual feedback.
387
+ * @ignore
388
+ */
389
+ this.overlayElement = null;
390
+ /**
391
+ * Flag to track if dragOver has been emitted for the current drag session.
392
+ * @ignore
393
+ */
394
+ this.dragOverEmitted = signal(false, ...(ngDevMode ? [{ debugName: "dragOverEmitted" }] : []));
395
+ /**
396
+ * Counter to track drag enter/leave depth to prevent false dragleave events.
397
+ * When moving from parent to child, dragleave fires even though we're still inside.
398
+ * @ignore
399
+ */
400
+ this.dragDepth = signal(0, ...(ngDevMode ? [{ debugName: "dragDepth" }] : []));
401
+ /**
402
+ * Animation end handler for cleanup.
403
+ * @ignore
404
+ */
405
+ this.animationEndHandler = null;
406
+ this.element = this.elementRef.nativeElement;
407
+ this.element.style.position = 'relative';
408
+ //
409
+ this.uploadService.onFileUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
410
+ this.onFileUploadComplete.emit(c);
411
+ });
412
+ this.uploadService.onFilesUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
413
+ this.onFilesUploadComplete.emit(c);
414
+ });
415
+ //
416
+ setTimeout(() => {
417
+ const browseHandlers = this.element.querySelectorAll('[data-ax-uploader-browse-handle="true"]');
418
+ if (browseHandlers.length <= 0 && !this.disableBrowse()) {
419
+ this.element.addEventListener('click', this.browser.bind(this), true);
420
+ }
421
+ });
422
+ if (!this.disableDragDrop()) {
423
+ this.element.addEventListener('dragenter', this.handleDragEnter.bind(this), true);
424
+ this.element.addEventListener('dragover', this.handleDragOver.bind(this), true);
425
+ this.element.addEventListener('drop', this.handleOnDrop.bind(this), true);
426
+ this.element.addEventListener('dragleave', (e) => this.removeZone(e), true);
427
+ }
428
+ }
429
+ /**
430
+ * Cleans up event listeners when the directive is destroyed.
431
+ */
432
+ ngOnDestroy() {
433
+ this.element.removeEventListener('click', this.browser.bind(this));
434
+ this.element.removeEventListener('dragenter', this.handleDragEnter.bind(this));
435
+ this.element.removeEventListener('drop', this.handleOnDrop.bind(this));
436
+ this.element.removeEventListener('dragover', this.handleDragOver.bind(this));
437
+ this.element.removeEventListener('dragleave', this.removeZone.bind(this));
438
+ }
439
+ /**
440
+ * Handles drag enter events to show the upload overlay.
441
+ * @private
442
+ */
443
+ async handleDragEnter(event) {
444
+ if (this.disableDragDrop())
445
+ return;
446
+ this.dragOverEmitted.set(false); // Reset flag when entering
447
+ this.dragDepth.update((depth) => depth + 1); // Increment depth counter
448
+ // Only create overlay on first enter (when depth is 1)
449
+ if (this.dragDepth() === 1) {
450
+ // Cancel any pending exit animation
451
+ this.cancelExitAnimation();
452
+ await this.createZone();
453
+ this.dragEnter.emit(event);
454
+ }
455
+ event.preventDefault();
456
+ event.stopImmediatePropagation();
457
+ }
458
+ /**
459
+ * Handles drop events to process dropped files.
460
+ * @private
461
+ */
462
+ async handleOnDrop(event) {
463
+ if (this.disableDragDrop())
464
+ return;
465
+ event.preventDefault();
466
+ event.stopImmediatePropagation();
467
+ // Reset drag depth on drop
468
+ this.dragDepth.set(0);
469
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
470
+ const files = Array.from(event.dataTransfer.files);
471
+ // Emit simple file change event (like HTML input)
472
+ this.fileChange.emit({ event, files });
473
+ // Also emit the old onChanged event for backward compatibility
474
+ const requests = await this.uploadService.add(event.dataTransfer.files);
475
+ this.onChanged.emit({
476
+ component: this,
477
+ requests,
478
+ isUserInteraction: true,
479
+ });
480
+ }
481
+ this.removeZone();
482
+ this.cdr.detectChanges();
483
+ }
484
+ /**
485
+ * Handles drag over events to allow dropping.
486
+ * @private
487
+ */
488
+ handleDragOver(event) {
489
+ if (this.disableDragDrop())
490
+ return;
491
+ // Only emit dragOver once per drag session to avoid spam
492
+ if (!this.dragOverEmitted()) {
493
+ this.dragOverEmitted.set(true);
494
+ this.dragOver.emit(event);
495
+ }
496
+ event.preventDefault();
497
+ event.stopImmediatePropagation();
498
+ }
499
+ /**
500
+ * Creates the visual overlay for drag and drop feedback.
501
+ * @private
502
+ */
503
+ async createZone() {
504
+ if (isPlatformBrowser(this.platformID)) {
505
+ // Check if overlay already exists (might be exiting)
506
+ let existingOverlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
507
+ // Verify the overlay is actually in the DOM (not removed after animation)
508
+ if (existingOverlay && existingOverlay.parentNode === this.element) {
509
+ // Remove exiting class to cancel exit animation and show overlay again
510
+ existingOverlay.classList.remove('ax-uploader-overlay-exiting');
511
+ // Clean up any pending animation handler
512
+ if (this.animationEndHandler) {
513
+ existingOverlay.removeEventListener('animationend', this.animationEndHandler);
514
+ this.animationEndHandler = null;
515
+ }
516
+ // Reset overlayElement reference if it was cleared
517
+ if (!this.overlayElement && existingOverlay) {
518
+ this.overlayElement = existingOverlay;
519
+ }
520
+ return;
521
+ }
522
+ else if (existingOverlay && existingOverlay.parentNode !== this.element) {
523
+ // Overlay exists but is not in the DOM, clear the reference
524
+ this.overlayElement = null;
525
+ existingOverlay = null;
526
+ }
527
+ // Check if custom template is provided
528
+ const template = this.overlayTemplate();
529
+ if (template) {
530
+ // Create container for template
531
+ this.overlayElement = this.document.createElement('div');
532
+ this.overlayElement.classList.add('ax-uploader-overlay-state', '-ax-z-1');
533
+ this.overlayElement.id = 'ax-uploader-overlay-state';
534
+ // Create embedded view from template (will be inserted at directive anchor)
535
+ this.viewContainerRef.clear();
536
+ this.templateViewRef = this.viewContainerRef.createEmbeddedView(template);
537
+ this.templateViewRef.detectChanges();
538
+ // Get root nodes and move them to overlay element
539
+ const rootNodes = this.templateViewRef.rootNodes;
540
+ for (const node of rootNodes) {
541
+ // Remove from current location if it has a parent
542
+ if (node.parentNode) {
543
+ node.parentNode.removeChild(node);
544
+ }
545
+ this.overlayElement.appendChild(node);
546
+ }
547
+ // Detach the view since we've moved the nodes
548
+ this.templateViewRef.detach();
549
+ this.element.appendChild(this.overlayElement);
550
+ this.cdr.detectChanges();
551
+ return;
552
+ }
553
+ // Use default overlay
554
+ this.overlayElement = this.document.createElement('div');
555
+ this.overlayElement.classList.add('ax-uploader-overlay-state', '-ax-z-1');
556
+ this.overlayElement.id = 'ax-uploader-overlay-state';
557
+ const icon = this.document.createElement('span');
558
+ icon.classList.add('ax-icon', 'ax-icon-upload');
559
+ const text = this.document.createElement('span');
560
+ text.innerText = await this.translateService.translateAsync('@acorex:uploader.zone.text');
561
+ this.overlayElement.appendChild(icon);
562
+ this.overlayElement.appendChild(text);
563
+ this.element.appendChild(this.overlayElement);
564
+ }
565
+ }
566
+ /**
567
+ * Cancels any pending exit animation and cleans up handlers.
568
+ * @private
569
+ */
570
+ cancelExitAnimation() {
571
+ if (isPlatformBrowser(this.platformID)) {
572
+ const overlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
573
+ if (overlay && this.animationEndHandler) {
574
+ // Remove exiting class to cancel animation
575
+ overlay.classList.remove('ax-uploader-overlay-exiting');
576
+ // Clean up the handler
577
+ overlay.removeEventListener('animationend', this.animationEndHandler);
578
+ this.animationEndHandler = null;
579
+ }
580
+ }
581
+ }
582
+ /**
583
+ * Removes the visual overlay for drag and drop feedback.
584
+ * @private
585
+ */
586
+ removeZone(event) {
587
+ if (this.disableDragDrop())
588
+ return;
589
+ this.dragDepth.update((depth) => Math.max(0, depth - 1)); // Decrement depth counter, ensure non-negative
590
+ // Only remove overlay when we've truly left the drop zone (depth reaches 0)
591
+ if (this.dragDepth() <= 0) {
592
+ this.dragDepth.set(0); // Ensure it's exactly 0
593
+ this.dragOverEmitted.set(false); // Reset flag when leaving
594
+ if (event) {
595
+ this.dragLeave.emit(event);
596
+ }
597
+ if (isPlatformBrowser(this.platformID)) {
598
+ const overlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
599
+ if (overlay) {
600
+ // Clean up any existing handler first
601
+ if (this.animationEndHandler) {
602
+ overlay.removeEventListener('animationend', this.animationEndHandler);
603
+ }
604
+ // Add exiting class to trigger CSS animation
605
+ overlay.classList.add('ax-uploader-overlay-exiting');
606
+ // Wait for CSS animation to complete
607
+ this.animationEndHandler = (e) => {
608
+ if (e.target === overlay && e.animationName === 'fadeOutScale') {
609
+ overlay.removeEventListener('animationend', this.animationEndHandler);
610
+ this.animationEndHandler = null;
611
+ if (overlay.parentNode === this.element) {
612
+ this.element.removeChild(overlay);
613
+ }
614
+ if (this.overlayElement && this.overlayElement.parentNode) {
615
+ this.overlayElement.remove();
616
+ }
617
+ // Clear the overlay element reference
618
+ this.overlayElement = null;
619
+ // Remove custom template view if it exists
620
+ if (this.templateViewRef) {
621
+ this.viewContainerRef.clear();
622
+ this.templateViewRef = null;
623
+ }
624
+ }
625
+ };
626
+ overlay.addEventListener('animationend', this.animationEndHandler);
627
+ }
628
+ else {
629
+ // If overlay doesn't exist, just clean up template view
630
+ if (this.templateViewRef) {
631
+ this.viewContainerRef.clear();
632
+ this.templateViewRef = null;
633
+ }
634
+ }
635
+ }
636
+ }
637
+ }
638
+ /**
639
+ * Opens the file browser dialog and processes selected files.
640
+ * @returns Promise that resolves when files are processed
641
+ */
642
+ async browser() {
643
+ if (this.disableBrowse())
644
+ return;
645
+ const files = await this.uploadService.browse({ accept: this.accept() ?? undefined, multiple: this.multiple() });
646
+ if (files.length > 0) {
647
+ // Create a synthetic event for consistency
648
+ const syntheticEvent = new Event('change');
649
+ const target = Object.create(EventTarget.prototype);
650
+ target.files = files.map((r) => r.file);
651
+ Object.defineProperty(syntheticEvent, 'target', { value: target, writable: false });
652
+ // Emit simple file change event (like HTML input)
653
+ this.fileChange.emit({
654
+ event: syntheticEvent,
655
+ files: files.map((r) => r.file),
656
+ });
657
+ // Also emit the old onChanged event for backward compatibility
658
+ this.onChanged.emit({
659
+ component: this,
660
+ requests: files,
661
+ isUserInteraction: true,
662
+ });
663
+ }
664
+ }
665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
666
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: AXUploaderZoneDirective, isStandalone: true, selector: "[axUploaderZone]", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, overlayTemplate: { classPropertyName: "overlayTemplate", publicName: "overlayTemplate", isSignal: true, isRequired: false, transformFunction: null }, disableBrowse: { classPropertyName: "disableBrowse", publicName: "disableBrowse", isSignal: true, isRequired: false, transformFunction: null }, disableDragDrop: { classPropertyName: "disableDragDrop", publicName: "disableDragDrop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", onChanged: "onChanged", dragEnter: "dragEnter", dragLeave: "dragLeave", dragOver: "dragOver", onFileUploadComplete: "onFileUploadComplete", onFilesUploadComplete: "onFilesUploadComplete" }, host: { classAttribute: "ax-drop-zone" }, providers: [AXUnsubscriber], ngImport: i0 }); }
667
+ }
668
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
669
+ type: Directive,
670
+ args: [{
671
+ selector: '[axUploaderZone]',
672
+ providers: [AXUnsubscriber],
673
+ host: {
674
+ class: 'ax-drop-zone',
675
+ },
676
+ }]
677
+ }], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], overlayTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "overlayTemplate", required: false }] }], disableBrowse: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableBrowse", required: false }] }], disableDragDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableDragDrop", required: false }] }], fileChange: [{ type: i0.Output, args: ["fileChange"] }], onChanged: [{ type: i0.Output, args: ["onChanged"] }], dragEnter: [{ type: i0.Output, args: ["dragEnter"] }], dragLeave: [{ type: i0.Output, args: ["dragLeave"] }], dragOver: [{ type: i0.Output, args: ["dragOver"] }], onFileUploadComplete: [{ type: i0.Output, args: ["onFileUploadComplete"] }], onFilesUploadComplete: [{ type: i0.Output, args: ["onFilesUploadComplete"] }] } });
678
+
679
+ /**
680
+ * A directive that provides browse functionality for file uploads.
681
+ * When applied to an element, clicking it will trigger the file browser dialog.
682
+ * @category Directives
683
+ */
684
+ class AXUploaderBrowseDirective {
685
+ constructor() {
686
+ /**
687
+ * The uploader zone directive instance.
688
+ * @ignore
689
+ */
690
+ this.uploaderZone = inject(AXUploaderZoneDirective);
691
+ /**
692
+ * The element reference for the directive host.
693
+ * @ignore
694
+ */
695
+ this.elementRef = inject((ElementRef));
696
+ /**
697
+ * Platform ID for browser detection.
698
+ * @ignore
699
+ */
700
+ this.platformID = inject(PLATFORM_ID);
701
+ }
702
+ /**
703
+ * Initializes the directive by adding click event listener and data attribute.
704
+ */
705
+ ngOnInit() {
706
+ if (isPlatformBrowser(this.platformID) && this.elementRef?.nativeElement) {
707
+ const element = this.elementRef.nativeElement;
708
+ element.addEventListener('click', this.handleClick.bind(this));
709
+ // Use setAttribute for SSR compatibility
710
+ if (element.dataset) {
711
+ element.dataset['axUploaderBrowseHandle'] = 'true';
712
+ }
713
+ else {
714
+ element.setAttribute('data-ax-uploader-browse-handle', 'true');
715
+ }
716
+ }
717
+ }
718
+ /**
719
+ * Cleans up the directive by removing event listeners.
720
+ */
721
+ ngOnDestroy() {
722
+ if (isPlatformBrowser(this.platformID) && this.elementRef.nativeElement) {
723
+ this.elementRef.nativeElement.removeEventListener('click', this.handleClick.bind(this));
724
+ }
725
+ }
726
+ /**
727
+ * Handles the click event to trigger file browser.
728
+ * @private
729
+ */
730
+ async handleClick() {
731
+ await this.uploaderZone.browser();
732
+ }
733
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
734
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
737
+ type: Directive,
738
+ args: [{ selector: '[axUploaderBrowseHandle]' }]
739
+ }] });
740
+
741
+ /**
742
+ * Generated bundle index. Do not edit.
743
+ */
744
+
745
+ export { AXUploadRequest, AXUploaderBrowseDirective, AXUploaderService, AXUploaderZoneDirective };
746
+ //# sourceMappingURL=acorex-cdk-uploader.mjs.map