@acorex/cdk 21.0.2-next.23 → 21.0.2-next.25

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 (73) hide show
  1. package/fesm2022/acorex-cdk-accordion.mjs +24 -24
  2. package/fesm2022/acorex-cdk-accordion.mjs.map +1 -1
  3. package/fesm2022/acorex-cdk-carousel.mjs +3 -3
  4. package/fesm2022/acorex-cdk-carousel.mjs.map +1 -1
  5. package/fesm2022/acorex-cdk-clipboard.mjs +8 -8
  6. package/fesm2022/acorex-cdk-clipboard.mjs.map +1 -1
  7. package/fesm2022/acorex-cdk-common.mjs +161 -106
  8. package/fesm2022/acorex-cdk-common.mjs.map +1 -1
  9. package/fesm2022/acorex-cdk-dom.mjs +4 -4
  10. package/fesm2022/acorex-cdk-dom.mjs.map +1 -1
  11. package/fesm2022/acorex-cdk-double-click.mjs +4 -4
  12. package/fesm2022/acorex-cdk-double-click.mjs.map +1 -1
  13. package/fesm2022/acorex-cdk-drag-drop.mjs +22 -22
  14. package/fesm2022/acorex-cdk-drag-drop.mjs.map +1 -1
  15. package/fesm2022/acorex-cdk-drawer.mjs +13 -13
  16. package/fesm2022/acorex-cdk-drawer.mjs.map +1 -1
  17. package/fesm2022/acorex-cdk-focus-trap.mjs +3 -3
  18. package/fesm2022/acorex-cdk-focus-trap.mjs.map +1 -1
  19. package/fesm2022/acorex-cdk-full-screen.mjs +4 -4
  20. package/fesm2022/acorex-cdk-full-screen.mjs.map +1 -1
  21. package/fesm2022/acorex-cdk-input-mask.mjs +11 -5
  22. package/fesm2022/acorex-cdk-input-mask.mjs.map +1 -1
  23. package/fesm2022/acorex-cdk-list-navigation.mjs +13 -13
  24. package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
  25. package/fesm2022/acorex-cdk-outline.mjs +68 -57
  26. package/fesm2022/acorex-cdk-outline.mjs.map +1 -1
  27. package/fesm2022/acorex-cdk-overlay.mjs +16 -3
  28. package/fesm2022/acorex-cdk-overlay.mjs.map +1 -1
  29. package/fesm2022/acorex-cdk-pan-view.mjs +4 -4
  30. package/fesm2022/acorex-cdk-pan-view.mjs.map +1 -1
  31. package/fesm2022/acorex-cdk-qrcode.mjs.map +1 -1
  32. package/fesm2022/acorex-cdk-resizable.mjs +4 -4
  33. package/fesm2022/acorex-cdk-resizable.mjs.map +1 -1
  34. package/fesm2022/acorex-cdk-selection.mjs +13 -13
  35. package/fesm2022/acorex-cdk-selection.mjs.map +1 -1
  36. package/fesm2022/acorex-cdk-sliding-item.mjs +3 -3
  37. package/fesm2022/acorex-cdk-sliding-item.mjs.map +1 -1
  38. package/fesm2022/acorex-cdk-sticky.mjs +3 -3
  39. package/fesm2022/acorex-cdk-sticky.mjs.map +1 -1
  40. package/fesm2022/acorex-cdk-uploader.mjs +171 -277
  41. package/fesm2022/acorex-cdk-uploader.mjs.map +1 -1
  42. package/fesm2022/acorex-cdk-virtual-scroll.mjs +11 -11
  43. package/fesm2022/acorex-cdk-virtual-scroll.mjs.map +1 -1
  44. package/fesm2022/acorex-cdk-wysiwyg.mjs.map +1 -1
  45. package/fesm2022/acorex-cdk-z-index.mjs +3 -3
  46. package/fesm2022/acorex-cdk-z-index.mjs.map +1 -1
  47. package/fesm2022/acorex-cdk.mjs.map +1 -1
  48. package/package.json +31 -31
  49. package/{common/index.d.ts → types/acorex-cdk-common.d.ts} +9 -7
  50. package/{focus-trap/index.d.ts → types/acorex-cdk-focus-trap.d.ts} +1 -1
  51. package/{input-mask/index.d.ts → types/acorex-cdk-input-mask.d.ts} +1 -0
  52. package/{outline/index.d.ts → types/acorex-cdk-outline.d.ts} +1 -0
  53. package/{overlay/index.d.ts → types/acorex-cdk-overlay.d.ts} +1 -0
  54. package/{uploader/index.d.ts → types/acorex-cdk-uploader.d.ts} +110 -119
  55. /package/{accordion/index.d.ts → types/acorex-cdk-accordion.d.ts} +0 -0
  56. /package/{carousel/index.d.ts → types/acorex-cdk-carousel.d.ts} +0 -0
  57. /package/{clipboard/index.d.ts → types/acorex-cdk-clipboard.d.ts} +0 -0
  58. /package/{dom/index.d.ts → types/acorex-cdk-dom.d.ts} +0 -0
  59. /package/{double-click/index.d.ts → types/acorex-cdk-double-click.d.ts} +0 -0
  60. /package/{drag-drop/index.d.ts → types/acorex-cdk-drag-drop.d.ts} +0 -0
  61. /package/{drawer/index.d.ts → types/acorex-cdk-drawer.d.ts} +0 -0
  62. /package/{full-screen/index.d.ts → types/acorex-cdk-full-screen.d.ts} +0 -0
  63. /package/{list-navigation/index.d.ts → types/acorex-cdk-list-navigation.d.ts} +0 -0
  64. /package/{pan-view/index.d.ts → types/acorex-cdk-pan-view.d.ts} +0 -0
  65. /package/{qrcode/index.d.ts → types/acorex-cdk-qrcode.d.ts} +0 -0
  66. /package/{resizable/index.d.ts → types/acorex-cdk-resizable.d.ts} +0 -0
  67. /package/{selection/index.d.ts → types/acorex-cdk-selection.d.ts} +0 -0
  68. /package/{sliding-item/index.d.ts → types/acorex-cdk-sliding-item.d.ts} +0 -0
  69. /package/{sticky/index.d.ts → types/acorex-cdk-sticky.d.ts} +0 -0
  70. /package/{virtual-scroll/index.d.ts → types/acorex-cdk-virtual-scroll.d.ts} +0 -0
  71. /package/{wysiwyg/index.d.ts → types/acorex-cdk-wysiwyg.d.ts} +0 -0
  72. /package/{z-index/index.d.ts → types/acorex-cdk-z-index.d.ts} +0 -0
  73. /package/{index.d.ts → types/acorex-cdk.d.ts} +0 -0
@@ -7,27 +7,6 @@ import { AXFileService } from '@acorex/core/file';
7
7
  import { sumBy } from 'lodash-es';
8
8
  import { Subject, BehaviorSubject, map } from 'rxjs';
9
9
 
10
- /** DOM marker so browse handles can find a zone across content projection. */
11
- const AX_UPLOADER_ZONE_HOST = Symbol('AX_UPLOADER_ZONE_HOST');
12
- function bindUploaderZoneHost(element, zone) {
13
- element[AX_UPLOADER_ZONE_HOST] = zone;
14
- }
15
- function unbindUploaderZoneHost(element) {
16
- delete element[AX_UPLOADER_ZONE_HOST];
17
- }
18
- /** Walk ancestors to resolve the zone (works with projected browse handles). */
19
- function findUploaderZoneFromDom(start) {
20
- let el = start;
21
- while (el) {
22
- const zone = el[AX_UPLOADER_ZONE_HOST];
23
- if (zone) {
24
- return zone;
25
- }
26
- el = el.parentElement;
27
- }
28
- return undefined;
29
- }
30
-
31
10
  class AXUploadRequest {
32
11
  get name() {
33
12
  return this.file.name;
@@ -127,165 +106,76 @@ class AXUploadRequest {
127
106
  }
128
107
 
129
108
  /**
130
- * File upload queue and validation via {@link AXFileService}.
131
- * Storage is implemented by subscribing to upload/resolve/delete events.
109
+ * Service for managing file uploads with drag-and-drop support, progress tracking, and dialog management.
132
110
  * @category Services
133
111
  */
134
112
  class AXUploaderService {
135
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
+ */
136
123
  this.fileService = inject(AXFileService);
124
+ /**
125
+ * Behavior subject for managing upload requests.
126
+ * @ignore
127
+ */
137
128
  this._files$ = new BehaviorSubject([]);
129
+ /**
130
+ * Gets the files behavior subject for observing upload requests.
131
+ */
138
132
  this.files = this._files$.asObservable();
133
+ /**
134
+ * Subject for file upload start events.
135
+ */
139
136
  this.onFileUploadStart = new Subject();
137
+ /**
138
+ * Subject for file upload complete events.
139
+ */
140
140
  this.onFileUploadComplete = new Subject();
141
+ /**
142
+ * Subject for all files upload complete events.
143
+ */
141
144
  this.onFilesUploadComplete = new Subject();
145
+ /**
146
+ * Subject for file upload canceled events.
147
+ */
142
148
  this.onFileUploadCanceled = new Subject();
143
- /** Subscribe to perform uploads (e.g. HTTP, IndexedDB). */
144
- this.onUpload = new Subject();
145
- /** Subscribe to resolve a stored reference to a playback URL. */
146
- this.onResolveUrl = new Subject();
147
- /** Subscribe to delete stored media. */
148
- this.onDeleteMedia = 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
+ */
149
156
  this.totalEstimateTime = this._files$.pipe(map((files) => sumBy(files, (file) => (file.status() === 'inprogress' ? file.estimateTime() : 0))));
150
157
  }
151
- isAnyDetermined() {
152
- return this._files$.value.some((file) => file.isDetermined());
153
- }
154
- validateFiles(files, fileType) {
155
- return this.fileService.validateMany(files, fileType);
156
- }
157
- async getAcceptAttribute(fileType) {
158
- return this.fileService.getAcceptAttribute(fileType);
159
- }
160
- upload(options) {
161
- return new Promise((resolve, reject) => {
162
- if (!this.hasUploadHandler()) {
163
- reject(new Error('No upload handler subscribed to AXUploaderService.onUpload. Provide a service that handles upload events.'));
164
- return;
165
- }
166
- this.onUpload.next({
167
- component: this,
168
- options,
169
- resolve,
170
- reject,
171
- isUserInteraction: false,
172
- });
173
- });
174
- }
175
- resolveUrl(reference, signal) {
176
- return new Promise((resolve, reject) => {
177
- if (!this.hasResolveUrlHandler()) {
178
- reject(new Error('No handler subscribed to AXUploaderService.onResolveUrl. Provide a service that handles resolve events.'));
179
- return;
180
- }
181
- this.onResolveUrl.next({
182
- component: this,
183
- reference,
184
- signal,
185
- resolve,
186
- reject,
187
- isUserInteraction: false,
188
- });
189
- });
190
- }
191
- async resolvePlaybackUrl(reference, signal) {
192
- const direct = reference.url?.trim();
193
- if (direct && !reference.mediaId) {
194
- return direct;
195
- }
196
- if (direct && reference.mediaId) {
197
- try {
198
- return await this.resolveUrl(reference, signal);
199
- }
200
- catch {
201
- return direct;
202
- }
203
- }
204
- if (reference.mediaId) {
205
- return this.resolveUrl(reference, signal);
206
- }
207
- if (direct) {
208
- return direct;
209
- }
210
- throw new Error('Upload reference has no url or mediaId');
211
- }
212
- deleteMedia(reference) {
213
- return new Promise((resolve, reject) => {
214
- if (!this.hasDeleteMediaHandler()) {
215
- reject(new Error('No handler subscribed to AXUploaderService.onDeleteMedia. Provide a service that handles delete events.'));
216
- return;
217
- }
218
- this.onDeleteMedia.next({
219
- component: this,
220
- reference,
221
- resolve,
222
- reject,
223
- isUserInteraction: false,
224
- });
225
- });
226
- }
227
- hasUploadHandler() {
228
- return this.onUpload.observers.length > 0;
229
- }
230
- hasResolveUrlHandler() {
231
- return this.onResolveUrl.observers.length > 0;
232
- }
233
- hasDeleteMediaHandler() {
234
- return this.onDeleteMedia.observers.length > 0;
235
- }
236
- async add(files, options) {
237
- const list = Array.from(files).map((f) => new AXUploadRequest(f));
238
- await this.applyFileTypeValidation(list, options?.fileType);
239
- const newFiles = [...this._files$.value, ...list];
240
- this._files$.next(newFiles);
241
- void this.startUpload();
242
- return list;
243
- }
244
158
  /**
245
- * Opens the file dialog and returns validated files without enqueueing uploads.
159
+ * Converts a File object to an AXUploadRequest.
160
+ * @private
246
161
  */
247
- async chooseFiles(options = {}) {
248
- try {
249
- if (!options.fileType) {
250
- console.warn('AXUploaderService.chooseFiles: fileType is required for validated file selection.');
251
- return { accepted: [], rejected: [] };
252
- }
253
- const accept = options.accept ?? (await this.fileService.getAcceptAttribute(options.fileType));
254
- return this.fileService.chooseValidated({
255
- fileType: options.fileType,
256
- multiple: options.multiple ?? false,
257
- accept,
258
- });
259
- }
260
- catch (error) {
261
- console.error('File choose failed:', error);
262
- return { accepted: [], rejected: [] };
263
- }
264
- }
265
- async browse(options = { multiple: false }) {
266
- const { accepted } = await this.chooseFiles(options);
267
- if (!accepted.length) {
268
- return [];
269
- }
270
- return this.add(accepted, { fileType: options.fileType });
271
- }
272
- async cancelAll() {
273
- await Promise.all(this._files$.value.filter((c) => c.status() !== 'completed').map((c) => c.cancel()));
274
- }
275
- clearAll() {
276
- const remainingFiles = this._files$.value.filter((c) => c.status() === 'inprogress');
277
- this._files$.next(remainingFiles);
278
- }
279
- remove(item) {
280
- const updatedFiles = this._files$.value.filter((c) => c !== item);
281
- this._files$.next(updatedFiles);
162
+ convertFileToRequest(file) {
163
+ return new AXUploadRequest(file);
282
164
  }
165
+ /**
166
+ * Starts uploading files that are in 'new' status.
167
+ * @private
168
+ */
283
169
  async startUpload() {
284
170
  const newFiles = this._files$.value.filter((c) => c.status() === 'new');
285
171
  for (const file of newFiles) {
286
172
  await this.bindEvents(file);
287
173
  }
288
174
  }
175
+ /**
176
+ * Binds event handlers to an upload request.
177
+ * @private
178
+ */
289
179
  async bindEvents(c) {
290
180
  c.onStart.subscribe(() => {
291
181
  this.onFileUploadStart.next({
@@ -318,22 +208,62 @@ class AXUploaderService {
318
208
  });
319
209
  });
320
210
  }
321
- async applyFileTypeValidation(requests, fileType) {
322
- if (!fileType) {
323
- return requests;
324
- }
325
- for (const request of requests) {
326
- const errors = await this.fileService.validate(request.file, fileType);
327
- if (errors.length > 0) {
328
- request.error(errors[0]?.message ?? 'Invalid file');
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);
329
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 [];
330
228
  }
331
- return requests;
332
229
  }
333
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
334
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
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: "21.1.3", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
264
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
335
265
  }
336
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, decorators: [{
266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderService, decorators: [{
337
267
  type: Injectable,
338
268
  args: [{ providedIn: 'root' }]
339
269
  }] });
@@ -368,11 +298,6 @@ class AXUploaderZoneDirective {
368
298
  * @defaultValue null
369
299
  */
370
300
  this.accept = input(null, ...(ngDevMode ? [{ debugName: "accept" }] : []));
371
- /**
372
- * Logical file type name from {@link AXFileService} (e.g. `conversation-image`).
373
- * Required for browse handles and validated selection.
374
- */
375
- this.fileType = input(null, ...(ngDevMode ? [{ debugName: "fileType" }] : []));
376
301
  /**
377
302
  * Custom template for the drag overlay. If provided, this will be used instead of the default overlay.
378
303
  */
@@ -480,7 +405,6 @@ class AXUploaderZoneDirective {
480
405
  this.animationEndHandler = null;
481
406
  this.element = this.elementRef.nativeElement;
482
407
  this.element.style.position = 'relative';
483
- bindUploaderZoneHost(this.element, this);
484
408
  //
485
409
  this.uploadService.onFileUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
486
410
  this.onFileUploadComplete.emit(c);
@@ -506,7 +430,6 @@ class AXUploaderZoneDirective {
506
430
  * Cleans up event listeners when the directive is destroyed.
507
431
  */
508
432
  ngOnDestroy() {
509
- unbindUploaderZoneHost(this.element);
510
433
  this.element.removeEventListener('click', this.browser.bind(this));
511
434
  this.element.removeEventListener('dragenter', this.handleDragEnter.bind(this));
512
435
  this.element.removeEventListener('drop', this.handleOnDrop.bind(this));
@@ -544,25 +467,16 @@ class AXUploaderZoneDirective {
544
467
  // Reset drag depth on drop
545
468
  this.dragDepth.set(0);
546
469
  if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
547
- let files = Array.from(event.dataTransfer.files);
548
- let rejected;
549
- const catalog = this.fileType();
550
- if (catalog) {
551
- const result = await this.uploadService.validateFiles(files, catalog);
552
- files = result.accepted;
553
- rejected = result.rejected;
554
- }
555
- this.fileChange.emit({ event, files, rejected });
556
- if (!this.disableBrowse()) {
557
- const requests = await this.uploadService.add(files, { fileType: catalog ?? undefined });
558
- if (requests.length > 0) {
559
- this.onChanged.emit({
560
- component: this,
561
- requests,
562
- isUserInteraction: true,
563
- });
564
- }
565
- }
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
+ });
566
480
  }
567
481
  this.removeZone();
568
482
  this.cdr.detectChanges();
@@ -726,60 +640,32 @@ class AXUploaderZoneDirective {
726
640
  * @returns Promise that resolves when files are processed
727
641
  */
728
642
  async browser() {
729
- const catalog = this.fileType();
730
- let selected = [];
731
- let rejected;
732
- if (catalog) {
733
- const accept = this.accept() ?? (await this.uploadService.getAcceptAttribute(catalog));
734
- const result = await this.uploadService.chooseFiles({
735
- fileType: catalog,
736
- multiple: this.multiple(),
737
- accept,
738
- });
739
- selected = result.accepted;
740
- rejected = result.rejected;
741
- }
742
- else if (!this.disableBrowse()) {
743
- const requests = await this.uploadService.browse({
744
- accept: this.accept() ?? undefined,
745
- multiple: this.multiple(),
746
- });
747
- selected = requests.map((r) => r.file);
748
- }
749
- else {
750
- console.warn('AXUploaderZone.browser: fileType is required when disableBrowse is enabled (use axUploaderBrowseHandle).');
751
- return;
752
- }
753
- if (selected.length === 0 && !(rejected?.length ?? 0)) {
643
+ if (this.disableBrowse())
754
644
  return;
755
- }
756
- const syntheticEvent = new Event('change');
757
- const target = Object.create(EventTarget.prototype);
758
- target.files = selected;
759
- Object.defineProperty(syntheticEvent, 'target', { value: target, writable: false });
760
- this.fileChange.emit({
761
- event: syntheticEvent,
762
- files: selected,
763
- rejected,
764
- });
765
- if (this.disableBrowse()) {
766
- return;
767
- }
768
- const requests = catalog
769
- ? await this.uploadService.add(selected, { fileType: catalog })
770
- : await this.uploadService.add(selected);
771
- if (requests.length > 0) {
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
772
658
  this.onChanged.emit({
773
659
  component: this,
774
- requests,
660
+ requests: files,
775
661
  isUserInteraction: true,
776
662
  });
777
663
  }
778
664
  }
779
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
780
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.3", 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 }, fileType: { classPropertyName: "fileType", publicName: "fileType", 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 }); }
665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
666
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", 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 }); }
781
667
  }
782
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
668
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
783
669
  type: Directive,
784
670
  args: [{
785
671
  selector: '[axUploaderZone]',
@@ -788,58 +674,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
788
674
  class: 'ax-drop-zone',
789
675
  },
790
676
  }]
791
- }], ctorParameters: () => [] });
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"] }] } });
792
678
 
793
679
  /**
794
- * Triggers the nearest {@link AXUploaderZoneDirective} file dialog on click.
795
- * Resolves the zone via DI when possible, otherwise walks the DOM (supports content projection).
680
+ * A directive that provides browse functionality for file uploads.
681
+ * When applied to an element, clicking it will trigger the file browser dialog.
796
682
  * @category Directives
797
683
  */
798
684
  class AXUploaderBrowseDirective {
799
685
  constructor() {
800
- this.elementRef = inject(ElementRef);
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
+ */
801
700
  this.platformID = inject(PLATFORM_ID);
802
- /** When browse + zone share the same host element. */
803
- this.zoneFromInjector = inject(AXUploaderZoneDirective, { optional: true, self: true });
804
- this.onClick = () => {
805
- void this.handleClick();
806
- };
807
701
  }
702
+ /**
703
+ * Initializes the directive by adding click event listener and data attribute.
704
+ */
808
705
  ngOnInit() {
809
- if (!isPlatformBrowser(this.platformID)) {
810
- return;
811
- }
812
- const element = this.elementRef.nativeElement;
813
- element.addEventListener('click', this.onClick);
814
- if (element.dataset) {
815
- element.dataset['axUploaderBrowseHandle'] = 'true';
816
- }
817
- else {
818
- element.setAttribute('data-ax-uploader-browse-handle', 'true');
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
+ }
819
716
  }
820
717
  }
718
+ /**
719
+ * Cleans up the directive by removing event listeners.
720
+ */
821
721
  ngOnDestroy() {
822
- if (isPlatformBrowser(this.platformID)) {
823
- this.elementRef.nativeElement.removeEventListener('click', this.onClick);
722
+ if (isPlatformBrowser(this.platformID) && this.elementRef.nativeElement) {
723
+ this.elementRef.nativeElement.removeEventListener('click', this.handleClick.bind(this));
824
724
  }
825
725
  }
826
- resolveZone() {
827
- return (this.zoneFromInjector ??
828
- findUploaderZoneFromDom(this.elementRef.nativeElement) ??
829
- undefined);
830
- }
726
+ /**
727
+ * Handles the click event to trigger file browser.
728
+ * @private
729
+ */
831
730
  async handleClick() {
832
- const zone = this.resolveZone();
833
- if (!zone) {
834
- console.warn('[axUploaderBrowseHandle] No axUploaderZone found. Place axUploaderBrowseHandle inside an axUploaderZone.');
835
- return;
836
- }
837
- await zone.browser();
731
+ await this.uploaderZone.browser();
838
732
  }
839
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
840
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.3", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
733
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
734
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
841
735
  }
842
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
843
737
  type: Directive,
844
738
  args: [{ selector: '[axUploaderBrowseHandle]' }]
845
739
  }] });