@quadrel-enterprise-ui/framework 20.6.0 → 20.6.1

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.
@@ -3,8 +3,8 @@ import { inject, ElementRef, Directive, InjectionToken, HostBinding, Input, View
3
3
  import { Dialog, DialogRef, DialogModule } from '@angular/cdk/dialog';
4
4
  import * as i1 from '@angular/common';
5
5
  import { CommonModule, NgFor, NgIf, NgClass, NgTemplateOutlet, AsyncPipe } from '@angular/common';
6
- import { BehaviorSubject, pairwise, from, switchMap, map as map$1, Subject, throwError, of, ReplaySubject, merge, fromEvent, isObservable, NEVER, Observable, EMPTY, shareReplay, Subscription, distinctUntilChanged as distinctUntilChanged$1, debounce, timer, startWith as startWith$1, debounceTime as debounceTime$1, takeUntil as takeUntil$1, firstValueFrom, combineLatest, concat, take as take$1, delay, tap as tap$1, first, scan, combineLatestWith, forkJoin, delayWhen, withLatestFrom, async, filter as filter$1 } from 'rxjs';
7
- import { map, takeUntil, take, filter, catchError, debounceTime, startWith, distinctUntilChanged, concatMap, tap, skip, pairwise as pairwise$1, switchMap as switchMap$1, mergeMap, delay as delay$1 } from 'rxjs/operators';
6
+ import { BehaviorSubject, pairwise, from, switchMap, map as map$1, Subject, throwError, of, ReplaySubject, merge, fromEvent, isObservable, NEVER, Observable, EMPTY, shareReplay, Subscription, distinctUntilChanged as distinctUntilChanged$1, debounce, timer, startWith as startWith$1, debounceTime as debounceTime$1, takeUntil as takeUntil$1, firstValueFrom, combineLatest, concat, take as take$1, delay, tap as tap$1, first, scan, queueScheduler, combineLatestWith, forkJoin, delayWhen, withLatestFrom, async, filter as filter$1 } from 'rxjs';
7
+ import { map, takeUntil, take, filter, catchError, debounceTime, startWith, distinctUntilChanged, concatMap, tap, skip, observeOn, switchMap as switchMap$1, pairwise as pairwise$1, mergeMap, delay as delay$1 } from 'rxjs/operators';
8
8
  import { v4 } from 'uuid';
9
9
  import * as i3 from '@ngx-translate/core';
10
10
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
@@ -2162,15 +2162,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2162
2162
  }]
2163
2163
  }] });
2164
2164
 
2165
- // @ts-strict-ignore
2166
2165
  class QdMockFileCollectorItemComponent {
2167
2166
  progress;
2168
- newlyUploaded;
2167
+ newlyUploaded = false;
2169
2168
  collectedFile;
2170
2169
  error;
2171
- readonly;
2172
- viewonly;
2173
- downloadFilesWithHttpClient;
2170
+ readonly = false;
2171
+ viewonly = false;
2172
+ downloadFilesWithHttpClient = false;
2174
2173
  uploadTimestamp;
2175
2174
  testId;
2176
2175
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2204,11 +2203,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2204
2203
  args: ['data-test-id']
2205
2204
  }] } });
2206
2205
 
2207
- // @ts-strict-ignore
2208
2206
  class QdMockFileCollectorItemNameComponent {
2209
2207
  name;
2208
+ testId;
2210
2209
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemNameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2211
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemNameComponent, isStandalone: false, selector: "qd-file-collector-item-name", inputs: { name: "name" }, ngImport: i0, template: 'name: {{ name }}', isInline: true });
2210
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemNameComponent, isStandalone: false, selector: "qd-file-collector-item-name", inputs: { name: "name", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'name: {{ name }}', isInline: true });
2212
2211
  }
2213
2212
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemNameComponent, decorators: [{
2214
2213
  type: Component,
@@ -2219,13 +2218,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2219
2218
  }]
2220
2219
  }], propDecorators: { name: [{
2221
2220
  type: Input
2221
+ }], testId: [{
2222
+ type: Input,
2223
+ args: ['data-test-id']
2222
2224
  }] } });
2223
2225
 
2224
- // @ts-strict-ignore
2225
2226
  class QdMockFileCollectorItemSizeComponent {
2226
2227
  size;
2228
+ testId;
2227
2229
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemSizeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2228
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemSizeComponent, isStandalone: false, selector: "qd-file-collector-item-size", inputs: { size: "size" }, ngImport: i0, template: 'size: {{ size }}', isInline: true });
2230
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemSizeComponent, isStandalone: false, selector: "qd-file-collector-item-size", inputs: { size: "size", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'size: {{ size }}', isInline: true });
2229
2231
  }
2230
2232
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemSizeComponent, decorators: [{
2231
2233
  type: Component,
@@ -2236,18 +2238,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2236
2238
  }]
2237
2239
  }], propDecorators: { size: [{
2238
2240
  type: Input
2241
+ }], testId: [{
2242
+ type: Input,
2243
+ args: ['data-test-id']
2239
2244
  }] } });
2240
2245
 
2241
- // @ts-strict-ignore
2242
2246
  class QdMockFileCollectorItemToolsComponent {
2243
2247
  progress;
2244
2248
  collectedFile;
2245
2249
  error;
2246
- readonly;
2247
- viewonly;
2248
- downloadFilesWithHttpClient;
2250
+ readonly = false;
2251
+ viewonly = false;
2252
+ downloadFilesWithHttpClient = false;
2253
+ testId;
2249
2254
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemToolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2250
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemToolsComponent, isStandalone: false, selector: "qd-file-collector-item-tools", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", readonly: "readonly", viewonly: "viewonly", downloadFilesWithHttpClient: "downloadFilesWithHttpClient" }, ngImport: i0, template: 'progress: {{ progress }}, collectedFile: {{ collectedFile | json }}, error: {{ error | json }}, readonly: {{ readonly ? "true" : "false" }}, viewonly: {{ viewonly ? "true" : "false" }}, downloadFilesWithHttpClient: {{ downloadFilesWithHttpClient ? "true" : "false" }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2255
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorItemToolsComponent, isStandalone: false, selector: "qd-file-collector-item-tools", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", readonly: "readonly", viewonly: "viewonly", downloadFilesWithHttpClient: "downloadFilesWithHttpClient", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'progress: {{ progress }}, collectedFile: {{ collectedFile | json }}, error: {{ error | json }}, readonly: {{ readonly ? "true" : "false" }}, viewonly: {{ viewonly ? "true" : "false" }}, downloadFilesWithHttpClient: {{ downloadFilesWithHttpClient ? "true" : "false" }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2251
2256
  }
2252
2257
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorItemToolsComponent, decorators: [{
2253
2258
  type: Component,
@@ -2268,15 +2273,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2268
2273
  type: Input
2269
2274
  }], downloadFilesWithHttpClient: [{
2270
2275
  type: Input
2276
+ }], testId: [{
2277
+ type: Input,
2278
+ args: ['data-test-id']
2271
2279
  }] } });
2272
2280
 
2273
- // @ts-strict-ignore
2274
2281
  class QdMockFileCollectorDialogItemComponent {
2275
2282
  progress;
2276
2283
  collectedFile;
2277
2284
  error;
2285
+ testId;
2278
2286
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2279
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemComponent, isStandalone: false, selector: "qd-file-collector-dialog-item", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error" }, ngImport: i0, template: 'progress: {{ progress }}, collectedFile: {{ collectedFile | json }}, error: {{ error | json }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2287
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemComponent, isStandalone: false, selector: "qd-file-collector-dialog-item", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'progress: {{ progress }}, collectedFile: {{ collectedFile | json }}, error: {{ error | json }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2280
2288
  }
2281
2289
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemComponent, decorators: [{
2282
2290
  type: Component,
@@ -2291,13 +2299,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2291
2299
  type: Input
2292
2300
  }], error: [{
2293
2301
  type: Input
2302
+ }], testId: [{
2303
+ type: Input,
2304
+ args: ['data-test-id']
2294
2305
  }] } });
2295
2306
 
2296
- // @ts-strict-ignore
2297
2307
  class QdMockFileCollectorDialogItemErrorComponent {
2298
2308
  error;
2309
+ testId;
2299
2310
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2300
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemErrorComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-error", inputs: { error: "error" }, ngImport: i0, template: 'error: {{ error | json }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2311
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemErrorComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-error", inputs: { error: "error", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'error: {{ error | json }}', isInline: true, dependencies: [{ kind: "pipe", type: i1.JsonPipe, name: "json" }] });
2301
2312
  }
2302
2313
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemErrorComponent, decorators: [{
2303
2314
  type: Component,
@@ -2308,14 +2319,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2308
2319
  }]
2309
2320
  }], propDecorators: { error: [{
2310
2321
  type: Input
2322
+ }], testId: [{
2323
+ type: Input,
2324
+ args: ['data-test-id']
2311
2325
  }] } });
2312
2326
 
2313
- // @ts-strict-ignore
2314
2327
  class QdMockFileCollectorDialogItemProgressComponent {
2315
- progress;
2328
+ progress = 0;
2316
2329
  error;
2330
+ testId;
2317
2331
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2318
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemProgressComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-progress", inputs: { progress: "progress", error: "error" }, ngImport: i0, template: 'progress: {{ progress }}', isInline: true });
2332
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdMockFileCollectorDialogItemProgressComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-progress", inputs: { progress: "progress", error: "error", testId: ["data-test-id", "testId"] }, ngImport: i0, template: 'progress: {{ progress }}', isInline: true });
2319
2333
  }
2320
2334
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdMockFileCollectorDialogItemProgressComponent, decorators: [{
2321
2335
  type: Component,
@@ -2328,6 +2342,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2328
2342
  type: Input
2329
2343
  }], error: [{
2330
2344
  type: Input
2345
+ }], testId: [{
2346
+ type: Input,
2347
+ args: ['data-test-id']
2331
2348
  }] } });
2332
2349
 
2333
2350
  class QdMockFileCollectorModule {
@@ -17345,7 +17362,13 @@ class QdPageStoreService {
17345
17362
  }
17346
17363
  initPageState(config) {
17347
17364
  this.config = config;
17348
- this.config?.pageType === 'inspect' ? this.toggleViewonly(true) : this.toggleViewonly(false);
17365
+ if (this.config?.pageType === 'inspect') {
17366
+ const operationMode = this.config.pageTypeConfig?.operationMode;
17367
+ this.toggleViewonly(operationMode !== 'edit');
17368
+ }
17369
+ else {
17370
+ this.toggleViewonly(false);
17371
+ }
17349
17372
  }
17350
17373
  getConfig() {
17351
17374
  return this.config;
@@ -18305,7 +18328,6 @@ var QdUploadErrorType;
18305
18328
  QdUploadErrorType["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
18306
18329
  })(QdUploadErrorType || (QdUploadErrorType = {}));
18307
18330
 
18308
- // @ts-strict-ignore
18309
18331
  class QdFileCollectorValidationService {
18310
18332
  _config;
18311
18333
  get allowedMimeTypes() {
@@ -18342,7 +18364,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18342
18364
  type: Injectable
18343
18365
  }] });
18344
18366
 
18345
- // @ts-strict-ignore
18346
18367
  class QdFileCollectorService {
18347
18368
  fileManager = inject(QD_FILE_MANAGER_TOKEN);
18348
18369
  fileCollectorValidationService = inject(QdFileCollectorValidationService);
@@ -18386,8 +18407,8 @@ class QdFileCollectorService {
18386
18407
  }
18387
18408
  if (uploadProgress?.error) {
18388
18409
  fileUpload.error = {
18389
- type: QdUploadErrorType.UNKNOWN_SERVER_ERROR,
18390
- ...uploadProgress.error
18410
+ ...uploadProgress.error,
18411
+ type: uploadProgress.error.type ?? QdUploadErrorType.UNKNOWN_SERVER_ERROR
18391
18412
  };
18392
18413
  }
18393
18414
  this.changeDetectorRef.detectChanges();
@@ -18403,7 +18424,10 @@ class QdFileCollectorService {
18403
18424
  this.fileManager.delete(collectedFile).subscribe(deleted => {
18404
18425
  if (!deleted)
18405
18426
  return;
18406
- this.fileUploads.splice(this.fileUploads.findIndex(fileUpload => fileUpload.collectedFile === collectedFile), 1);
18427
+ const index = this.fileUploads.findIndex(fileUpload => fileUpload.collectedFile === collectedFile);
18428
+ if (index === -1)
18429
+ return;
18430
+ this.fileUploads.splice(index, 1);
18407
18431
  this.changeDetectorRef.detectChanges();
18408
18432
  });
18409
18433
  }
@@ -18452,7 +18476,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18452
18476
  }]
18453
18477
  }] });
18454
18478
 
18455
- // @ts-strict-ignore
18456
18479
  class QdFileCollectorAllowedFilesDescriptionComponent {
18457
18480
  fileCollectorService = inject(QdFileCollectorService);
18458
18481
  get allowedFileTypes() {
@@ -18487,10 +18510,13 @@ var QdUploadProgressState;
18487
18510
  QdUploadProgressState["ERROR"] = "error";
18488
18511
  })(QdUploadProgressState || (QdUploadProgressState = {}));
18489
18512
 
18490
- // @ts-strict-ignore
18491
18513
  class QdFileCollectorDialogItemProgressComponent {
18492
18514
  progress = 0;
18493
18515
  error;
18516
+ testId;
18517
+ get hostTestId() {
18518
+ return this.testId;
18519
+ }
18494
18520
  _uploadStateSubject = new BehaviorSubject(QdUploadProgressState.STARTING);
18495
18521
  _destroyed$ = new Subject();
18496
18522
  uploadState$ = this._uploadStateSubject.asObservable();
@@ -18533,7 +18559,7 @@ class QdFileCollectorDialogItemProgressComponent {
18533
18559
  }
18534
18560
  QdUploadState = QdUploadProgressState;
18535
18561
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemProgressComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-progress", inputs: { progress: "progress", error: "error" }, host: { classAttribute: "qd-file-collector-dialog-item-progress" }, usesOnChanges: true, ngImport: i0, template: "<ng-container *ngIf=\"uploadState$ | async as uploadState\">\n <span\n [class]=\"'upload-progress-bar-container'\"\n *ngIf=\"\n [QdUploadState.STARTING, QdUploadState.IN_PROGRESS, QdUploadState.FINISHING].includes(uploadState);\n else completed\n \"\n >\n <span\n [ngClass]=\"{\n 'upload-progress-bar-progress': true,\n filled: [QdUploadState.IN_PROGRESS, QdUploadState.FINISHING].includes(uploadState),\n success: QdUploadState.FINISHING === uploadState\n }\"\n [class]=\"'upload-progress-bar-progress'\"\n [ngStyle]=\"{ minWidth: progress + '%' }\"\n >\n {{ progress ? progress : 0 }}%\n </span>\n </span>\n <ng-template #completed>\n <span [class]=\"'upload-state-chip ' + uploadState\">\n {{ \"i18n.qd.fileCollector.uploadState.\" + uploadState | translate }}\n </span>\n </ng-template>\n</ng-container>\n", styles: [":host{position:relative;display:flex;margin-top:.0625rem}:host .upload-progress-bar-container{width:12.5rem}:host .upload-progress-bar-progress,:host .upload-state-chip{color:#e97e00;font-size:.625rem;font-weight:500;line-height:1rem;display:inline-block;padding-right:.375rem;padding-left:.5rem;border:solid .0625rem rgb(233,126,0);border-radius:.5rem;background:#fff;transition:min-width 1s}:host .upload-progress-bar-progress.filled,:host .upload-state-chip.filled{border-color:#e97e00;background:#e97e00;color:#fff}:host .upload-progress-bar-progress.success,:host .upload-state-chip.success{border-color:#00813a;background:#fff;color:#00813a}:host .upload-progress-bar-progress.success.filled,:host .upload-state-chip.success.filled{border-color:#00813a;background:#00813a;color:#fff}:host .upload-progress-bar-progress.error,:host .upload-state-chip.error{border-color:#c70023;background:#fff;color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18562
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemProgressComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-progress", inputs: { progress: "progress", error: "error", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.hostTestId" }, classAttribute: "qd-file-collector-dialog-item-progress" }, usesOnChanges: true, ngImport: i0, template: "<ng-container *ngIf=\"uploadState$ | async as uploadState\">\n <span\n [class]=\"'upload-progress-bar-container'\"\n *ngIf=\"\n [QdUploadState.STARTING, QdUploadState.IN_PROGRESS, QdUploadState.FINISHING].includes(uploadState);\n else completed\n \"\n >\n <span\n [ngClass]=\"{\n 'upload-progress-bar-progress': true,\n filled: [QdUploadState.IN_PROGRESS, QdUploadState.FINISHING].includes(uploadState),\n success: QdUploadState.FINISHING === uploadState\n }\"\n [class]=\"'upload-progress-bar-progress'\"\n [ngStyle]=\"{ minWidth: progress + '%' }\"\n >\n {{ progress ? progress : 0 }}%\n </span>\n </span>\n <ng-template #completed>\n <span [class]=\"'upload-state-chip ' + uploadState\">\n {{ \"i18n.qd.fileCollector.uploadState.\" + uploadState | translate }}\n </span>\n </ng-template>\n</ng-container>\n", styles: [":host{position:relative;display:flex;margin-top:.0625rem}:host .upload-progress-bar-container{width:12.5rem}:host .upload-progress-bar-progress,:host .upload-state-chip{color:#e97e00;font-size:.625rem;font-weight:500;line-height:1rem;display:inline-block;padding-right:.375rem;padding-left:.5rem;border:solid .0625rem rgb(233,126,0);border-radius:.5rem;background:#fff;transition:min-width 1s}:host .upload-progress-bar-progress.filled,:host .upload-state-chip.filled{border-color:#e97e00;background:#e97e00;color:#fff}:host .upload-progress-bar-progress.success,:host .upload-state-chip.success{border-color:#00813a;background:#fff;color:#00813a}:host .upload-progress-bar-progress.success.filled,:host .upload-state-chip.success.filled{border-color:#00813a;background:#00813a;color:#fff}:host .upload-progress-bar-progress.error,:host .upload-state-chip.error{border-color:#c70023;background:#fff;color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18537
18563
  }
18538
18564
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemProgressComponent, decorators: [{
18539
18565
  type: Component,
@@ -18542,12 +18568,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18542
18568
  type: Input
18543
18569
  }], error: [{
18544
18570
  type: Input
18571
+ }], testId: [{
18572
+ type: Input,
18573
+ args: [{ required: true, alias: 'data-test-id' }]
18574
+ }], hostTestId: [{
18575
+ type: HostBinding,
18576
+ args: ['attr.data-test-id']
18545
18577
  }] } });
18546
18578
 
18547
- // @ts-strict-ignore
18548
18579
  class QdFileCollectorDialogItemErrorComponent {
18549
18580
  fileCollectorService = inject(QdFileCollectorService);
18550
18581
  error;
18582
+ testId;
18583
+ get hostTestId() {
18584
+ return this.testId;
18585
+ }
18551
18586
  QdUploadErrorType = QdUploadErrorType;
18552
18587
  get maxFileSizeInBytes() {
18553
18588
  return this.config?.maxFileSizeInBytes;
@@ -18562,41 +18597,53 @@ class QdFileCollectorDialogItemErrorComponent {
18562
18597
  return this.fileCollectorService.config;
18563
18598
  }
18564
18599
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18565
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemErrorComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-error", inputs: { error: "error" }, ngImport: i0, template: "<span *ngIf=\"error.message; else errorMessageNotGiven\">{{ error.message }}</span>\n<ng-template #errorMessageNotGiven [ngSwitch]=\"error.type\">\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_MIME_TYPE\">{{\n invalidMimeTypeErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileType\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_SIZE\">{{\n \"i18n.qd.fileCollector.invalid.fileSize\"\n | translate\n | placeholder : \"maxFileSize\" : (maxFileSizeInBytes | filesize | async)\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_NAME\">{{\n invalidFileNameErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileName\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.UNKNOWN_ERROR\">{{ \"i18n.qd.fileCollector.invalid.unknown\" | translate }}</span>\n</ng-template>\n", styles: [":host{display:flex;width:100%;align-items:center}:host span{color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "pipe", type: QdPlaceholderPipe, name: "placeholder" }, { kind: "pipe", type: QdFileSizePipe, name: "filesize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18600
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemErrorComponent, isStandalone: false, selector: "qd-file-collector-dialog-item-error", inputs: { error: "error", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.hostTestId" } }, ngImport: i0, template: "<span *ngIf=\"error.message; else errorMessageNotGiven\">{{ error.message }}</span>\n<ng-template #errorMessageNotGiven [ngSwitch]=\"error.type\">\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_MIME_TYPE\">{{\n invalidMimeTypeErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileType\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_SIZE\">{{\n \"i18n.qd.fileCollector.invalid.fileSize\"\n | translate\n | placeholder : \"maxFileSize\" : (maxFileSizeInBytes | filesize | async)\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_NAME\">{{\n invalidFileNameErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileName\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.UNKNOWN_ERROR\">{{ \"i18n.qd.fileCollector.invalid.unknown\" | translate }}</span>\n</ng-template>\n", styles: [":host{display:flex;width:100%;align-items:center}:host span{color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "pipe", type: QdPlaceholderPipe, name: "placeholder" }, { kind: "pipe", type: QdFileSizePipe, name: "filesize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18566
18601
  }
18567
18602
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemErrorComponent, decorators: [{
18568
18603
  type: Component,
18569
18604
  args: [{ selector: 'qd-file-collector-dialog-item-error', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<span *ngIf=\"error.message; else errorMessageNotGiven\">{{ error.message }}</span>\n<ng-template #errorMessageNotGiven [ngSwitch]=\"error.type\">\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_MIME_TYPE\">{{\n invalidMimeTypeErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileType\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_SIZE\">{{\n \"i18n.qd.fileCollector.invalid.fileSize\"\n | translate\n | placeholder : \"maxFileSize\" : (maxFileSizeInBytes | filesize | async)\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.INVALID_FILE_NAME\">{{\n invalidFileNameErrorMessageI18n || \"i18n.qd.fileCollector.invalid.fileName\" | translate\n }}</span>\n <span *ngSwitchCase=\"QdUploadErrorType.UNKNOWN_ERROR\">{{ \"i18n.qd.fileCollector.invalid.unknown\" | translate }}</span>\n</ng-template>\n", styles: [":host{display:flex;width:100%;align-items:center}:host span{color:#c70023}\n"] }]
18570
18605
  }], propDecorators: { error: [{
18571
18606
  type: Input
18607
+ }], testId: [{
18608
+ type: Input,
18609
+ args: [{ required: true, alias: 'data-test-id' }]
18610
+ }], hostTestId: [{
18611
+ type: HostBinding,
18612
+ args: ['attr.data-test-id']
18572
18613
  }] } });
18573
18614
 
18574
- // @ts-strict-ignore
18575
18615
  class QdFileCollectorDialogItemComponent {
18576
18616
  progress;
18577
18617
  collectedFile;
18578
18618
  error;
18619
+ testId;
18620
+ get hostTestId() {
18621
+ return this.testId;
18622
+ }
18579
18623
  get isSuccessful() {
18580
18624
  return !this.error && this.progress === 100;
18581
18625
  }
18582
18626
  get hasError() {
18583
18627
  return !!this.error;
18584
18628
  }
18585
- canUpload() {
18586
- return false; // TODO discuss whether we want this feature; return !!this.error;
18587
- }
18588
18629
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18589
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemComponent, isStandalone: false, selector: "qd-file-collector-dialog-item", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error" }, host: { properties: { "class.success": "this.isSuccessful", "class.error": "this.hasError" } }, ngImport: i0, template: "<span class=\"name\">{{ this.collectedFile.name }}</span>\n<qd-file-collector-dialog-item-progress\n [progress]=\"this.progress\"\n [error]=\"this.error\"\n></qd-file-collector-dialog-item-progress>\n<span class=\"filesize\">{{ this.collectedFile.size | filesize | async }}</span>\n<qd-icon class=\"upload\" icon=\"dataUpload\" *ngIf=\"canUpload()\"></qd-icon>\n<qd-file-collector-dialog-item-error [error]=\"this.error\" *ngIf=\"!!this.error\"></qd-file-collector-dialog-item-error>\n", styles: [":host{color:#333;font-size:.875rem;font-weight:400;line-height:1.3125rem;position:relative;display:grid;align-items:self-start;padding:.625rem 1rem .625rem 2.875rem;border-left:transparent .1875rem solid;margin-bottom:.75rem;background:#fff;gap:.25rem 1rem;grid-template-columns:30rem auto max-content;outline:rgb(151,151,151) .0625rem solid}:host qd-file-collector-dialog-item-error{grid-column:span 2}:host .filesize{text-align:right}@media (max-width: 959.98px){:host{grid-template-columns:18.75rem auto max-content}:host .name{overflow:auto;overflow-wrap:break-word}}@media (max-width: 599.98px){:host{grid-template-columns:auto auto auto}:host .name,:host qd-file-collector-dialog-item-progress{grid-column:span 2}:host qd-file-collector-dialog-item-progress,:host .filesize{order:-1}}:host:last-child{margin-bottom:0}:host.error{border-left-color:#c70023}:host.success{border-left-color:#00813a}:host:before{position:absolute;top:.5625rem;left:.875rem;content:\"\\e964\";font-family:Quadrel-Icon,sans-serif;font-size:1.25rem}:host .upload{color:#454545;cursor:pointer;font-size:1rem}:host .upload:hover,:host .upload:focus{color:#171717}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }, { kind: "component", type: QdFileCollectorDialogItemProgressComponent, selector: "qd-file-collector-dialog-item-progress", inputs: ["progress", "error"] }, { kind: "component", type: QdFileCollectorDialogItemErrorComponent, selector: "qd-file-collector-dialog-item-error", inputs: ["error"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: QdFileSizePipe, name: "filesize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18630
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogItemComponent, isStandalone: false, selector: "qd-file-collector-dialog-item", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.hostTestId", "class.success": "this.isSuccessful", "class.error": "this.hasError" } }, ngImport: i0, template: "<span class=\"name\" [attr.data-test-id]=\"testId + '-name'\">{{ this.collectedFile.name }}</span>\n<qd-file-collector-dialog-item-progress\n [progress]=\"this.progress\"\n [error]=\"this.error\"\n [data-test-id]=\"testId + '-progress'\"\n></qd-file-collector-dialog-item-progress>\n<span class=\"filesize\" [attr.data-test-id]=\"testId + '-size'\">{{ this.collectedFile.size | filesize | async }}</span>\n<qd-file-collector-dialog-item-error\n [error]=\"this.error\"\n *ngIf=\"!!this.error\"\n [data-test-id]=\"testId + '-error'\"\n></qd-file-collector-dialog-item-error>\n", styles: [":host{color:#333;font-size:.875rem;font-weight:400;line-height:1.3125rem;position:relative;display:grid;align-items:self-start;padding:.625rem 1rem .625rem 2.875rem;border-left:transparent .1875rem solid;margin-bottom:.75rem;background:#fff;gap:.25rem 1rem;grid-template-columns:30rem auto max-content;outline:rgb(151,151,151) .0625rem solid}:host qd-file-collector-dialog-item-error{grid-column:span 2}:host .filesize{text-align:right}@media (max-width: 959.98px){:host{grid-template-columns:18.75rem auto max-content}:host .name{overflow:auto;overflow-wrap:break-word}}@media (max-width: 599.98px){:host{grid-template-columns:auto auto auto}:host .name,:host qd-file-collector-dialog-item-progress{grid-column:span 2}:host qd-file-collector-dialog-item-progress,:host .filesize{order:-1}}:host:last-child{margin-bottom:0}:host.error{border-left-color:#c70023}:host.success{border-left-color:#00813a}:host:before{position:absolute;top:.5625rem;left:.875rem;content:\"\\e964\";font-family:Quadrel-Icon,sans-serif;font-size:1.25rem}:host .upload{color:#454545;cursor:pointer;font-size:1rem}:host .upload:hover,:host .upload:focus{color:#171717}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdFileCollectorDialogItemProgressComponent, selector: "qd-file-collector-dialog-item-progress", inputs: ["progress", "error", "data-test-id"] }, { kind: "component", type: QdFileCollectorDialogItemErrorComponent, selector: "qd-file-collector-dialog-item-error", inputs: ["error", "data-test-id"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: QdFileSizePipe, name: "filesize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18590
18631
  }
18591
18632
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemComponent, decorators: [{
18592
18633
  type: Component,
18593
- args: [{ selector: 'qd-file-collector-dialog-item', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<span class=\"name\">{{ this.collectedFile.name }}</span>\n<qd-file-collector-dialog-item-progress\n [progress]=\"this.progress\"\n [error]=\"this.error\"\n></qd-file-collector-dialog-item-progress>\n<span class=\"filesize\">{{ this.collectedFile.size | filesize | async }}</span>\n<qd-icon class=\"upload\" icon=\"dataUpload\" *ngIf=\"canUpload()\"></qd-icon>\n<qd-file-collector-dialog-item-error [error]=\"this.error\" *ngIf=\"!!this.error\"></qd-file-collector-dialog-item-error>\n", styles: [":host{color:#333;font-size:.875rem;font-weight:400;line-height:1.3125rem;position:relative;display:grid;align-items:self-start;padding:.625rem 1rem .625rem 2.875rem;border-left:transparent .1875rem solid;margin-bottom:.75rem;background:#fff;gap:.25rem 1rem;grid-template-columns:30rem auto max-content;outline:rgb(151,151,151) .0625rem solid}:host qd-file-collector-dialog-item-error{grid-column:span 2}:host .filesize{text-align:right}@media (max-width: 959.98px){:host{grid-template-columns:18.75rem auto max-content}:host .name{overflow:auto;overflow-wrap:break-word}}@media (max-width: 599.98px){:host{grid-template-columns:auto auto auto}:host .name,:host qd-file-collector-dialog-item-progress{grid-column:span 2}:host qd-file-collector-dialog-item-progress,:host .filesize{order:-1}}:host:last-child{margin-bottom:0}:host.error{border-left-color:#c70023}:host.success{border-left-color:#00813a}:host:before{position:absolute;top:.5625rem;left:.875rem;content:\"\\e964\";font-family:Quadrel-Icon,sans-serif;font-size:1.25rem}:host .upload{color:#454545;cursor:pointer;font-size:1rem}:host .upload:hover,:host .upload:focus{color:#171717}\n"] }]
18634
+ args: [{ selector: 'qd-file-collector-dialog-item', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<span class=\"name\" [attr.data-test-id]=\"testId + '-name'\">{{ this.collectedFile.name }}</span>\n<qd-file-collector-dialog-item-progress\n [progress]=\"this.progress\"\n [error]=\"this.error\"\n [data-test-id]=\"testId + '-progress'\"\n></qd-file-collector-dialog-item-progress>\n<span class=\"filesize\" [attr.data-test-id]=\"testId + '-size'\">{{ this.collectedFile.size | filesize | async }}</span>\n<qd-file-collector-dialog-item-error\n [error]=\"this.error\"\n *ngIf=\"!!this.error\"\n [data-test-id]=\"testId + '-error'\"\n></qd-file-collector-dialog-item-error>\n", styles: [":host{color:#333;font-size:.875rem;font-weight:400;line-height:1.3125rem;position:relative;display:grid;align-items:self-start;padding:.625rem 1rem .625rem 2.875rem;border-left:transparent .1875rem solid;margin-bottom:.75rem;background:#fff;gap:.25rem 1rem;grid-template-columns:30rem auto max-content;outline:rgb(151,151,151) .0625rem solid}:host qd-file-collector-dialog-item-error{grid-column:span 2}:host .filesize{text-align:right}@media (max-width: 959.98px){:host{grid-template-columns:18.75rem auto max-content}:host .name{overflow:auto;overflow-wrap:break-word}}@media (max-width: 599.98px){:host{grid-template-columns:auto auto auto}:host .name,:host qd-file-collector-dialog-item-progress{grid-column:span 2}:host qd-file-collector-dialog-item-progress,:host .filesize{order:-1}}:host:last-child{margin-bottom:0}:host.error{border-left-color:#c70023}:host.success{border-left-color:#00813a}:host:before{position:absolute;top:.5625rem;left:.875rem;content:\"\\e964\";font-family:Quadrel-Icon,sans-serif;font-size:1.25rem}:host .upload{color:#454545;cursor:pointer;font-size:1rem}:host .upload:hover,:host .upload:focus{color:#171717}\n"] }]
18594
18635
  }], propDecorators: { progress: [{
18595
18636
  type: Input
18596
18637
  }], collectedFile: [{
18597
18638
  type: Input
18598
18639
  }], error: [{
18599
18640
  type: Input
18641
+ }], testId: [{
18642
+ type: Input,
18643
+ args: [{ required: true, alias: 'data-test-id' }]
18644
+ }], hostTestId: [{
18645
+ type: HostBinding,
18646
+ args: ['attr.data-test-id']
18600
18647
  }], isSuccessful: [{
18601
18648
  type: HostBinding,
18602
18649
  args: ['class.success']
@@ -18622,17 +18669,17 @@ class QdFileCollectorDialogComponent {
18622
18669
  this.dialogRef.close(this.fileUploads);
18623
18670
  }
18624
18671
  isCloseButtonDisabled() {
18625
- return !this.fileUploads.every(this._isFileUploadCompleted);
18672
+ return !this.fileUploads.every(this.isFileUploadCompleted);
18626
18673
  }
18627
- _isFileUploadCompleted(fileUpload) {
18674
+ isFileUploadCompleted(fileUpload) {
18628
18675
  return !!fileUpload.error || fileUpload.progress === 100;
18629
18676
  }
18630
18677
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18631
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogComponent, isStandalone: false, selector: "qd-file-collector-dialog", providers: [QdFileCollectorService], ngImport: i0, template: "<qd-dialog>\n <qd-file-collector-dialog-item\n *ngFor=\"let fileUpload of this.fileUploads\"\n [progress]=\"fileUpload.progress\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n ></qd-file-collector-dialog-item>\n <qd-dialog-action>\n <button qdButton (click)=\"close()\" [disabled]=\"isCloseButtonDisabled()\">\n {{ \"i18n.qd.fileCollector.button.finish\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "component", type: QdFileCollectorDialogItemComponent, selector: "qd-file-collector-dialog-item", inputs: ["progress", "collectedFile", "error"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18678
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorDialogComponent, isStandalone: false, selector: "qd-file-collector-dialog", providers: [QdFileCollectorService], ngImport: i0, template: "<qd-dialog>\n <qd-file-collector-dialog-item\n *ngFor=\"let fileUpload of this.fileUploads; let i = index\"\n [progress]=\"fileUpload.progress\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [data-test-id]=\"'file-collector-dialog-item-' + i\"\n ></qd-file-collector-dialog-item>\n <qd-dialog-action>\n <button\n qdButton\n (click)=\"close()\"\n [disabled]=\"isCloseButtonDisabled()\"\n data-test-id=\"file-collector-dialog-button-finish\"\n >\n {{ \"i18n.qd.fileCollector.button.finish\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "component", type: QdFileCollectorDialogItemComponent, selector: "qd-file-collector-dialog-item", inputs: ["progress", "collectedFile", "error", "data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18632
18679
  }
18633
18680
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogComponent, decorators: [{
18634
18681
  type: Component,
18635
- args: [{ selector: 'qd-file-collector-dialog', providers: [QdFileCollectorService], standalone: false, template: "<qd-dialog>\n <qd-file-collector-dialog-item\n *ngFor=\"let fileUpload of this.fileUploads\"\n [progress]=\"fileUpload.progress\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n ></qd-file-collector-dialog-item>\n <qd-dialog-action>\n <button qdButton (click)=\"close()\" [disabled]=\"isCloseButtonDisabled()\">\n {{ \"i18n.qd.fileCollector.button.finish\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n" }]
18682
+ args: [{ selector: 'qd-file-collector-dialog', providers: [QdFileCollectorService], standalone: false, template: "<qd-dialog>\n <qd-file-collector-dialog-item\n *ngFor=\"let fileUpload of this.fileUploads; let i = index\"\n [progress]=\"fileUpload.progress\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [data-test-id]=\"'file-collector-dialog-item-' + i\"\n ></qd-file-collector-dialog-item>\n <qd-dialog-action>\n <button\n qdButton\n (click)=\"close()\"\n [disabled]=\"isCloseButtonDisabled()\"\n data-test-id=\"file-collector-dialog-button-finish\"\n >\n {{ \"i18n.qd.fileCollector.button.finish\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n" }]
18636
18683
  }], ctorParameters: () => [] });
18637
18684
 
18638
18685
  class QdSectionToolbarActionService {
@@ -18768,7 +18815,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18768
18815
  }]
18769
18816
  }] });
18770
18817
 
18771
- // @ts-strict-ignore
18772
18818
  class QdFileCollectorItemNameComponent {
18773
18819
  name;
18774
18820
  testId;
@@ -18791,7 +18837,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18791
18837
  args: ['attr.data-test-id']
18792
18838
  }] } });
18793
18839
 
18794
- // @ts-strict-ignore
18795
18840
  class QdFileCollectorItemSizeComponent {
18796
18841
  size;
18797
18842
  testId;
@@ -18828,14 +18873,13 @@ class QdFileDeleteDialogComponent {
18828
18873
  this.dialogRef.close(true);
18829
18874
  }
18830
18875
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileDeleteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18831
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileDeleteDialogComponent, isStandalone: false, selector: "qd-file-delete-dialog", ngImport: i0, template: "<qd-dialog>\n {{ deleteHint }}\n\n <qd-dialog-action>\n <button (click)=\"cancel()\" qdButton qdButtonGhost color=\"secondary\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.cancel\" | translate }}\n </button>\n\n <button (click)=\"delete()\" qdButton color=\"error\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.delete\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host{overflow-wrap:break-word}\n"], dependencies: [{ kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "directive", type: QdButtonGhostDirective, selector: "button[qdButtonGhost], a[qdButtonGhost]" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18876
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileDeleteDialogComponent, isStandalone: false, selector: "qd-file-delete-dialog", ngImport: i0, template: "<qd-dialog>\n {{ deleteHint }}\n\n <qd-dialog-action>\n <button (click)=\"cancel()\" qdButton qdButtonGhost color=\"secondary\" data-test-id=\"file-delete-dialog-button-cancel\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.cancel\" | translate }}\n </button>\n\n <button (click)=\"delete()\" qdButton color=\"error\" data-test-id=\"file-delete-dialog-button-delete\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.delete\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host{overflow-wrap:break-word}\n"], dependencies: [{ kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "directive", type: QdButtonGhostDirective, selector: "button[qdButtonGhost], a[qdButtonGhost]" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18832
18877
  }
18833
18878
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileDeleteDialogComponent, decorators: [{
18834
18879
  type: Component,
18835
- args: [{ selector: 'qd-file-delete-dialog', standalone: false, template: "<qd-dialog>\n {{ deleteHint }}\n\n <qd-dialog-action>\n <button (click)=\"cancel()\" qdButton qdButtonGhost color=\"secondary\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.cancel\" | translate }}\n </button>\n\n <button (click)=\"delete()\" qdButton color=\"error\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.delete\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host{overflow-wrap:break-word}\n"] }]
18880
+ args: [{ selector: 'qd-file-delete-dialog', standalone: false, template: "<qd-dialog>\n {{ deleteHint }}\n\n <qd-dialog-action>\n <button (click)=\"cancel()\" qdButton qdButtonGhost color=\"secondary\" data-test-id=\"file-delete-dialog-button-cancel\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.cancel\" | translate }}\n </button>\n\n <button (click)=\"delete()\" qdButton color=\"error\" data-test-id=\"file-delete-dialog-button-delete\">\n {{ \"i18n.qd.fileCollector.deleteConfirmation.button.delete\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host{overflow-wrap:break-word}\n"] }]
18836
18881
  }] });
18837
18882
 
18838
- // @ts-strict-ignore
18839
18883
  class QdFileCollectorItemToolsComponent {
18840
18884
  fileCollectorService = inject(QdFileCollectorService);
18841
18885
  dialogService = inject(QdDialogService);
@@ -18843,16 +18887,13 @@ class QdFileCollectorItemToolsComponent {
18843
18887
  progress;
18844
18888
  collectedFile;
18845
18889
  error;
18846
- readonly;
18847
- viewonly;
18848
- downloadFilesWithHttpClient;
18890
+ readonly = false;
18891
+ viewonly = false;
18892
+ downloadFilesWithHttpClient = false;
18849
18893
  testId;
18850
18894
  get dataTestId() {
18851
18895
  return this.testId;
18852
18896
  }
18853
- canDownload() {
18854
- return !!this.collectedFile.downloadUrl;
18855
- }
18856
18897
  downloadFile(event) {
18857
18898
  if (!this.downloadFilesWithHttpClient)
18858
18899
  return;
@@ -18880,11 +18921,11 @@ class QdFileCollectorItemToolsComponent {
18880
18921
  });
18881
18922
  }
18882
18923
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorItemToolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18883
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorItemToolsComponent, isStandalone: false, selector: "qd-file-collector-item-tools", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", readonly: "readonly", viewonly: "viewonly", downloadFilesWithHttpClient: "downloadFilesWithHttpClient", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.dataTestId" } }, ngImport: i0, template: "<a\n (click)=\"downloadFile($event)\"\n [href]=\"collectedFile.downloadUrl\"\n target=\"_blank\"\n [ngClass]=\"{ hidden: !canDownload() }\"\n [attr.data-test-id]=\"testId + '-download'\"\n>\n <qd-icon icon=\"dataDownload\"></qd-icon>\n</a>\n\n<qd-icon\n icon=\"trash\"\n (click)=\"deleteFile()\"\n *ngIf=\"!readonly && !viewonly\"\n [attr.data-test-id]=\"testId + '-delete'\"\n></qd-icon>\n", styles: [":host qd-icon{color:#454545;cursor:pointer;font-size:1rem}:host qd-icon:hover,:host qd-icon:focus{color:#171717}:host *+*{margin-left:.5rem}:host .hidden{cursor:none;pointer-events:none;visibility:hidden}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18924
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorItemToolsComponent, isStandalone: false, selector: "qd-file-collector-item-tools", inputs: { progress: "progress", collectedFile: "collectedFile", error: "error", readonly: "readonly", viewonly: "viewonly", downloadFilesWithHttpClient: "downloadFilesWithHttpClient", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.dataTestId" } }, ngImport: i0, template: "<a\n (click)=\"downloadFile($event)\"\n [href]=\"collectedFile.downloadUrl\"\n target=\"_blank\"\n [ngClass]=\"{ hidden: !collectedFile.downloadUrl }\"\n [attr.data-test-id]=\"testId + '-download'\"\n>\n <qd-icon icon=\"dataDownload\"></qd-icon>\n</a>\n\n<qd-icon\n icon=\"trash\"\n (click)=\"deleteFile()\"\n *ngIf=\"!readonly && !viewonly\"\n [attr.data-test-id]=\"testId + '-delete'\"\n></qd-icon>\n", styles: [":host qd-icon{color:#454545;cursor:pointer;font-size:1rem}:host qd-icon:hover,:host qd-icon:focus{color:#171717}:host *+*{margin-left:.5rem}:host .hidden{cursor:none;pointer-events:none;visibility:hidden}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18884
18925
  }
18885
18926
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorItemToolsComponent, decorators: [{
18886
18927
  type: Component,
18887
- args: [{ selector: 'qd-file-collector-item-tools', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<a\n (click)=\"downloadFile($event)\"\n [href]=\"collectedFile.downloadUrl\"\n target=\"_blank\"\n [ngClass]=\"{ hidden: !canDownload() }\"\n [attr.data-test-id]=\"testId + '-download'\"\n>\n <qd-icon icon=\"dataDownload\"></qd-icon>\n</a>\n\n<qd-icon\n icon=\"trash\"\n (click)=\"deleteFile()\"\n *ngIf=\"!readonly && !viewonly\"\n [attr.data-test-id]=\"testId + '-delete'\"\n></qd-icon>\n", styles: [":host qd-icon{color:#454545;cursor:pointer;font-size:1rem}:host qd-icon:hover,:host qd-icon:focus{color:#171717}:host *+*{margin-left:.5rem}:host .hidden{cursor:none;pointer-events:none;visibility:hidden}\n"] }]
18928
+ args: [{ selector: 'qd-file-collector-item-tools', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<a\n (click)=\"downloadFile($event)\"\n [href]=\"collectedFile.downloadUrl\"\n target=\"_blank\"\n [ngClass]=\"{ hidden: !collectedFile.downloadUrl }\"\n [attr.data-test-id]=\"testId + '-download'\"\n>\n <qd-icon icon=\"dataDownload\"></qd-icon>\n</a>\n\n<qd-icon\n icon=\"trash\"\n (click)=\"deleteFile()\"\n *ngIf=\"!readonly && !viewonly\"\n [attr.data-test-id]=\"testId + '-delete'\"\n></qd-icon>\n", styles: [":host qd-icon{color:#454545;cursor:pointer;font-size:1rem}:host qd-icon:hover,:host qd-icon:focus{color:#171717}:host *+*{margin-left:.5rem}:host .hidden{cursor:none;pointer-events:none;visibility:hidden}\n"] }]
18888
18929
  }], propDecorators: { progress: [{
18889
18930
  type: Input
18890
18931
  }], collectedFile: [{
@@ -18927,7 +18968,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18927
18968
  }]
18928
18969
  }] });
18929
18970
 
18930
- // @ts-strict-ignore
18931
18971
  class QdFileCollectorUploadTimestampComponent {
18932
18972
  value;
18933
18973
  testId;
@@ -18950,15 +18990,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18950
18990
  args: ['attr.data-test-id']
18951
18991
  }] } });
18952
18992
 
18953
- // @ts-strict-ignore
18954
18993
  class QdFileCollectorItemComponent {
18955
18994
  progress;
18956
- newlyUploaded;
18995
+ newlyUploaded = false;
18957
18996
  collectedFile;
18958
18997
  error;
18959
- readonly;
18960
- viewonly;
18961
- downloadFilesWithHttpClient;
18998
+ readonly = false;
18999
+ viewonly = false;
19000
+ downloadFilesWithHttpClient = false;
18962
19001
  testId;
18963
19002
  get hostTestId() {
18964
19003
  return this.testId;
@@ -18997,88 +19036,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18997
19036
  args: ['class.newly-uploaded']
18998
19037
  }] } });
18999
19038
 
19000
- // @ts-strict-ignore
19001
19039
  /**
19002
- * **QdFileCollectorComponent** is a flexible component for managing files within an application.
19003
- * It supports uploading, downloading, and displaying files, offering various options for customization and configuration.
19040
+ * **QdFileCollectorComponent** lets users view, upload, download, and remove files.
19004
19041
  *
19005
19042
  * #### **Features**
19006
19043
  *
19007
- * - **Upload**: Allows users to add new files with restrictions on file type and size.
19008
- * - **Download**: Enables downloads directly via URL or, if needed, through Angular’s `HttpClient`, for example, to support authorization.
19009
- * - **Customizable Error Handling**: Validates file size, type, and name pattern, with customizable error messages for user guidance.
19010
- * - **Read-only Mode**: Disables interactions in read-only mode, allowing only viewing or downloading of files.
19011
- * - **Standalone Enabled**: Detects if used within or outside a QLS section structure and, if outside, enables file uploads in standalone mode.
19012
- *
19013
- * #### **Interactions**
19014
- *
19015
- * File interactions are managed through several key functions:
19016
- *
19017
- * - **upload**: Adds a file to the files list if validation is successful.
19018
- * - **delete**: Opens a confirmation dialog that, if confirmed, removes the file from the list.
19019
- * - **readonly/viewonly**: In read-only or view-only mode, upload and delete functions are disabled, limiting user actions.
19044
+ * - **Upload**: select files, validate them, and upload with progress tracking via a custom file manager
19045
+ * - **Download**: directly via URL or through `HttpClient` (e.g. for authorization)
19046
+ * - **Delete**: with confirmation dialog
19047
+ * - **Upload Timestamp**: shows the upload date next to the file name when `uploadTimestamp` is set
19048
+ * - **Validation**: restricts file type, size, and name pattern with customizable error messages
19049
+ * - **Readonly / Viewonly**: disables upload and delete, allows only viewing and downloading
19050
+ * - **Standalone / Section**: works independently with its own add button, or inside a `QdSection` via the toolbar action
19020
19051
  *
19021
- * #### **Standalone Mode**
19052
+ * #### **Quick start**
19022
19053
  *
19023
- * Following Quadrel’s design principles, QdFileCollector includes a standalone mode, allowing it to work independently.
19024
- * It automatically detects when used outside a QLS section structure and, in standalone mode, displays an "Add New" button to support file uploads.
19054
+ * **1. Add the collector to the template**
19025
19055
  *
19026
- * #### **File Restrictions**
19027
- *
19028
- * - **allowedFileTypes** and **allowedMimeTypes** should not be used together; using both will trigger an error log.
19029
- * - **maxFileSizeInBytes** sets a maximum file size allowed for uploads.
19030
- * - **allowedFileNamePattern** defines a regex pattern for file names; files that don’t match the pattern are rejected.
19056
+ * ```html
19057
+ * <qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>
19058
+ * ```
19031
19059
  *
19032
- * #### **Error Handling**
19060
+ * **2. Define the config in your component**
19033
19061
  *
19034
- * Upload errors can occur in the following cases:
19035
- * - **Invalid MIME Type**: When the file type is not in `allowedMimeTypes`.
19036
- * - **Invalid File Size**: When the file size exceeds `maxFileSizeInBytes`.
19037
- * - **Invalid File Name Pattern**: When the file name does not match `allowedFileNamePattern`.
19062
+ * ```typescript
19063
+ * @Component({
19064
+ * template: `<qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>`
19065
+ * })
19066
+ * export class MyComponent {
19067
+ * myFiles: QdCollectedFile[] = [
19068
+ * {
19069
+ * name: 'report.pdf',
19070
+ * size: 9432,
19071
+ * type: 'application/pdf',
19072
+ * downloadUrl: '/api/files/report.pdf',
19073
+ * uploadTimestamp: new Date()
19074
+ * }
19075
+ * ];
19038
19076
  *
19039
- * Error messages can be customized:
19040
- * - **invalidMimeTypeErrorMessageI18n**: Custom message for unsupported MIME types.
19041
- * - **invalidFileNameErrorMessageI18n**: Custom message for unsupported file names.
19077
+ * myFileCollectorConfig: QdFileCollectorConfig = {
19078
+ * files: this.myFiles
19079
+ * };
19080
+ * }
19081
+ * ```
19042
19082
  *
19043
- * #### **File Manager**
19083
+ * This is enough to show already existing files.
19044
19084
  *
19045
- * QdFileManager is an interface that provides methods for uploading and deleting files.
19046
- * To use it with QdFileCollectorComponent, implement this interface in a custom service and provide it via the `QD_FILE_MANAGER_TOKEN` token.
19047
- * This service enables you to create custom file-handling logic.
19085
+ * **3. Connect a file manager**
19048
19086
  *
19049
- * #### **Usage**
19087
+ * To enable upload and delete, provide a `QdFileManager` via `QD_FILE_MANAGER_TOKEN`.
19050
19088
  *
19051
19089
  * ```typescript
19052
19090
  * @Injectable()
19053
19091
  * export class MyFileManager implements QdFileManager {
19092
+ * private http = inject(HttpClient);
19093
+ *
19054
19094
  * upload(file: File): Observable<QdUploadProgress> {
19055
- * // custom implementation here
19095
+ * // Emit progress values from 0 to 100.
19096
+ * // When done, return the final QdCollectedFile from your backend response.
19097
+ * return this.http.post('/api/files', file).pipe(
19098
+ * map(response => {
19099
+ *
19100
+ * const myCollectedFile: QdCollectedFile = {
19101
+ * name: response.fileName,
19102
+ * size: response.fileSize,
19103
+ * type: response.mimeType,
19104
+ * downloadUrl: response.downloadUrl,
19105
+ * uploadTimestamp: new Date(response.storedAt)
19106
+ * };
19107
+ *
19108
+ * return { progress: 100, collectedFile: myCollectedFile };
19109
+ * })
19110
+ * );
19056
19111
  * }
19057
19112
  *
19058
19113
  * delete(collectedFile: QdCollectedFile): Observable<boolean> {
19059
- * // custom implementation here
19114
+ * return this.http.delete('/api/files/' + collectedFile.name).pipe(map(() => true));
19060
19115
  * }
19061
19116
  * }
19117
+ * ```
19062
19118
  *
19119
+ * Register the file manager in your component:
19120
+ *
19121
+ * ```typescript
19063
19122
  * @Component({
19064
- * selector: 'my-file-collector',
19065
- * providers: [
19066
- * {
19067
- * provide: QD_FILE_MANAGER_TOKEN,
19068
- * useClass: MyFileManager
19069
- * }
19070
- * ]
19123
+ * template: `<qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>`,
19124
+ * // Add the file manager here to connect the collector to your backend.
19125
+ * providers: [{ provide: QD_FILE_MANAGER_TOKEN, useClass: MyFileManager }]
19071
19126
  * })
19072
- * class MyFileCollectorComponent {
19073
- * fileCollectorConfig: QdFileCollectorConfig = {
19074
- * // configuration options here
19075
- * };
19127
+ * export class MyComponent {
19128
+ * // ...
19076
19129
  * }
19077
19130
  * ```
19078
19131
  *
19079
- * ```html
19080
- * <qd-file-collector [config]="fileCollectorConfig"></qd-file-collector>
19081
- * ```
19132
+ * #### **How the file manager works**
19133
+ *
19134
+ * The file manager connects the collector to your backend.
19135
+ *
19136
+ * It must support these two actions:
19137
+ *
19138
+ * - `upload(file)`: uploads a file and emits upload progress
19139
+ * - `delete(collectedFile)`: deletes a file and emits `true` if the deletion was successful
19140
+ *
19141
+ * For uploads:
19142
+ *
19143
+ * - emit `{ progress }` values from `0` to `100`
19144
+ * - when the upload is complete, return the final `collectedFile`
19145
+ *
19146
+ * > The example above uses a simple POST and returns one final result with `progress: 100`.
19147
+ * If you need a progress bar, use `HttpClient` with `reportProgress: true` to emit intermediate progress values.
19148
+ *
19149
+ * #### **Validation and file type rules**
19150
+ *
19151
+ * A file is rejected if:
19152
+ *
19153
+ * - its type is not allowed
19154
+ * - it is too large
19155
+ * - its name does not match the expected pattern
19156
+ *
19157
+ * You can provide custom error messages in the config.
19158
+ *
19159
+ * For file type validation, use one of these approaches:
19160
+ *
19161
+ * - `allowedFileTypes`
19162
+ * - `allowedMimeTypes` together with `allowedFileNamePattern`
19163
+ *
19164
+ * > Do not mix both approaches unless they describe the same rules.
19165
+ * > If the settings do not match, the component logs an error and does not use the config.
19166
+ *
19167
+ * #### **Upload behavior**
19168
+ *
19169
+ * Upload is triggered differently depending on where the component is used:
19170
+ *
19171
+ * - **outside a `QdSection`**: the component shows its own add button
19172
+ * - **inside a `QdSection`**: the component listens to the section action and opens the file picker when `addNew` is triggered
19173
+ *
19174
+ * In `readonly` or `viewonly` mode, upload and delete are disabled.
19175
+ *
19176
+ * If the component is connected to operation mode changes through the event broker,
19177
+ * `viewonly` can also change dynamically.
19178
+ *
19179
+ * #### **Download**
19180
+ *
19181
+ * Files can be downloaded directly from their `downloadUrl`.
19182
+ *
19183
+ * If your application needs authorization or custom download logic,
19184
+ * handle the download through your backend or file manager.
19082
19185
  */
19083
19186
  class QdFileCollectorComponent {
19084
19187
  sectionActionService = inject(QdSectionToolbarActionService, { optional: true });
@@ -19128,14 +19231,16 @@ class QdFileCollectorComponent {
19128
19231
  title: { i18n: 'i18n.qd.fileCollector.title' },
19129
19232
  viewContainerRef: this.viewContainerRef
19130
19233
  });
19131
- dialogRef.closed.subscribe((fileUploads = []) => {
19132
- fileUploads
19234
+ dialogRef.closed.subscribe((result) => {
19235
+ const fileUploads = result;
19236
+ (fileUploads ?? [])
19133
19237
  .filter(fileUpload => !fileUpload.error && fileUpload.progress === 100)
19134
19238
  .forEach(fileUpload => this.fileUploads.push({ ...fileUpload, newlyUploaded: true }));
19135
19239
  });
19136
19240
  const fileCollectorDialog = dialogRef.componentInstance;
19137
- fileCollectorDialog.uploadFiles(event.target.files);
19138
- event.target.value = '';
19241
+ const inputElement = event.target;
19242
+ fileCollectorDialog.uploadFiles(Array.from(inputElement.files ?? []));
19243
+ inputElement.value = '';
19139
19244
  }
19140
19245
  clickFileInput() {
19141
19246
  if (this.fileInput.nativeElement.disabled) {
@@ -19184,11 +19289,11 @@ class QdFileCollectorComponent {
19184
19289
  console.error(`QD-UI | QdFileCollector - ${message}`);
19185
19290
  }
19186
19291
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorComponent, isStandalone: false, selector: "qd-file-collector", inputs: { config: "config", testId: ["data-test-id", "testId"] }, providers: [QdFileCollectorService, QdFileCollectorValidationService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<button\n qdButton\n qdButtonGhost\n icon=\"plus\"\n *ngIf=\"canShowAddButton\"\n (click)=\"clickFileInput()\"\n [data-test-id]=\"testId + '-button-add-new'\"\n>\n {{ config?.standaloneAddNewLabel?.i18n || \"i18n.qd.fileCollector.standalone.addNew\" | translate }}\n</button>\n\n<input\n [disabled]=\"config?.readonly || config?.viewonly\"\n type=\"file\"\n (change)=\"handleFiles($event)\"\n #fileInput\n hidden\n multiple=\"multiple\"\n/>\n\n<qd-file-collector-allowed-files-description\n *ngIf=\"!config?.readonly && !config?.viewonly\"\n></qd-file-collector-allowed-files-description>\n\n<ng-container *ngFor=\"let fileUpload of this.fileUploads; let i = index\">\n <qd-file-collector-item\n *ngIf=\"!fileUpload.error && fileUpload.progress === 100\"\n [progress]=\"fileUpload.progress\"\n [newlyUploaded]=\"fileUpload.newlyUploaded\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [readonly]=\"config?.readonly\"\n [viewonly]=\"config?.viewonly\"\n [downloadFilesWithHttpClient]=\"config?.downloadFilesWithHttpClient\"\n [data-test-id]=\"testId + '-item-' + i\"\n ></qd-file-collector-item>\n</ng-container>\n", styles: [":host{display:flex;flex-direction:column}:host>.qd-button{align-self:end;margin-bottom:1.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "directive", type: QdButtonGhostDirective, selector: "button[qdButtonGhost], a[qdButtonGhost]" }, { kind: "component", type: QdFileCollectorAllowedFilesDescriptionComponent, selector: "qd-file-collector-allowed-files-description" }, { kind: "component", type: QdFileCollectorItemComponent, selector: "qd-file-collector-item", inputs: ["progress", "newlyUploaded", "collectedFile", "error", "readonly", "viewonly", "downloadFilesWithHttpClient", "data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19292
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdFileCollectorComponent, isStandalone: false, selector: "qd-file-collector", inputs: { config: "config", testId: ["data-test-id", "testId"] }, providers: [QdFileCollectorService, QdFileCollectorValidationService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<button\n qdButton\n qdButtonGhost\n icon=\"plus\"\n *ngIf=\"canShowAddButton\"\n (click)=\"clickFileInput()\"\n [data-test-id]=\"testId + '-button-add-new'\"\n>\n {{ config?.standaloneAddNewLabel?.i18n || \"i18n.qd.fileCollector.standalone.addNew\" | translate }}\n</button>\n\n<input\n [disabled]=\"config?.readonly || config?.viewonly\"\n type=\"file\"\n (change)=\"handleFiles($event)\"\n #fileInput\n hidden\n multiple=\"multiple\"\n/>\n\n<qd-file-collector-allowed-files-description\n *ngIf=\"!config?.readonly && !config?.viewonly\"\n [attr.data-test-id]=\"testId + '-allowed-files-description'\"\n></qd-file-collector-allowed-files-description>\n\n<ng-container *ngFor=\"let fileUpload of this.fileUploads; let i = index\">\n <qd-file-collector-item\n *ngIf=\"!fileUpload.error && fileUpload.progress === 100\"\n [progress]=\"fileUpload.progress\"\n [newlyUploaded]=\"fileUpload.newlyUploaded\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [readonly]=\"config?.readonly\"\n [viewonly]=\"config?.viewonly\"\n [downloadFilesWithHttpClient]=\"config?.downloadFilesWithHttpClient\"\n [data-test-id]=\"testId + '-item-' + i\"\n ></qd-file-collector-item>\n</ng-container>\n", styles: [":host{display:flex;flex-direction:column}:host>.qd-button{align-self:end;margin-bottom:1.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "directive", type: QdButtonGhostDirective, selector: "button[qdButtonGhost], a[qdButtonGhost]" }, { kind: "component", type: QdFileCollectorAllowedFilesDescriptionComponent, selector: "qd-file-collector-allowed-files-description" }, { kind: "component", type: QdFileCollectorItemComponent, selector: "qd-file-collector-item", inputs: ["progress", "newlyUploaded", "collectedFile", "error", "readonly", "viewonly", "downloadFilesWithHttpClient", "data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19188
19293
  }
19189
19294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorComponent, decorators: [{
19190
19295
  type: Component,
19191
- args: [{ selector: 'qd-file-collector', providers: [QdFileCollectorService, QdFileCollectorValidationService], standalone: false, template: "<button\n qdButton\n qdButtonGhost\n icon=\"plus\"\n *ngIf=\"canShowAddButton\"\n (click)=\"clickFileInput()\"\n [data-test-id]=\"testId + '-button-add-new'\"\n>\n {{ config?.standaloneAddNewLabel?.i18n || \"i18n.qd.fileCollector.standalone.addNew\" | translate }}\n</button>\n\n<input\n [disabled]=\"config?.readonly || config?.viewonly\"\n type=\"file\"\n (change)=\"handleFiles($event)\"\n #fileInput\n hidden\n multiple=\"multiple\"\n/>\n\n<qd-file-collector-allowed-files-description\n *ngIf=\"!config?.readonly && !config?.viewonly\"\n></qd-file-collector-allowed-files-description>\n\n<ng-container *ngFor=\"let fileUpload of this.fileUploads; let i = index\">\n <qd-file-collector-item\n *ngIf=\"!fileUpload.error && fileUpload.progress === 100\"\n [progress]=\"fileUpload.progress\"\n [newlyUploaded]=\"fileUpload.newlyUploaded\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [readonly]=\"config?.readonly\"\n [viewonly]=\"config?.viewonly\"\n [downloadFilesWithHttpClient]=\"config?.downloadFilesWithHttpClient\"\n [data-test-id]=\"testId + '-item-' + i\"\n ></qd-file-collector-item>\n</ng-container>\n", styles: [":host{display:flex;flex-direction:column}:host>.qd-button{align-self:end;margin-bottom:1.5rem}\n"] }]
19296
+ args: [{ selector: 'qd-file-collector', providers: [QdFileCollectorService, QdFileCollectorValidationService], standalone: false, template: "<button\n qdButton\n qdButtonGhost\n icon=\"plus\"\n *ngIf=\"canShowAddButton\"\n (click)=\"clickFileInput()\"\n [data-test-id]=\"testId + '-button-add-new'\"\n>\n {{ config?.standaloneAddNewLabel?.i18n || \"i18n.qd.fileCollector.standalone.addNew\" | translate }}\n</button>\n\n<input\n [disabled]=\"config?.readonly || config?.viewonly\"\n type=\"file\"\n (change)=\"handleFiles($event)\"\n #fileInput\n hidden\n multiple=\"multiple\"\n/>\n\n<qd-file-collector-allowed-files-description\n *ngIf=\"!config?.readonly && !config?.viewonly\"\n [attr.data-test-id]=\"testId + '-allowed-files-description'\"\n></qd-file-collector-allowed-files-description>\n\n<ng-container *ngFor=\"let fileUpload of this.fileUploads; let i = index\">\n <qd-file-collector-item\n *ngIf=\"!fileUpload.error && fileUpload.progress === 100\"\n [progress]=\"fileUpload.progress\"\n [newlyUploaded]=\"fileUpload.newlyUploaded\"\n [collectedFile]=\"fileUpload.collectedFile\"\n [error]=\"fileUpload.error\"\n [readonly]=\"config?.readonly\"\n [viewonly]=\"config?.viewonly\"\n [downloadFilesWithHttpClient]=\"config?.downloadFilesWithHttpClient\"\n [data-test-id]=\"testId + '-item-' + i\"\n ></qd-file-collector-item>\n</ng-container>\n", styles: [":host{display:flex;flex-direction:column}:host>.qd-button{align-self:end;margin-bottom:1.5rem}\n"] }]
19192
19297
  }], propDecorators: { config: [{
19193
19298
  type: Input
19194
19299
  }], testId: [{
@@ -26999,8 +27104,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
26999
27104
  class QdFormGroupManagerService {
27000
27105
  _formGroups = new Map();
27001
27106
  _formGroupsSnapshot = new Map();
27107
+ _formGroupsChanged$ = new BehaviorSubject(undefined);
27002
27108
  setFormGroup(key, formGroup) {
27003
27109
  this._formGroups.set(key, formGroup);
27110
+ this._formGroupsChanged$.next();
27004
27111
  }
27005
27112
  getFormGroup(key) {
27006
27113
  return this._formGroups.get(key);
@@ -27010,6 +27117,7 @@ class QdFormGroupManagerService {
27010
27117
  }
27011
27118
  tryRemoveFormGroup(key) {
27012
27119
  this._formGroups.delete(key);
27120
+ this._formGroupsChanged$.next();
27013
27121
  }
27014
27122
  hasFormGroups() {
27015
27123
  return this._formGroups.size > 0;
@@ -27023,19 +27131,24 @@ class QdFormGroupManagerService {
27023
27131
  return !this._formGroups.has(key);
27024
27132
  }
27025
27133
  $areFormGroupsValid() {
27026
- if (!this.hasFormGroups())
27027
- return of(false);
27028
- const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
27029
- return combineLatest(obs).pipe(map(valids => valids.every(Boolean)));
27134
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
27135
+ if (!this.hasFormGroups())
27136
+ return of(false);
27137
+ const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
27138
+ return combineLatest(obs).pipe(map(valids => valids.every(Boolean)));
27139
+ }));
27030
27140
  }
27031
27141
  $hasValuesChanged() {
27032
- if (!this.hasFormGroups())
27033
- return of(false);
27034
- const obs = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(fg.getRawValue()), map(currentValues => {
27035
- const snapshot = this._formGroupsSnapshot.get(key);
27036
- return snapshot ? !isEqual$1(currentValues, snapshot) : true;
27037
- })));
27038
- return combineLatest(obs).pipe(map(changes => changes.some(Boolean)));
27142
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
27143
+ if (!this.hasFormGroups())
27144
+ return of(false);
27145
+ const obs = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(fg.getRawValue()), map(() => {
27146
+ const currentValues = fg.getRawValue();
27147
+ const snapshot = this._formGroupsSnapshot.get(key);
27148
+ return snapshot ? !isEqual$1(currentValues, snapshot) : true;
27149
+ })));
27150
+ return combineLatest(obs).pipe(map(changes => changes.some(Boolean)));
27151
+ }));
27039
27152
  }
27040
27153
  takeFormGroupsSnapshot() {
27041
27154
  this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
@@ -27055,6 +27168,43 @@ class QdFormGroupManagerService {
27055
27168
  }
27056
27169
  });
27057
27170
  });
27171
+ this.cancelPendingAsyncValidation();
27172
+ }
27173
+ /**
27174
+ * Cancels any in-flight async validators on all registered form groups.
27175
+ *
27176
+ * 1. Collect all PENDING controls and their async validators
27177
+ * 2. Clear all async validators, then update validity (sync-only, no cascade)
27178
+ * 3. Re-attach async validators for future use
27179
+ */
27180
+ cancelPendingAsyncValidation() {
27181
+ this._formGroups.forEach(fg => {
27182
+ const pendingControls = this.collectPendingControls(fg);
27183
+ if (pendingControls.length === 0)
27184
+ return;
27185
+ for (const { control } of pendingControls) {
27186
+ control.clearAsyncValidators();
27187
+ }
27188
+ for (const { control } of pendingControls) {
27189
+ control.updateValueAndValidity({ onlySelf: true });
27190
+ }
27191
+ for (const { control, asyncValidator } of pendingControls) {
27192
+ control.setAsyncValidators(asyncValidator);
27193
+ }
27194
+ });
27195
+ }
27196
+ collectPendingControls(control) {
27197
+ const result = [];
27198
+ if (control instanceof FormGroup) {
27199
+ Object.values(control.controls).forEach(c => result.push(...this.collectPendingControls(c)));
27200
+ }
27201
+ else if (control instanceof FormArray) {
27202
+ control.controls.forEach(c => result.push(...this.collectPendingControls(c)));
27203
+ }
27204
+ if (control.status === 'PENDING') {
27205
+ result.push({ control, asyncValidator: control.asyncValidator });
27206
+ }
27207
+ return result;
27058
27208
  }
27059
27209
  resetFormArrayToValues(array, newValues) {
27060
27210
  if (!Array.isArray(newValues))
@@ -27179,6 +27329,7 @@ class QdPageObjectHeaderComponent {
27179
27329
  _pageObjectDataSubject = new BehaviorSubject({});
27180
27330
  _isLoadingSubject = new BehaviorSubject(false);
27181
27331
  _customActionsSubject = new BehaviorSubject({ actions: [] });
27332
+ _customActionsSub;
27182
27333
  _destroyed$ = new Subject();
27183
27334
  _availableContexts = 0;
27184
27335
  pageObjectData$ = this._pageObjectDataSubject.asObservable();
@@ -27309,14 +27460,18 @@ class QdPageObjectHeaderComponent {
27309
27460
  .$hasValuesChanged()
27310
27461
  .pipe(take(1))
27311
27462
  .subscribe(hasChanged => {
27312
- if (hasChanged)
27463
+ if (hasChanged) {
27313
27464
  this.openCancelDialog();
27314
- else
27465
+ }
27466
+ else {
27467
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27315
27468
  this.pageStoreService.toggleViewonly(true);
27469
+ }
27316
27470
  });
27317
27471
  }
27318
27472
  save() {
27319
27473
  const handleSuccess = () => {
27474
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27320
27475
  this.pageStoreService.toggleViewonly(true);
27321
27476
  this.formGroupManagerService.takeFormGroupsSnapshot();
27322
27477
  };
@@ -27345,7 +27500,7 @@ class QdPageObjectHeaderComponent {
27345
27500
  setupResolverTrigger() {
27346
27501
  this.resolverTriggerService
27347
27502
  .shouldTriggerResolver(this.pageObjectResolver.config?.triggerOn ?? 'pathParamsChange')
27348
- .pipe(takeUntil(this._destroyed$), filter(shouldTrigger => shouldTrigger), tap(() => this._isLoadingSubject.next(true)), switchMap(() => this.pageObjectResolver.resolve()), tap(objectData => this._pageObjectDataSubject.next(objectData)), tap(() => this._isLoadingSubject.next(false)))
27503
+ .pipe(takeUntil(this._destroyed$), filter(shouldTrigger => shouldTrigger), tap(() => this._isLoadingSubject.next(true)), switchMap(() => this.pageObjectResolver.resolve()), tap(objectData => this._pageObjectDataSubject.next(objectData)), tap(() => this._isLoadingSubject.next(false)), tap(() => this.formGroupManagerService.takeFormGroupsSnapshot()))
27349
27504
  .subscribe();
27350
27505
  }
27351
27506
  initContexts() {
@@ -27364,17 +27519,32 @@ class QdPageObjectHeaderComponent {
27364
27519
  .subscribe();
27365
27520
  }
27366
27521
  updateCustomActions() {
27367
- if (this.config.pageType === 'inspect') {
27368
- const operationMode = this.config.pageTypeConfig?.operationMode;
27369
- if (operationMode)
27370
- this.pageStoreService.toggleViewonly(operationMode === 'view');
27371
- this.pageStoreService.isViewonly$.pipe(takeUntil(this._destroyed$)).subscribe(isViewonly => {
27372
- const customActions = this.config.pageTypeConfig?.customActions ?? [];
27373
- this._customActionsSubject.next(this.getCustomActionsByMode(customActions, isViewonly ? 'view' : 'edit'));
27374
- });
27522
+ // 1. Handle non-inspect pages early
27523
+ if (this.config.pageType !== 'inspect') {
27524
+ const actions = this.config.pageTypeConfig?.customActions ?? [];
27525
+ this._customActionsSubject.next({ actions });
27526
+ return;
27527
+ }
27528
+ // 2. Setup Inspect-specific logic
27529
+ const config = this.config.pageTypeConfig;
27530
+ if (config?.operationMode) {
27531
+ this.pageStoreService.toggleViewonly(config.operationMode === 'view');
27375
27532
  }
27376
- if (this.config.pageType !== 'inspect')
27377
- this._customActionsSubject.next({ actions: this.config.pageTypeConfig?.customActions ?? [] });
27533
+ this.subscribeToViewOnlyMode();
27534
+ }
27535
+ subscribeToViewOnlyMode() {
27536
+ this._customActionsSub?.unsubscribe();
27537
+ this._customActionsSub = this.pageStoreService.isViewonly$
27538
+ .pipe(takeUntil(this._destroyed$))
27539
+ .subscribe(isViewonly => {
27540
+ const mode = isViewonly ? 'view' : 'edit';
27541
+ const config = this.config.pageTypeConfig;
27542
+ if (config) {
27543
+ config.operationMode = mode;
27544
+ }
27545
+ const actions = config?.customActions ?? [];
27546
+ this._customActionsSubject.next(this.getCustomActionsByMode(actions, mode));
27547
+ });
27378
27548
  }
27379
27549
  getCustomActionsByMode(customActions, mode) {
27380
27550
  const actions = customActions
@@ -28373,6 +28543,7 @@ class QdPageSubmitActionService {
28373
28543
  _submitHandler;
28374
28544
  _isVisible = true;
28375
28545
  _destroyed$ = new Subject();
28546
+ _cancelTrackFormValidity$ = new Subject();
28376
28547
  ngOnDestroy() {
28377
28548
  this._destroyed$.next(null);
28378
28549
  this._destroyed$.complete();
@@ -28411,9 +28582,10 @@ class QdPageSubmitActionService {
28411
28582
  };
28412
28583
  }
28413
28584
  trackFormValidity() {
28585
+ this._cancelTrackFormValidity$.next();
28414
28586
  this.formGroupManagerService
28415
28587
  .$areFormGroupsValid()
28416
- .pipe(takeUntil(this._destroyed$), distinctUntilChanged())
28588
+ .pipe(takeUntil(this._cancelTrackFormValidity$), takeUntil(this._destroyed$), distinctUntilChanged())
28417
28589
  .subscribe(isValid => this.updateSubmitButtonState(isValid));
28418
28590
  }
28419
28591
  updateSubmitButtonState(isValid) {
@@ -28851,6 +29023,7 @@ class QdPageComponent {
28851
29023
  projectionGuardMessage = 'QD-UI | QdPage - This content is not supported. Please use QdSectionAdapterDirective for custom content projection.';
28852
29024
  _isInitialized = false;
28853
29025
  _destroyed$ = new Subject();
29026
+ _cancelSubmitValidation$ = new Subject();
28854
29027
  get isControlPanelVisible() {
28855
29028
  return this.controlPanel !== undefined && this.controlPanel.config?.isHidden !== true;
28856
29029
  }
@@ -28965,6 +29138,10 @@ class QdPageComponent {
28965
29138
  ]);
28966
29139
  }
28967
29140
  updateInspectPageOperationMode(pageTypeConfig, mode) {
29141
+ if (mode === 'view') {
29142
+ this.formGroupManagerService.cancelPendingAsyncValidation();
29143
+ setTimeout(() => this.formGroupManagerService.cancelPendingAsyncValidation());
29144
+ }
28968
29145
  if (pageTypeConfig?.submit) {
28969
29146
  const isVisible = mode === 'view' && !pageTypeConfig.submit.isHidden;
28970
29147
  this.submitActionService.setSubmitActionForInspect(pageTypeConfig, isVisible);
@@ -28992,9 +29169,10 @@ class QdPageComponent {
28992
29169
  .subscribe();
28993
29170
  }
28994
29171
  initSubmitValidation() {
29172
+ this._cancelSubmitValidation$.next();
28995
29173
  this.formGroupManagerService
28996
29174
  .$areFormGroupsValid()
28997
- .pipe(takeUntil(this._destroyed$), tap(isValid => {
29175
+ .pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
28998
29176
  const submitDisabledInfoText = this.config.pageType === 'inspect' ? this.config.pageTypeConfig?.submit?.disabledInfo : undefined;
28999
29177
  this.footerService.updateActions([
29000
29178
  {