@acorex/cdk 21.0.0-next.2 → 21.0.0-next.20

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 (49) 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 +55 -70
  5. package/fesm2022/acorex-cdk-accordion.mjs.map +1 -1
  6. package/fesm2022/acorex-cdk-carousel.mjs +9 -7
  7. package/fesm2022/acorex-cdk-carousel.mjs.map +1 -1
  8. package/fesm2022/acorex-cdk-clipboard.mjs +7 -7
  9. package/fesm2022/acorex-cdk-clipboard.mjs.map +1 -1
  10. package/fesm2022/acorex-cdk-common.mjs +110 -110
  11. package/fesm2022/acorex-cdk-common.mjs.map +1 -1
  12. package/fesm2022/acorex-cdk-dom.mjs +4 -4
  13. package/fesm2022/acorex-cdk-dom.mjs.map +1 -1
  14. package/fesm2022/acorex-cdk-drag-drop.mjs +279 -86
  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 +66 -63
  21. package/fesm2022/acorex-cdk-input-mask.mjs.map +1 -1
  22. package/fesm2022/acorex-cdk-list-navigation.mjs +41 -23
  23. package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
  24. package/fesm2022/acorex-cdk-outline.mjs +8 -8
  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 +4 -4
  29. package/fesm2022/acorex-cdk-pan-view.mjs.map +1 -1
  30. package/fesm2022/acorex-cdk-resizable.mjs +4 -4
  31. package/fesm2022/acorex-cdk-resizable.mjs.map +1 -1
  32. package/fesm2022/acorex-cdk-selection.mjs +12 -12
  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 +723 -0
  39. package/fesm2022/acorex-cdk-uploader.mjs.map +1 -0
  40. package/fesm2022/acorex-cdk-virtual-scroll.mjs +11 -11
  41. package/fesm2022/acorex-cdk-virtual-scroll.mjs.map +1 -1
  42. package/fesm2022/acorex-cdk-wysiwyg.mjs +5 -1
  43. package/fesm2022/acorex-cdk-wysiwyg.mjs.map +1 -1
  44. package/input-mask/index.d.ts +1 -0
  45. package/list-navigation/index.d.ts +1 -0
  46. package/package.json +6 -2
  47. package/uploader/README.md +3 -0
  48. package/uploader/index.d.ts +368 -0
  49. package/wysiwyg/index.d.ts +1 -0
@@ -0,0 +1,723 @@
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
+ * Change detector reference.
307
+ * @ignore
308
+ */
309
+ this.cdr = inject(ChangeDetectorRef);
310
+ /**
311
+ * Document reference.
312
+ * @ignore
313
+ */
314
+ this.document = inject(DOCUMENT);
315
+ /**
316
+ * Platform ID for browser detection.
317
+ * @ignore
318
+ */
319
+ this.platformID = inject(PLATFORM_ID);
320
+ /**
321
+ * Upload service instance.
322
+ * @ignore
323
+ */
324
+ this.uploadService = inject(AXUploaderService);
325
+ /**
326
+ * Unsubscriber service instance.
327
+ * @ignore
328
+ */
329
+ this.unsubscriber = inject(AXUnsubscriber);
330
+ /**
331
+ * Translation service instance.
332
+ * @ignore
333
+ */
334
+ this.translateService = inject(AXTranslationService);
335
+ /**
336
+ * Embedded view reference for custom template.
337
+ * @ignore
338
+ */
339
+ this.templateViewRef = null;
340
+ /**
341
+ * Emitted when files are selected or dropped (like HTML file input).
342
+ * Emits an event with files property containing the selected files.
343
+ */
344
+ this.fileChange = output();
345
+ /**
346
+ * Emitted when files are changed (added, removed, etc.).
347
+ * @deprecated Use fileChange instead for simpler file input-like behavior
348
+ */
349
+ this.onChanged = output();
350
+ /**
351
+ * Emitted on drag enter event.
352
+ */
353
+ this.dragEnter = output();
354
+ /**
355
+ * Emitted on drag leave event.
356
+ */
357
+ this.dragLeave = output();
358
+ /**
359
+ * Emitted on drag over event.
360
+ */
361
+ this.dragOver = output();
362
+ /**
363
+ * Emitted when a single file upload is completed.
364
+ */
365
+ this.onFileUploadComplete = output();
366
+ /**
367
+ * Emitted when all files upload is completed.
368
+ */
369
+ this.onFilesUploadComplete = output();
370
+ /**
371
+ * CSS class for the overlay state.
372
+ * @ignore
373
+ */
374
+ this.stateClass = 'ax-uploader-overlay-state';
375
+ /**
376
+ * The overlay element for drag and drop visual feedback.
377
+ * @ignore
378
+ */
379
+ this.overlayElement = null;
380
+ /**
381
+ * Flag to track if dragOver has been emitted for the current drag session.
382
+ * @ignore
383
+ */
384
+ this.dragOverEmitted = signal(false, ...(ngDevMode ? [{ debugName: "dragOverEmitted" }] : []));
385
+ /**
386
+ * Counter to track drag enter/leave depth to prevent false dragleave events.
387
+ * When moving from parent to child, dragleave fires even though we're still inside.
388
+ * @ignore
389
+ */
390
+ this.dragDepth = signal(0, ...(ngDevMode ? [{ debugName: "dragDepth" }] : []));
391
+ /**
392
+ * Animation end handler for cleanup.
393
+ * @ignore
394
+ */
395
+ this.animationEndHandler = null;
396
+ this.element = this.elementRef.nativeElement;
397
+ this.element.style.position = 'relative';
398
+ //
399
+ this.uploadService.onFileUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
400
+ this.onFileUploadComplete.emit(c);
401
+ });
402
+ this.uploadService.onFilesUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
403
+ this.onFilesUploadComplete.emit(c);
404
+ });
405
+ //
406
+ setTimeout(() => {
407
+ const browseHandlers = this.element.querySelectorAll('[data-ax-uploader-browse-handle="true"]');
408
+ if (browseHandlers.length <= 0)
409
+ this.element.addEventListener('click', this.browser.bind(this), true);
410
+ });
411
+ this.element.addEventListener('dragenter', this.handleDragEnter.bind(this), true);
412
+ this.element.addEventListener('dragover', this.handleDragOver.bind(this), true);
413
+ this.element.addEventListener('drop', this.handleOnDrop.bind(this), true);
414
+ this.element.addEventListener('dragleave', (e) => this.removeZone(e), true);
415
+ }
416
+ /**
417
+ * Cleans up event listeners when the directive is destroyed.
418
+ */
419
+ ngOnDestroy() {
420
+ this.element.removeEventListener('click', this.browser.bind(this));
421
+ this.element.removeEventListener('dragenter', this.handleDragEnter.bind(this));
422
+ this.element.removeEventListener('drop', this.handleOnDrop.bind(this));
423
+ this.element.removeEventListener('dragover', this.handleDragOver.bind(this));
424
+ this.element.removeEventListener('dragleave', this.removeZone.bind(this));
425
+ }
426
+ /**
427
+ * Handles drag enter events to show the upload overlay.
428
+ * @private
429
+ */
430
+ async handleDragEnter(event) {
431
+ this.dragOverEmitted.set(false); // Reset flag when entering
432
+ this.dragDepth.update((depth) => depth + 1); // Increment depth counter
433
+ // Only create overlay on first enter (when depth is 1)
434
+ if (this.dragDepth() === 1) {
435
+ // Cancel any pending exit animation
436
+ this.cancelExitAnimation();
437
+ await this.createZone();
438
+ this.dragEnter.emit(event);
439
+ }
440
+ event.preventDefault();
441
+ event.stopImmediatePropagation();
442
+ }
443
+ /**
444
+ * Handles drop events to process dropped files.
445
+ * @private
446
+ */
447
+ async handleOnDrop(event) {
448
+ event.preventDefault();
449
+ event.stopImmediatePropagation();
450
+ // Reset drag depth on drop
451
+ this.dragDepth.set(0);
452
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
453
+ const files = Array.from(event.dataTransfer.files);
454
+ // Emit simple file change event (like HTML input)
455
+ this.fileChange.emit({ event, files });
456
+ // Also emit the old onChanged event for backward compatibility
457
+ const requests = await this.uploadService.add(event.dataTransfer.files);
458
+ this.onChanged.emit({
459
+ component: this,
460
+ requests,
461
+ isUserInteraction: true,
462
+ });
463
+ }
464
+ this.removeZone();
465
+ this.cdr.detectChanges();
466
+ }
467
+ /**
468
+ * Handles drag over events to allow dropping.
469
+ * @private
470
+ */
471
+ handleDragOver(event) {
472
+ // Only emit dragOver once per drag session to avoid spam
473
+ if (!this.dragOverEmitted()) {
474
+ this.dragOverEmitted.set(true);
475
+ this.dragOver.emit(event);
476
+ }
477
+ event.preventDefault();
478
+ event.stopImmediatePropagation();
479
+ }
480
+ /**
481
+ * Creates the visual overlay for drag and drop feedback.
482
+ * @private
483
+ */
484
+ async createZone() {
485
+ if (isPlatformBrowser(this.platformID)) {
486
+ // Check if overlay already exists (might be exiting)
487
+ let existingOverlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
488
+ // Verify the overlay is actually in the DOM (not removed after animation)
489
+ if (existingOverlay && existingOverlay.parentNode === this.element) {
490
+ // Remove exiting class to cancel exit animation and show overlay again
491
+ existingOverlay.classList.remove('ax-uploader-overlay-exiting');
492
+ // Clean up any pending animation handler
493
+ if (this.animationEndHandler) {
494
+ existingOverlay.removeEventListener('animationend', this.animationEndHandler);
495
+ this.animationEndHandler = null;
496
+ }
497
+ // Reset overlayElement reference if it was cleared
498
+ if (!this.overlayElement && existingOverlay) {
499
+ this.overlayElement = existingOverlay;
500
+ }
501
+ return;
502
+ }
503
+ else if (existingOverlay && existingOverlay.parentNode !== this.element) {
504
+ // Overlay exists but is not in the DOM, clear the reference
505
+ this.overlayElement = null;
506
+ existingOverlay = null;
507
+ }
508
+ // Check if custom template is provided
509
+ const template = this.overlayTemplate();
510
+ if (template) {
511
+ // Create container for template
512
+ this.overlayElement = this.document.createElement('div');
513
+ this.overlayElement.classList.add('ax-uploader-overlay-state', '-ax-z-1');
514
+ this.overlayElement.id = 'ax-uploader-overlay-state';
515
+ // Create embedded view from template (will be inserted at directive anchor)
516
+ this.viewContainerRef.clear();
517
+ this.templateViewRef = this.viewContainerRef.createEmbeddedView(template);
518
+ this.templateViewRef.detectChanges();
519
+ // Get root nodes and move them to overlay element
520
+ const rootNodes = this.templateViewRef.rootNodes;
521
+ for (const node of rootNodes) {
522
+ // Remove from current location if it has a parent
523
+ if (node.parentNode) {
524
+ node.parentNode.removeChild(node);
525
+ }
526
+ this.overlayElement.appendChild(node);
527
+ }
528
+ // Detach the view since we've moved the nodes
529
+ this.templateViewRef.detach();
530
+ this.element.appendChild(this.overlayElement);
531
+ this.cdr.detectChanges();
532
+ return;
533
+ }
534
+ // Use default overlay
535
+ this.overlayElement = this.document.createElement('div');
536
+ this.overlayElement.classList.add('ax-uploader-overlay-state', '-ax-z-1');
537
+ this.overlayElement.id = 'ax-uploader-overlay-state';
538
+ const icon = this.document.createElement('span');
539
+ icon.classList.add('ax-icon', 'ax-icon-upload');
540
+ const text = this.document.createElement('span');
541
+ text.innerText = await this.translateService.translateAsync('@acorex:uploader.zone.text');
542
+ this.overlayElement.appendChild(icon);
543
+ this.overlayElement.appendChild(text);
544
+ this.element.appendChild(this.overlayElement);
545
+ }
546
+ }
547
+ /**
548
+ * Cancels any pending exit animation and cleans up handlers.
549
+ * @private
550
+ */
551
+ cancelExitAnimation() {
552
+ if (isPlatformBrowser(this.platformID)) {
553
+ const overlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
554
+ if (overlay && this.animationEndHandler) {
555
+ // Remove exiting class to cancel animation
556
+ overlay.classList.remove('ax-uploader-overlay-exiting');
557
+ // Clean up the handler
558
+ overlay.removeEventListener('animationend', this.animationEndHandler);
559
+ this.animationEndHandler = null;
560
+ }
561
+ }
562
+ }
563
+ /**
564
+ * Removes the visual overlay for drag and drop feedback.
565
+ * @private
566
+ */
567
+ removeZone(event) {
568
+ this.dragDepth.update((depth) => Math.max(0, depth - 1)); // Decrement depth counter, ensure non-negative
569
+ // Only remove overlay when we've truly left the drop zone (depth reaches 0)
570
+ if (this.dragDepth() <= 0) {
571
+ this.dragDepth.set(0); // Ensure it's exactly 0
572
+ this.dragOverEmitted.set(false); // Reset flag when leaving
573
+ if (event) {
574
+ this.dragLeave.emit(event);
575
+ }
576
+ if (isPlatformBrowser(this.platformID)) {
577
+ const overlay = this.document.getElementById('ax-uploader-overlay-state') || this.overlayElement;
578
+ if (overlay) {
579
+ // Clean up any existing handler first
580
+ if (this.animationEndHandler) {
581
+ overlay.removeEventListener('animationend', this.animationEndHandler);
582
+ }
583
+ // Add exiting class to trigger CSS animation
584
+ overlay.classList.add('ax-uploader-overlay-exiting');
585
+ // Wait for CSS animation to complete
586
+ this.animationEndHandler = (e) => {
587
+ if (e.target === overlay && e.animationName === 'fadeOutScale') {
588
+ overlay.removeEventListener('animationend', this.animationEndHandler);
589
+ this.animationEndHandler = null;
590
+ if (overlay.parentNode === this.element) {
591
+ this.element.removeChild(overlay);
592
+ }
593
+ if (this.overlayElement && this.overlayElement.parentNode) {
594
+ this.overlayElement.remove();
595
+ }
596
+ // Clear the overlay element reference
597
+ this.overlayElement = null;
598
+ // Remove custom template view if it exists
599
+ if (this.templateViewRef) {
600
+ this.viewContainerRef.clear();
601
+ this.templateViewRef = null;
602
+ }
603
+ }
604
+ };
605
+ overlay.addEventListener('animationend', this.animationEndHandler);
606
+ }
607
+ else {
608
+ // If overlay doesn't exist, just clean up template view
609
+ if (this.templateViewRef) {
610
+ this.viewContainerRef.clear();
611
+ this.templateViewRef = null;
612
+ }
613
+ }
614
+ }
615
+ }
616
+ }
617
+ /**
618
+ * Opens the file browser dialog and processes selected files.
619
+ * @returns Promise that resolves when files are processed
620
+ */
621
+ async browser() {
622
+ const files = await this.uploadService.browse({ accept: this.accept() ?? undefined, multiple: this.multiple() });
623
+ if (files.length > 0) {
624
+ // Create a synthetic event for consistency
625
+ const syntheticEvent = new Event('change');
626
+ const target = Object.create(EventTarget.prototype);
627
+ target.files = files.map((r) => r.file);
628
+ Object.defineProperty(syntheticEvent, 'target', { value: target, writable: false });
629
+ // Emit simple file change event (like HTML input)
630
+ this.fileChange.emit({
631
+ event: syntheticEvent,
632
+ files: files.map((r) => r.file),
633
+ });
634
+ // Also emit the old onChanged event for backward compatibility
635
+ this.onChanged.emit({
636
+ component: this,
637
+ requests: files,
638
+ isUserInteraction: true,
639
+ });
640
+ }
641
+ }
642
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
643
+ 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 } }, outputs: { fileChange: "fileChange", onChanged: "onChanged", dragEnter: "dragEnter", dragLeave: "dragLeave", dragOver: "dragOver", onFileUploadComplete: "onFileUploadComplete", onFilesUploadComplete: "onFilesUploadComplete" }, host: { classAttribute: "ax-drop-zone" }, providers: [AXUnsubscriber], ngImport: i0 }); }
644
+ }
645
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
646
+ type: Directive,
647
+ args: [{
648
+ selector: '[axUploaderZone]',
649
+ providers: [AXUnsubscriber],
650
+ host: {
651
+ class: 'ax-drop-zone',
652
+ },
653
+ }]
654
+ }], 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 }] }], 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"] }] } });
655
+
656
+ /**
657
+ * A directive that provides browse functionality for file uploads.
658
+ * When applied to an element, clicking it will trigger the file browser dialog.
659
+ * @category Directives
660
+ */
661
+ class AXUploaderBrowseDirective {
662
+ constructor() {
663
+ /**
664
+ * The uploader zone directive instance.
665
+ * @ignore
666
+ */
667
+ this.uploaderZone = inject(AXUploaderZoneDirective);
668
+ /**
669
+ * The element reference for the directive host.
670
+ * @ignore
671
+ */
672
+ this.elementRef = inject((ElementRef));
673
+ /**
674
+ * Platform ID for browser detection.
675
+ * @ignore
676
+ */
677
+ this.platformID = inject(PLATFORM_ID);
678
+ }
679
+ /**
680
+ * Initializes the directive by adding click event listener and data attribute.
681
+ */
682
+ ngOnInit() {
683
+ if (isPlatformBrowser(this.platformID) && this.elementRef?.nativeElement) {
684
+ const element = this.elementRef.nativeElement;
685
+ element.addEventListener('click', this.handleClick.bind(this));
686
+ // Use setAttribute for SSR compatibility
687
+ if (element.dataset) {
688
+ element.dataset['axUploaderBrowseHandle'] = 'true';
689
+ }
690
+ else {
691
+ element.setAttribute('data-ax-uploader-browse-handle', 'true');
692
+ }
693
+ }
694
+ }
695
+ /**
696
+ * Cleans up the directive by removing event listeners.
697
+ */
698
+ ngOnDestroy() {
699
+ if (isPlatformBrowser(this.platformID) && this.elementRef.nativeElement) {
700
+ this.elementRef.nativeElement.removeEventListener('click', this.handleClick.bind(this));
701
+ }
702
+ }
703
+ /**
704
+ * Handles the click event to trigger file browser.
705
+ * @private
706
+ */
707
+ async handleClick() {
708
+ await this.uploaderZone.browser();
709
+ }
710
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
711
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
712
+ }
713
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
714
+ type: Directive,
715
+ args: [{ selector: '[axUploaderBrowseHandle]' }]
716
+ }] });
717
+
718
+ /**
719
+ * Generated bundle index. Do not edit.
720
+ */
721
+
722
+ export { AXUploadRequest, AXUploaderBrowseDirective, AXUploaderService, AXUploaderZoneDirective };
723
+ //# sourceMappingURL=acorex-cdk-uploader.mjs.map