@quadrel-enterprise-ui/framework 20.5.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**
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
19014
19051
  *
19015
- * File interactions are managed through several key functions:
19052
+ * #### **Quick start**
19016
19053
  *
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.
19054
+ * **1. Add the collector to the template**
19020
19055
  *
19021
- * #### **Standalone Mode**
19022
- *
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.
19025
- *
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: [{
@@ -22401,6 +22506,8 @@ const updateSort$1 = createAction('[QdUi Table] Update Sort', props());
22401
22506
  const setRequestState$1 = createAction('[QdUi Table] Set Loading State', props());
22402
22507
  const connect$1 = createAction('[QdUi Table]', props());
22403
22508
  const resetConnectorStates$1 = createAction('[QdUi Table] Reset Connector States', props());
22509
+ const setLastVisitedRow$1 = createAction('[QdUi Table] Set Last Visited Row', props());
22510
+ const clearLastVisitedRow$1 = createAction('[QdUi Table] Clear Last Visited Row', props());
22404
22511
  const deleteTableState = createAction('[QdUi Table] Delete Table State', props());
22405
22512
  const QdTableActions = {
22406
22513
  initTableState,
@@ -22420,6 +22527,8 @@ const QdTableActions = {
22420
22527
  setRequestState: setRequestState$1,
22421
22528
  connect: connect$1,
22422
22529
  resetConnectorStates: resetConnectorStates$1,
22530
+ setLastVisitedRow: setLastVisitedRow$1,
22531
+ clearLastVisitedRow: clearLastVisitedRow$1,
22423
22532
  deleteTableState
22424
22533
  };
22425
22534
 
@@ -22447,6 +22556,15 @@ var QdTableRequestState;
22447
22556
  QdTableRequestState[QdTableRequestState["SUCCESS"] = 1] = "SUCCESS";
22448
22557
  })(QdTableRequestState || (QdTableRequestState = {}));
22449
22558
 
22559
+ function resolveRowIdentifier(row, rowIndex, trackRowBy) {
22560
+ if (trackRowBy) {
22561
+ const value = trackRowBy(row);
22562
+ if (value !== undefined && value !== null && (typeof value === 'string' || typeof value === 'number'))
22563
+ return value;
22564
+ }
22565
+ return row.uid ?? rowIndex;
22566
+ }
22567
+
22450
22568
  class QdTableStoreSelectorService {
22451
22569
  _tableId;
22452
22570
  set tableId(tableId) {
@@ -22476,13 +22594,16 @@ class QdTableStoreSelectorService {
22476
22594
  selectedRows() {
22477
22595
  return createSelector(this.selectTables, tables => this.getTable(tables)?.selectedRows ?? []);
22478
22596
  }
22479
- isTableRowSelected(rowIndex) {
22597
+ isTableRowSelected(rowIndex, trackRowBy) {
22480
22598
  return createSelector(this.selectTables, tables => {
22481
22599
  const table = this.getTable(tables);
22482
22600
  if (!table)
22483
22601
  return false;
22484
- const tableUid = table.data[rowIndex]?.uid;
22485
- return table.selectedRows?.includes(rowIndex) || (tableUid && table.selectedRows?.includes(tableUid)) || false;
22602
+ const row = table.data[rowIndex];
22603
+ if (!row)
22604
+ return false;
22605
+ const identifier = resolveRowIdentifier(row, rowIndex, trackRowBy);
22606
+ return table.selectedRows?.includes(identifier) || false;
22486
22607
  });
22487
22608
  }
22488
22609
  recentSecondaryAction() {
@@ -22572,6 +22693,9 @@ class QdTableStoreSelectorService {
22572
22693
  };
22573
22694
  });
22574
22695
  }
22696
+ lastVisitedRowIdentifier() {
22697
+ return createSelector(this.selectTables, tables => this.getTable(tables)?.lastVisitedRowIdentifier);
22698
+ }
22575
22699
  selectTables(state) {
22576
22700
  return state.qdUiTable;
22577
22701
  }
@@ -22610,14 +22734,14 @@ class QdTableStoreService {
22610
22734
  updateTableStateData(data) {
22611
22735
  this.store.dispatch(QdTableActions.updateTableStateData({ tableId: this.tableId, data }));
22612
22736
  }
22613
- updateTableStateRowToSelected(rowIndex) {
22614
- this.store.dispatch(QdTableActions.updateTableStateRowToSelected({ tableId: this.tableId, rowIndex }));
22737
+ updateTableStateRowToSelected(rowIdentifier) {
22738
+ this.store.dispatch(QdTableActions.updateTableStateRowToSelected({ tableId: this.tableId, rowIdentifier }));
22615
22739
  }
22616
- updateTableStateSingleRowToSelected(rowIndex) {
22617
- this.store.dispatch(QdTableActions.updateTableStateSingleRowToSelected({ tableId: this.tableId, rowIndex }));
22740
+ updateTableStateSingleRowToSelected(rowIdentifier) {
22741
+ this.store.dispatch(QdTableActions.updateTableStateSingleRowToSelected({ tableId: this.tableId, rowIdentifier }));
22618
22742
  }
22619
- updateTableStateRowToUnselected(rowIndex) {
22620
- this.store.dispatch(QdTableActions.updateTableStateRowToUnselected({ tableId: this.tableId, rowIndex }));
22743
+ updateTableStateRowToUnselected(rowIdentifier) {
22744
+ this.store.dispatch(QdTableActions.updateTableStateRowToUnselected({ tableId: this.tableId, rowIdentifier }));
22621
22745
  }
22622
22746
  updateTableStateRecentSecondaryAction(action) {
22623
22747
  this.store.dispatch(QdTableActions.updateTableStateRecentSecondaryAction({ tableId: this.tableId, recentSecondaryAction: action }));
@@ -22649,6 +22773,12 @@ class QdTableStoreService {
22649
22773
  connect(connectorName, connectorState, connectorCriteria) {
22650
22774
  this.store.dispatch(QdTableActions.connect({ tableId: this.tableId, connectorCriteria, connectorName, connectorState }));
22651
22775
  }
22776
+ setLastVisitedRow(rowIdentifier) {
22777
+ this.store.dispatch(QdTableActions.setLastVisitedRow({ tableId: this.tableId, rowIdentifier }));
22778
+ }
22779
+ clearLastVisitedRow() {
22780
+ this.store.dispatch(QdTableActions.clearLastVisitedRow({ tableId: this.tableId }));
22781
+ }
22652
22782
  resetConnectorStates() {
22653
22783
  this.store.dispatch(QdTableActions.resetConnectorStates({ tableId: this.tableId }));
22654
22784
  }
@@ -22669,8 +22799,8 @@ class QdTableStoreService {
22669
22799
  .select(this.tableStoreSelectorService.selectedRows())
22670
22800
  .pipe(filter(selectedRows => selectedRows !== undefined));
22671
22801
  }
22672
- isTableRowSelected$(rowIndex) {
22673
- return this.store.select(this.tableStoreSelectorService.isTableRowSelected(rowIndex));
22802
+ isTableRowSelected$(rowIndex, trackRowBy) {
22803
+ return this.store.select(this.tableStoreSelectorService.isTableRowSelected(rowIndex, trackRowBy));
22674
22804
  }
22675
22805
  recentSecondaryAction$() {
22676
22806
  return this.store
@@ -22707,6 +22837,9 @@ class QdTableStoreService {
22707
22837
  selectDataResolutionCriteria$() {
22708
22838
  return this.store.select(this.tableStoreSelectorService.selectDataResolutionCriteria());
22709
22839
  }
22840
+ lastVisitedRowIdentifier$() {
22841
+ return this.store.select(this.tableStoreSelectorService.lastVisitedRowIdentifier());
22842
+ }
22710
22843
  selectConnectorState$(connectorName) {
22711
22844
  return this.store.select(this.tableStoreSelectorService.selectConnectorState(connectorName));
22712
22845
  }
@@ -23106,24 +23239,27 @@ class QdTableRowSelectionService {
23106
23239
  ]).pipe(skip(this._config.selection?.emitOutputInitially ? 0 : 1), map(([tableData]) => (tableData || []).map(this.getIndexedRows).filter(isRowSelected)));
23107
23240
  }
23108
23241
  isRowSelected(row, index) {
23109
- return this._selectedRows.includes(row.uid) || this._selectedRows.includes(index);
23242
+ const identifier = resolveRowIdentifier(row, index, this._config.trackRowBy);
23243
+ return this._selectedRows.includes(identifier);
23110
23244
  }
23111
23245
  getIndexedRows(row, index) {
23112
23246
  return { ...row, index };
23113
23247
  }
23114
23248
  isRowSelected$(rowIndex) {
23115
- return this.tableStoreService.isTableRowSelected$(rowIndex);
23249
+ return this.tableStoreService.isTableRowSelected$(rowIndex, this._config.trackRowBy);
23116
23250
  }
23117
- setRowSelected(rowIndex) {
23251
+ setRowSelected(rowIndex, rowData) {
23252
+ const identifier = resolveRowIdentifier(rowData, rowIndex, this._config.trackRowBy);
23118
23253
  if (this._config.selection.type === 'singleSelect') {
23119
- this.tableStoreService.updateTableStateSingleRowToSelected(rowIndex);
23254
+ this.tableStoreService.updateTableStateSingleRowToSelected(identifier);
23120
23255
  }
23121
23256
  else {
23122
- this.tableStoreService.updateTableStateRowToSelected(rowIndex);
23257
+ this.tableStoreService.updateTableStateRowToSelected(identifier);
23123
23258
  }
23124
23259
  }
23125
- setRowUnselected(rowIndex) {
23126
- this.tableStoreService.updateTableStateRowToUnselected(rowIndex);
23260
+ setRowUnselected(rowIndex, rowData) {
23261
+ const identifier = resolveRowIdentifier(rowData, rowIndex, this._config.trackRowBy);
23262
+ this.tableStoreService.updateTableStateRowToUnselected(identifier);
23127
23263
  }
23128
23264
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
23129
23265
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionService });
@@ -23562,6 +23698,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
23562
23698
  class QdTableRowSelectionComponent {
23563
23699
  rowSelectionService = inject(QdTableRowSelectionService);
23564
23700
  rowIndex;
23701
+ rowData;
23565
23702
  testId;
23566
23703
  get dataTestId() {
23567
23704
  return this.testId;
@@ -23580,20 +23717,22 @@ class QdTableRowSelectionComponent {
23580
23717
  }
23581
23718
  handleChange(event) {
23582
23719
  if (event.target.checked) {
23583
- this.rowSelectionService.setRowSelected(this.rowIndex);
23720
+ this.rowSelectionService.setRowSelected(this.rowIndex, this.rowData);
23584
23721
  }
23585
23722
  else {
23586
- this.rowSelectionService.setRowUnselected(this.rowIndex);
23723
+ this.rowSelectionService.setRowUnselected(this.rowIndex, this.rowData);
23587
23724
  }
23588
23725
  }
23589
23726
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23590
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableRowSelectionComponent, isStandalone: false, selector: "[qd-table-row-selection]", inputs: { rowIndex: "rowIndex", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.dataTestId" } }, ngImport: i0, template: "<ng-container [ngSwitch]=\"selectionType\">\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <input\n type=\"radio\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-radio'\"\n />\n </ng-container>\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <input\n type=\"checkbox\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-checkbox'\"\n />\n </ng-container>\n</ng-container>\n", styles: ["input{cursor:pointer}\n"], dependencies: [{ 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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23727
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableRowSelectionComponent, isStandalone: false, selector: "[qd-table-row-selection]", inputs: { rowIndex: "rowIndex", rowData: "rowData", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.dataTestId" } }, ngImport: i0, template: "<ng-container [ngSwitch]=\"selectionType\">\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <input\n type=\"radio\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-radio'\"\n />\n </ng-container>\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <input\n type=\"checkbox\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-checkbox'\"\n />\n </ng-container>\n</ng-container>\n", styles: ["input{cursor:pointer}\n"], dependencies: [{ 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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23591
23728
  }
23592
23729
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionComponent, decorators: [{
23593
23730
  type: Component,
23594
23731
  args: [{ selector: '[qd-table-row-selection]', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ng-container [ngSwitch]=\"selectionType\">\n <ng-container *ngSwitchCase=\"'singleSelect'\">\n <input\n type=\"radio\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-radio'\"\n />\n </ng-container>\n <ng-container *ngSwitchCase=\"'multiSelect'\">\n <input\n type=\"checkbox\"\n [name]=\"'qd-table-selection__' + tableId\"\n [checked]=\"isRowSelected$() | async\"\n (click)=\"stopPropagation($event)\"\n (change)=\"handleChange($event)\"\n [attr.data-test-id]=\"testId + '-checkbox'\"\n />\n </ng-container>\n</ng-container>\n", styles: ["input{cursor:pointer}\n"] }]
23595
23732
  }], propDecorators: { rowIndex: [{
23596
23733
  type: Input
23734
+ }], rowData: [{
23735
+ type: Input
23597
23736
  }], testId: [{
23598
23737
  type: Input,
23599
23738
  args: ['data-test-id']
@@ -23676,11 +23815,11 @@ class QdTableRowComponent {
23676
23815
  return this.config.columns.find(column => column.column === item)?.type || 'blank';
23677
23816
  }
23678
23817
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23679
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableRowComponent, isStandalone: false, selector: "[qd-table-row]", inputs: { config: "config", rowData: "rowData", rowIndex: "rowIndex", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.hostTestId" } }, providers: [QdDataFacetsContextService], ngImport: i0, template: "<td\n qd-table-row-selection\n *ngIf=\"config.selection\"\n class=\"qd-table__body-cell--selection\"\n [rowIndex]=\"rowIndex\"\n [data-test-id]=\"testId + '-cell-selection'\"\n></td>\n\n<ng-container *ngFor=\"let item of columnsDefinitions$ | async\">\n <td\n *ngIf=\"hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell--merged ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-merged'\"\n >\n <div *ngFor=\"let item of item; let first = first\">\n <ng-container *ngTemplateOutlet=\"headings; context: { item, translations, first }\"></ng-container>\n <span class=\"qd-table__body-cell\">\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </span>\n </div>\n </td>\n\n <td\n *ngIf=\"!hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-' + item\"\n >\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </td>\n</ng-container>\n\n<td\n *ngIf=\"config.secondaryActions?.length > 0\"\n qd-table-row-actions-secondary-menu\n class=\"qd-table__body-cell--actions-secondary-actions-menu\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [i18ns]=\"config.i18ns\"\n [testId]=\"testId + '-secondary-actions'\"\n [viewonly]=\"config.viewonly\"\n [attr.data-test-id]=\"testId + '-cell-secondary-actions'\"\n></td>\n\n<ng-template #headings let-item=\"item\" let-translations=\"translations\" let-first=\"first\">\n <ng-container *ngIf=\"!first && findColumnType(item) !== 'blank'\">\n <span class=\"qd-table__body-cell--merged-headings\">\n {{ translations ? (getI18n(item) | translate) : capitalize(item) }}\n </span>\n </ng-container>\n</ng-template>\n", styles: ["[qd-table-row] .qd-table__body-cell--actions-secondary-actions-menu{vertical-align:top}[qd-table-row] .qd-data-facets{display:block}[qd-table-row] qd-data-facets-progress.qd-data-facets{transform:translateY(.1875rem)}[qd-table-row] qd-data-facets-chip.qd-data-facets{display:contents}[qd-table-row] .qd-table__body-cell--merged qd-data-facets-text{padding-top:0}\n"], dependencies: [{ kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QdTableRowActionsSecondaryMenuComponent, selector: "[qd-table-row-actions-secondary-menu]", inputs: ["rowIndex", "rowData", "i18ns", "testId", "viewonly"] }, { kind: "component", type: QdTableRowSelectionComponent, selector: "[qd-table-row-selection]", inputs: ["rowIndex", "data-test-id"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
23818
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableRowComponent, isStandalone: false, selector: "[qd-table-row]", inputs: { config: "config", rowData: "rowData", rowIndex: "rowIndex", testId: ["data-test-id", "testId"] }, host: { properties: { "attr.data-test-id": "this.hostTestId" } }, providers: [QdDataFacetsContextService], ngImport: i0, template: "<td\n qd-table-row-selection\n *ngIf=\"config.selection\"\n class=\"qd-table__body-cell--selection\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [data-test-id]=\"testId + '-cell-selection'\"\n></td>\n\n<ng-container *ngFor=\"let item of columnsDefinitions$ | async\">\n <td\n *ngIf=\"hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell--merged ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-merged'\"\n >\n <div *ngFor=\"let item of item; let first = first\">\n <ng-container *ngTemplateOutlet=\"headings; context: { item, translations, first }\"></ng-container>\n <span class=\"qd-table__body-cell\">\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </span>\n </div>\n </td>\n\n <td\n *ngIf=\"!hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-' + item\"\n >\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </td>\n</ng-container>\n\n<td\n *ngIf=\"config.secondaryActions?.length > 0\"\n qd-table-row-actions-secondary-menu\n class=\"qd-table__body-cell--actions-secondary-actions-menu\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [i18ns]=\"config.i18ns\"\n [testId]=\"testId + '-secondary-actions'\"\n [viewonly]=\"config.viewonly\"\n [attr.data-test-id]=\"testId + '-cell-secondary-actions'\"\n></td>\n\n<ng-template #headings let-item=\"item\" let-translations=\"translations\" let-first=\"first\">\n <ng-container *ngIf=\"!first && findColumnType(item) !== 'blank'\">\n <span class=\"qd-table__body-cell--merged-headings\">\n {{ translations ? (getI18n(item) | translate) : capitalize(item) }}\n </span>\n </ng-container>\n</ng-template>\n", styles: ["[qd-table-row] .qd-table__body-cell--actions-secondary-actions-menu{vertical-align:top}[qd-table-row] .qd-data-facets{display:block}[qd-table-row] qd-data-facets-progress.qd-data-facets{transform:translateY(.1875rem)}[qd-table-row] qd-data-facets-chip.qd-data-facets{display:contents}[qd-table-row] .qd-table__body-cell--merged qd-data-facets-text{padding-top:0}\n"], dependencies: [{ kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QdTableRowActionsSecondaryMenuComponent, selector: "[qd-table-row-actions-secondary-menu]", inputs: ["rowIndex", "rowData", "i18ns", "testId", "viewonly"] }, { kind: "component", type: QdTableRowSelectionComponent, selector: "[qd-table-row-selection]", inputs: ["rowIndex", "rowData", "data-test-id"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
23680
23819
  }
23681
23820
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowComponent, decorators: [{
23682
23821
  type: Component,
23683
- args: [{ selector: '[qd-table-row]', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [QdDataFacetsContextService], standalone: false, template: "<td\n qd-table-row-selection\n *ngIf=\"config.selection\"\n class=\"qd-table__body-cell--selection\"\n [rowIndex]=\"rowIndex\"\n [data-test-id]=\"testId + '-cell-selection'\"\n></td>\n\n<ng-container *ngFor=\"let item of columnsDefinitions$ | async\">\n <td\n *ngIf=\"hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell--merged ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-merged'\"\n >\n <div *ngFor=\"let item of item; let first = first\">\n <ng-container *ngTemplateOutlet=\"headings; context: { item, translations, first }\"></ng-container>\n <span class=\"qd-table__body-cell\">\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </span>\n </div>\n </td>\n\n <td\n *ngIf=\"!hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-' + item\"\n >\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </td>\n</ng-container>\n\n<td\n *ngIf=\"config.secondaryActions?.length > 0\"\n qd-table-row-actions-secondary-menu\n class=\"qd-table__body-cell--actions-secondary-actions-menu\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [i18ns]=\"config.i18ns\"\n [testId]=\"testId + '-secondary-actions'\"\n [viewonly]=\"config.viewonly\"\n [attr.data-test-id]=\"testId + '-cell-secondary-actions'\"\n></td>\n\n<ng-template #headings let-item=\"item\" let-translations=\"translations\" let-first=\"first\">\n <ng-container *ngIf=\"!first && findColumnType(item) !== 'blank'\">\n <span class=\"qd-table__body-cell--merged-headings\">\n {{ translations ? (getI18n(item) | translate) : capitalize(item) }}\n </span>\n </ng-container>\n</ng-template>\n", styles: ["[qd-table-row] .qd-table__body-cell--actions-secondary-actions-menu{vertical-align:top}[qd-table-row] .qd-data-facets{display:block}[qd-table-row] qd-data-facets-progress.qd-data-facets{transform:translateY(.1875rem)}[qd-table-row] qd-data-facets-chip.qd-data-facets{display:contents}[qd-table-row] .qd-table__body-cell--merged qd-data-facets-text{padding-top:0}\n"] }]
23822
+ args: [{ selector: '[qd-table-row]', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [QdDataFacetsContextService], standalone: false, template: "<td\n qd-table-row-selection\n *ngIf=\"config.selection\"\n class=\"qd-table__body-cell--selection\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [data-test-id]=\"testId + '-cell-selection'\"\n></td>\n\n<ng-container *ngFor=\"let item of columnsDefinitions$ | async\">\n <td\n *ngIf=\"hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell--merged ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-merged'\"\n >\n <div *ngFor=\"let item of item; let first = first\">\n <ng-container *ngTemplateOutlet=\"headings; context: { item, translations, first }\"></ng-container>\n <span class=\"qd-table__body-cell\">\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </span>\n </div>\n </td>\n\n <td\n *ngIf=\"!hasMergedColumns(item)\"\n [class]=\"'qd-table__body-cell ' + getCellClass(item)\"\n [attr.data-test-id]=\"testId + '-cell-' + item\"\n >\n <ng-container *ngComponentOutlet=\"getComponentType(item); inputs: getComponentInputs(item)\"></ng-container>\n </td>\n</ng-container>\n\n<td\n *ngIf=\"config.secondaryActions?.length > 0\"\n qd-table-row-actions-secondary-menu\n class=\"qd-table__body-cell--actions-secondary-actions-menu\"\n [rowIndex]=\"rowIndex\"\n [rowData]=\"rowData\"\n [i18ns]=\"config.i18ns\"\n [testId]=\"testId + '-secondary-actions'\"\n [viewonly]=\"config.viewonly\"\n [attr.data-test-id]=\"testId + '-cell-secondary-actions'\"\n></td>\n\n<ng-template #headings let-item=\"item\" let-translations=\"translations\" let-first=\"first\">\n <ng-container *ngIf=\"!first && findColumnType(item) !== 'blank'\">\n <span class=\"qd-table__body-cell--merged-headings\">\n {{ translations ? (getI18n(item) | translate) : capitalize(item) }}\n </span>\n </ng-container>\n</ng-template>\n", styles: ["[qd-table-row] .qd-table__body-cell--actions-secondary-actions-menu{vertical-align:top}[qd-table-row] .qd-data-facets{display:block}[qd-table-row] qd-data-facets-progress.qd-data-facets{transform:translateY(.1875rem)}[qd-table-row] qd-data-facets-chip.qd-data-facets{display:contents}[qd-table-row] .qd-table__body-cell--merged qd-data-facets-text{padding-top:0}\n"] }]
23684
23823
  }], propDecorators: { config: [{
23685
23824
  type: Input
23686
23825
  }], rowData: [{
@@ -23698,20 +23837,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
23698
23837
  class QdTableBodyComponent {
23699
23838
  tableStoreService = inject(QdTableStoreService);
23700
23839
  resolverService = inject(QdTableResolverService);
23840
+ cdr = inject(ChangeDetectorRef);
23701
23841
  config;
23702
23842
  data;
23703
23843
  testId;
23704
23844
  isLoading$;
23845
+ highlightedRowIdentifier;
23705
23846
  _primaryActionRefreshSubscription;
23706
23847
  constructor() {
23707
23848
  this.isLoading$ = this.tableStoreService.selectIsLoading$().pipe(debounceTime$1(400));
23708
23849
  }
23850
+ ngOnInit() {
23851
+ this.initHighlight();
23852
+ }
23709
23853
  ngOnDestroy() {
23710
23854
  this._primaryActionRefreshSubscription?.unsubscribe();
23711
23855
  }
23856
+ isLastVisitedRow(rowData, rowIndex) {
23857
+ if (this.highlightedRowIdentifier === undefined)
23858
+ return false;
23859
+ return resolveRowIdentifier(rowData, rowIndex, this.config.trackRowBy) === this.highlightedRowIdentifier;
23860
+ }
23712
23861
  primaryAction(rowData, index) {
23713
23862
  if (!this.config.primaryAction?.handler)
23714
23863
  return console.warn('QD-UI | QdTable - No handler defined for primary action.');
23864
+ if (this.config.primaryAction?.highlightOnRevisit) {
23865
+ this.tableStoreService.setLastVisitedRow(resolveRowIdentifier(rowData, index, this.config.trackRowBy));
23866
+ }
23715
23867
  const result = this.config.primaryAction?.handler({ rowData, index });
23716
23868
  if (!this.config.primaryAction?.refresh?.isEnabled)
23717
23869
  return;
@@ -23721,16 +23873,28 @@ class QdTableBodyComponent {
23721
23873
  }
23722
23874
  this.triggerRefresh();
23723
23875
  }
23876
+ initHighlight() {
23877
+ this.tableStoreService
23878
+ .lastVisitedRowIdentifier$()
23879
+ .pipe(take(1))
23880
+ .subscribe(id => {
23881
+ if (id === undefined)
23882
+ return;
23883
+ this.highlightedRowIdentifier = id;
23884
+ this.tableStoreService.clearLastVisitedRow();
23885
+ this.cdr.markForCheck();
23886
+ });
23887
+ }
23724
23888
  triggerRefresh() {
23725
23889
  const pageIndex = this.config.primaryAction?.refresh?.page;
23726
23890
  this.resolverService.refresh(pageIndex !== undefined ? { pageIndex } : undefined);
23727
23891
  }
23728
23892
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23729
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableBodyComponent, isStandalone: false, selector: "[qd-table-body]", inputs: { config: "config", data: "data", testId: ["data-test-id", "testId"] }, host: { classAttribute: "qd-table__body qd-table__body--plain" }, ngImport: i0, template: "<div class=\"loading-overlay\" *ngIf=\"isLoading$ | async\">\n <qd-spinner></qd-spinner>\n</div>\n<ng-container *ngFor=\"let rowData of data; index as rowIndex\">\n <tr\n qd-table-row\n [rowData]=\"rowData\"\n [rowIndex]=\"rowIndex\"\n [config]=\"config\"\n [class]=\"'qd-table__body-row'\"\n [data-test-id]=\"testId + '-row-' + rowIndex\"\n (click)=\"config.primaryAction && primaryAction(rowData, rowIndex)\"\n [class.hasAction]=\"config.primaryAction\"\n ></tr>\n</ng-container>\n", styles: [".qd-table__body--plain{position:relative}.qd-table__body--plain td{border-bottom:.0625rem solid rgb(213,213,213)}.qd-table__body--plain td.qd-table__body-cell--selection{padding-left:1rem;vertical-align:top}.qd-table__body--plain td>div{display:flex;align-items:stretch;line-height:1.5rem}.qd-table__body--plain td>div:first-child{font-weight:500;line-height:1.5rem}.qd-table__body--plain td>div:not(:first-child) .qd-table__body-cell{padding:0 .5rem}.qd-table__body--plain td.qd-table__body-cell--merged{padding:.5rem 0;vertical-align:top}.qd-table__body--plain td .qd-table__body-cell--merged-headings{width:33%;padding-left:1rem;color:#757575;overflow-wrap:break-word}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell{width:66%}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell>*{overflow-wrap:break-word;text-align:left}@media (max-width: 959.98px){.qd-table__body--plain td.qd-table__body-cell--merged{display:grid;max-width:100%;grid-template-columns:fit-content(66%) 1fr}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child{display:block;font-weight:500;grid-column:1/-1;line-height:1.5rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child>span.qd-table__body-cell{display:block;width:auto;padding-left:1rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child){display:grid;grid-column:1/-1;grid-template-columns:subgrid}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell--merged-headings{display:block;overflow:hidden;width:auto;min-width:0;padding-right:0;padding-left:1rem;color:#757575;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell{display:block;overflow:hidden;width:auto;min-width:0;padding:0 .5rem;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell>*{display:block;overflow:hidden;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}}.qd-table__body--plain td span.qd-table__body-cell--boolean{display:flex;align-items:center}.qd-table__body--plain td span.qd-table__body-cell--progress{flex-grow:1;align-self:center;padding-top:0}.qd-table__body--plain td span.qd-table__body-cell--status{margin-top:-.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text{min-height:auto;padding-top:0;line-height:1.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text+.text{margin-top:.1875rem}.qd-table__body--plain tr.hasAction{cursor:pointer}.qd-table__body--plain tr.hasAction:hover{background-color:#f2f7fa}.loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\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: QdSpinnerComponent, selector: "qd-spinner" }, { kind: "component", type: QdTableRowComponent, selector: "[qd-table-row]", inputs: ["config", "rowData", "rowIndex", "data-test-id"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
23893
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdTableBodyComponent, isStandalone: false, selector: "[qd-table-body]", inputs: { config: "config", data: "data", testId: ["data-test-id", "testId"] }, host: { classAttribute: "qd-table__body qd-table__body--plain" }, ngImport: i0, template: "<div class=\"loading-overlay\" *ngIf=\"isLoading$ | async\">\n <qd-spinner></qd-spinner>\n</div>\n<ng-container *ngFor=\"let rowData of data; index as rowIndex\">\n <tr\n qd-table-row\n [rowData]=\"rowData\"\n [rowIndex]=\"rowIndex\"\n [config]=\"config\"\n [class]=\"'qd-table__body-row'\"\n [data-test-id]=\"testId + '-row-' + rowIndex\"\n (click)=\"config.primaryAction && primaryAction(rowData, rowIndex)\"\n [class.hasAction]=\"config.primaryAction\"\n [class.qd-table__body-row--last-visited]=\"isLastVisitedRow(rowData, rowIndex)\"\n ></tr>\n</ng-container>\n", styles: [".qd-table__body--plain{position:relative}.qd-table__body--plain td{border-bottom:.0625rem solid rgb(213,213,213)}.qd-table__body--plain td.qd-table__body-cell--selection{padding-left:1rem;vertical-align:top}.qd-table__body--plain td>div{display:flex;align-items:stretch;line-height:1.5rem}.qd-table__body--plain td>div:first-child{font-weight:500;line-height:1.5rem}.qd-table__body--plain td>div:not(:first-child) .qd-table__body-cell{padding:0 .5rem}.qd-table__body--plain td.qd-table__body-cell--merged{padding:.5rem 0;vertical-align:top}.qd-table__body--plain td .qd-table__body-cell--merged-headings{width:33%;padding-left:1rem;color:#757575;overflow-wrap:break-word}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell{width:66%}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell>*{overflow-wrap:break-word;text-align:left}@media (max-width: 959.98px){.qd-table__body--plain td.qd-table__body-cell--merged{display:grid;max-width:100%;grid-template-columns:fit-content(66%) 1fr}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child{display:block;font-weight:500;grid-column:1/-1;line-height:1.5rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child>span.qd-table__body-cell{display:block;width:auto;padding-left:1rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child){display:grid;grid-column:1/-1;grid-template-columns:subgrid}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell--merged-headings{display:block;overflow:hidden;width:auto;min-width:0;padding-right:0;padding-left:1rem;color:#757575;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell{display:block;overflow:hidden;width:auto;min-width:0;padding:0 .5rem;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell>*{display:block;overflow:hidden;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}}.qd-table__body--plain td span.qd-table__body-cell--boolean{display:flex;align-items:center}.qd-table__body--plain td span.qd-table__body-cell--progress{flex-grow:1;align-self:center;padding-top:0}.qd-table__body--plain td span.qd-table__body-cell--status{margin-top:-.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text{min-height:auto;padding-top:0;line-height:1.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text+.text{margin-top:.1875rem}.qd-table__body--plain tr.hasAction{cursor:pointer}.qd-table__body--plain tr.hasAction:hover{background-color:#f2f7fa}.qd-table__body--plain tr.qd-table__body-row--last-visited{animation:last-visited-fade 1.5s linear forwards}@keyframes last-visited-fade{0%,33%{background-color:#c3e8cd}to{background-color:transparent}}.loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\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: QdSpinnerComponent, selector: "qd-spinner" }, { kind: "component", type: QdTableRowComponent, selector: "[qd-table-row]", inputs: ["config", "rowData", "rowIndex", "data-test-id"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
23730
23894
  }
23731
23895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableBodyComponent, decorators: [{
23732
23896
  type: Component,
23733
- args: [{ selector: '[qd-table-body]', host: { class: 'qd-table__body qd-table__body--plain' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"loading-overlay\" *ngIf=\"isLoading$ | async\">\n <qd-spinner></qd-spinner>\n</div>\n<ng-container *ngFor=\"let rowData of data; index as rowIndex\">\n <tr\n qd-table-row\n [rowData]=\"rowData\"\n [rowIndex]=\"rowIndex\"\n [config]=\"config\"\n [class]=\"'qd-table__body-row'\"\n [data-test-id]=\"testId + '-row-' + rowIndex\"\n (click)=\"config.primaryAction && primaryAction(rowData, rowIndex)\"\n [class.hasAction]=\"config.primaryAction\"\n ></tr>\n</ng-container>\n", styles: [".qd-table__body--plain{position:relative}.qd-table__body--plain td{border-bottom:.0625rem solid rgb(213,213,213)}.qd-table__body--plain td.qd-table__body-cell--selection{padding-left:1rem;vertical-align:top}.qd-table__body--plain td>div{display:flex;align-items:stretch;line-height:1.5rem}.qd-table__body--plain td>div:first-child{font-weight:500;line-height:1.5rem}.qd-table__body--plain td>div:not(:first-child) .qd-table__body-cell{padding:0 .5rem}.qd-table__body--plain td.qd-table__body-cell--merged{padding:.5rem 0;vertical-align:top}.qd-table__body--plain td .qd-table__body-cell--merged-headings{width:33%;padding-left:1rem;color:#757575;overflow-wrap:break-word}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell{width:66%}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell>*{overflow-wrap:break-word;text-align:left}@media (max-width: 959.98px){.qd-table__body--plain td.qd-table__body-cell--merged{display:grid;max-width:100%;grid-template-columns:fit-content(66%) 1fr}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child{display:block;font-weight:500;grid-column:1/-1;line-height:1.5rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child>span.qd-table__body-cell{display:block;width:auto;padding-left:1rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child){display:grid;grid-column:1/-1;grid-template-columns:subgrid}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell--merged-headings{display:block;overflow:hidden;width:auto;min-width:0;padding-right:0;padding-left:1rem;color:#757575;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell{display:block;overflow:hidden;width:auto;min-width:0;padding:0 .5rem;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell>*{display:block;overflow:hidden;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}}.qd-table__body--plain td span.qd-table__body-cell--boolean{display:flex;align-items:center}.qd-table__body--plain td span.qd-table__body-cell--progress{flex-grow:1;align-self:center;padding-top:0}.qd-table__body--plain td span.qd-table__body-cell--status{margin-top:-.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text{min-height:auto;padding-top:0;line-height:1.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text+.text{margin-top:.1875rem}.qd-table__body--plain tr.hasAction{cursor:pointer}.qd-table__body--plain tr.hasAction:hover{background-color:#f2f7fa}.loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\n"] }]
23897
+ args: [{ selector: '[qd-table-body]', host: { class: 'qd-table__body qd-table__body--plain' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"loading-overlay\" *ngIf=\"isLoading$ | async\">\n <qd-spinner></qd-spinner>\n</div>\n<ng-container *ngFor=\"let rowData of data; index as rowIndex\">\n <tr\n qd-table-row\n [rowData]=\"rowData\"\n [rowIndex]=\"rowIndex\"\n [config]=\"config\"\n [class]=\"'qd-table__body-row'\"\n [data-test-id]=\"testId + '-row-' + rowIndex\"\n (click)=\"config.primaryAction && primaryAction(rowData, rowIndex)\"\n [class.hasAction]=\"config.primaryAction\"\n [class.qd-table__body-row--last-visited]=\"isLastVisitedRow(rowData, rowIndex)\"\n ></tr>\n</ng-container>\n", styles: [".qd-table__body--plain{position:relative}.qd-table__body--plain td{border-bottom:.0625rem solid rgb(213,213,213)}.qd-table__body--plain td.qd-table__body-cell--selection{padding-left:1rem;vertical-align:top}.qd-table__body--plain td>div{display:flex;align-items:stretch;line-height:1.5rem}.qd-table__body--plain td>div:first-child{font-weight:500;line-height:1.5rem}.qd-table__body--plain td>div:not(:first-child) .qd-table__body-cell{padding:0 .5rem}.qd-table__body--plain td.qd-table__body-cell--merged{padding:.5rem 0;vertical-align:top}.qd-table__body--plain td .qd-table__body-cell--merged-headings{width:33%;padding-left:1rem;color:#757575;overflow-wrap:break-word}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell{width:66%}.qd-table__body--plain td .qd-table__body-cell--merged-headings+.qd-table__body-cell>*{overflow-wrap:break-word;text-align:left}@media (max-width: 959.98px){.qd-table__body--plain td.qd-table__body-cell--merged{display:grid;max-width:100%;grid-template-columns:fit-content(66%) 1fr}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child{display:block;font-weight:500;grid-column:1/-1;line-height:1.5rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:first-child>span.qd-table__body-cell{display:block;width:auto;padding-left:1rem}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child){display:grid;grid-column:1/-1;grid-template-columns:subgrid}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell--merged-headings{display:block;overflow:hidden;width:auto;min-width:0;padding-right:0;padding-left:1rem;color:#757575;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell{display:block;overflow:hidden;width:auto;min-width:0;padding:0 .5rem;-webkit-hyphens:auto;hyphens:auto;line-height:1.5rem;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}.qd-table__body--plain td.qd-table__body-cell--merged>div:not(:first-child)>span.qd-table__body-cell>*{display:block;overflow:hidden;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-align:left;text-overflow:ellipsis}}.qd-table__body--plain td span.qd-table__body-cell--boolean{display:flex;align-items:center}.qd-table__body--plain td span.qd-table__body-cell--progress{flex-grow:1;align-self:center;padding-top:0}.qd-table__body--plain td span.qd-table__body-cell--status{margin-top:-.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text{min-height:auto;padding-top:0;line-height:1.5rem}.qd-table__body--plain td span.qd-table__body-cell--text .text+.text{margin-top:.1875rem}.qd-table__body--plain tr.hasAction{cursor:pointer}.qd-table__body--plain tr.hasAction:hover{background-color:#f2f7fa}.qd-table__body--plain tr.qd-table__body-row--last-visited{animation:last-visited-fade 1.5s linear forwards}@keyframes last-visited-fade{0%,33%{background-color:#c3e8cd}to{background-color:transparent}}.loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\n"] }]
23734
23898
  }], ctorParameters: () => [], propDecorators: { config: [{
23735
23899
  type: Input
23736
23900
  }], data: [{
@@ -24260,6 +24424,9 @@ class QdTableComponent {
24260
24424
  const action = this.config.secondaryActions?.find(a => a.type === recentAction.type);
24261
24425
  if (!action?.handler)
24262
24426
  return console.warn('QD-UI | QdTable - No handler defined for secondary action:', recentAction.type);
24427
+ if (action.highlightOnRevisit) {
24428
+ this.tableStoreService.setLastVisitedRow(resolveRowIdentifier(recentAction.rowData, recentAction.index, this.config.trackRowBy));
24429
+ }
24263
24430
  const result = action.handler(recentAction);
24264
24431
  if (action.refresh?.isEnabled)
24265
24432
  this.processSecondaryActionRefresh(result, action.refresh);
@@ -24273,13 +24440,14 @@ class QdTableComponent {
24273
24440
  'The table uid has to match the path of the server side event.');
24274
24441
  return;
24275
24442
  }
24276
- if (!this.pushEventsService.isConnectedOrConnecting()) {
24443
+ if (!this.pushEventsService || !this.pushEventsService.isConnectedOrConnecting()) {
24277
24444
  console.error('QD-UI | QdTable - You have to connect the QdPushEventsService on your own ' +
24278
24445
  'if you enable the refreshOnPushEvent flag in the table.');
24279
24446
  return;
24280
24447
  }
24448
+ const pushEventsService = this.pushEventsService;
24281
24449
  const events = [...(this.config?.refreshingEvents || []), 'RESOURCE_UPDATED'];
24282
- const combinedEvents$ = merge(...events.map(eventName => this.pushEventsService.observe(eventName)));
24450
+ const combinedEvents$ = merge(...events.map(eventName => pushEventsService.observe(eventName)));
24283
24451
  this._pushEventsSubscription = combinedEvents$
24284
24452
  .pipe(filter(messageEvent => {
24285
24453
  try {
@@ -24313,6 +24481,13 @@ class QdTableComponent {
24313
24481
  if (!this.config.i18ns) {
24314
24482
  console.warn('QD-UI | QdTable - No translations available for the secondaryActions.');
24315
24483
  }
24484
+ if (this.hasHighlightOnRevisit() && !this.config.uid) {
24485
+ console.warn('QD-UI | QdTable - A uid has to be defined when highlightOnRevisit is enabled. ' +
24486
+ 'Without a uid, the table state does not persist across navigation.');
24487
+ }
24488
+ }
24489
+ hasHighlightOnRevisit() {
24490
+ return (!!this.config.primaryAction?.highlightOnRevisit || !!this.config.secondaryActions?.some(a => a.highlightOnRevisit));
24316
24491
  }
24317
24492
  /**
24318
24493
  * @description Can be deleted when the property "mainColumnNotFillingWidth" is dropped.
@@ -25268,6 +25443,8 @@ const connect = (state, tableId, newConnectorCriteria, connectorName, connectorS
25268
25443
  connectors: updatedConnectors
25269
25444
  });
25270
25445
  };
25446
+ const setLastVisitedRow = (state, tableId, rowIdentifier) => updateStateWithNewTable(state, tableId, { ...state[tableId], lastVisitedRowIdentifier: rowIdentifier });
25447
+ const clearLastVisitedRow = (state, tableId) => updateStateWithNewTable(state, tableId, { ...state[tableId], lastVisitedRowIdentifier: undefined });
25271
25448
  const resetConnectorStates = (state, tableId) => {
25272
25449
  const { connectors } = state[tableId];
25273
25450
  return updateStateWithNewTable(state, tableId, {
@@ -25295,10 +25472,9 @@ const _tableReducer = createReducer(initialState, on(QdTableActions.initTableSta
25295
25472
  if (state[tableId].selectedRows)
25296
25473
  return state;
25297
25474
  return updateTableStateSelectedRows(state, tableId, selectedRows);
25298
- }), on(QdTableActions.updateTableStateData, (state, { tableId, data }) => updateTableStateData(state, tableId, data)), on(QdTableActions.updateTableStateRowToSelected, (state, { tableId, rowIndex }) => updateTableStateSelectedRows(state, tableId, [
25299
- ...(state[tableId].selectedRows ?? []),
25300
- state[tableId].data[rowIndex].uid || rowIndex
25301
- ])), on(QdTableActions.updateTableStateSingleRowToSelected, (state, { tableId, rowIndex }) => updateTableStateSelectedRows(state, tableId, [state[tableId].data[rowIndex].uid || rowIndex])), on(QdTableActions.updateTableStateRowToUnselected, (state, { tableId, rowIndex }) => updateTableStateSelectedRows(state, tableId, (state[tableId].selectedRows ?? []).filter(rowIdentifier => rowIdentifier !== rowIndex && rowIdentifier !== state[tableId].data[rowIndex].uid))), on(QdTableActions.updateTableStateRecentSecondaryAction, (state, { tableId, recentSecondaryAction: recentSecondaryAction }) => updateTableStateRecentSecondaryAction(state, tableId, recentSecondaryAction)), on(QdTableActions.setupPagination, (state, { tableId, pageSize }) => setupPagination(state, tableId, pageSize)), on(QdTableActions.setPage, (state, { tableId, pageIndex, pageSize, totalCount, data }) => setPage(state, tableId, pageIndex, pageSize, totalCount, data)), on(QdTableActions.setPageSize, (state, { tableId, pageSize }) => setPageSize(state, tableId, pageSize)), on(QdTableActions.setPageParams, (state, { tableId, pageIndex, pageSize, totalCount }) => setPageParams(state, tableId, pageIndex, pageSize, totalCount)), on(QdTableActions.setupSort, (state, { tableId, columns }) => setupSort(state, tableId, columns)), on(QdTableActions.setSort, (state, { tableId, column, direction }) => setSort(state, tableId, column, direction)), on(QdTableActions.updateSort, (state, { tableId, column, direction }) => updateSort(state, tableId, column, direction)), on(QdTableActions.setRequestState, (state, { tableId, requestState }) => setRequestState(state, tableId, requestState)), on(QdTableActions.connect, (state, { tableId, connectorName, connectorCriteria, connectorState }) => connect(state, tableId, connectorCriteria, connectorName, connectorState)), on(QdTableActions.resetConnectorStates, (state, { tableId }) => resetConnectorStates(state, tableId)), on(QdTableActions.deleteTableState, (state, { tableId }) => omit({ ...state }, tableId)));
25475
+ }),
25476
+ // TODO: Remove QdTreeData references from table — Tree has been extracted into its own component
25477
+ on(QdTableActions.updateTableStateData, (state, { tableId, data }) => updateTableStateData(state, tableId, data)), on(QdTableActions.updateTableStateRowToSelected, (state, { tableId, rowIdentifier }) => updateTableStateSelectedRows(state, tableId, [...(state[tableId].selectedRows ?? []), rowIdentifier])), on(QdTableActions.updateTableStateSingleRowToSelected, (state, { tableId, rowIdentifier }) => updateTableStateSelectedRows(state, tableId, [rowIdentifier])), on(QdTableActions.updateTableStateRowToUnselected, (state, { tableId, rowIdentifier }) => updateTableStateSelectedRows(state, tableId, (state[tableId].selectedRows ?? []).filter(id => id !== rowIdentifier))), on(QdTableActions.updateTableStateRecentSecondaryAction, (state, { tableId, recentSecondaryAction: recentSecondaryAction }) => updateTableStateRecentSecondaryAction(state, tableId, recentSecondaryAction)), on(QdTableActions.setupPagination, (state, { tableId, pageSize }) => setupPagination(state, tableId, pageSize)), on(QdTableActions.setPage, (state, { tableId, pageIndex, pageSize, totalCount, data }) => setPage(state, tableId, pageIndex, pageSize, totalCount, data)), on(QdTableActions.setPageSize, (state, { tableId, pageSize }) => setPageSize(state, tableId, pageSize)), on(QdTableActions.setPageParams, (state, { tableId, pageIndex, pageSize, totalCount }) => setPageParams(state, tableId, pageIndex, pageSize, totalCount)), on(QdTableActions.setupSort, (state, { tableId, columns }) => setupSort(state, tableId, columns)), on(QdTableActions.setSort, (state, { tableId, column, direction }) => setSort(state, tableId, column, direction)), on(QdTableActions.updateSort, (state, { tableId, column, direction }) => updateSort(state, tableId, column, direction)), on(QdTableActions.setRequestState, (state, { tableId, requestState }) => setRequestState(state, tableId, requestState)), on(QdTableActions.connect, (state, { tableId, connectorName, connectorCriteria, connectorState }) => connect(state, tableId, connectorCriteria, connectorName, connectorState)), on(QdTableActions.resetConnectorStates, (state, { tableId }) => resetConnectorStates(state, tableId)), on(QdTableActions.setLastVisitedRow, (state, { tableId, rowIdentifier }) => setLastVisitedRow(state, tableId, rowIdentifier)), on(QdTableActions.clearLastVisitedRow, (state, { tableId }) => clearLastVisitedRow(state, tableId)), on(QdTableActions.deleteTableState, (state, { tableId }) => omit({ ...state }, tableId)));
25302
25478
  function tableReducer(state, action) {
25303
25479
  return _tableReducer(state, action);
25304
25480
  }
@@ -26928,8 +27104,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
26928
27104
  class QdFormGroupManagerService {
26929
27105
  _formGroups = new Map();
26930
27106
  _formGroupsSnapshot = new Map();
27107
+ _formGroupsChanged$ = new BehaviorSubject(undefined);
26931
27108
  setFormGroup(key, formGroup) {
26932
27109
  this._formGroups.set(key, formGroup);
27110
+ this._formGroupsChanged$.next();
26933
27111
  }
26934
27112
  getFormGroup(key) {
26935
27113
  return this._formGroups.get(key);
@@ -26939,6 +27117,7 @@ class QdFormGroupManagerService {
26939
27117
  }
26940
27118
  tryRemoveFormGroup(key) {
26941
27119
  this._formGroups.delete(key);
27120
+ this._formGroupsChanged$.next();
26942
27121
  }
26943
27122
  hasFormGroups() {
26944
27123
  return this._formGroups.size > 0;
@@ -26952,19 +27131,24 @@ class QdFormGroupManagerService {
26952
27131
  return !this._formGroups.has(key);
26953
27132
  }
26954
27133
  $areFormGroupsValid() {
26955
- if (!this.hasFormGroups())
26956
- return of(false);
26957
- const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
26958
- 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
+ }));
26959
27140
  }
26960
27141
  $hasValuesChanged() {
26961
- if (!this.hasFormGroups())
26962
- return of(false);
26963
- const obs = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(fg.getRawValue()), map(currentValues => {
26964
- const snapshot = this._formGroupsSnapshot.get(key);
26965
- return snapshot ? !isEqual$1(currentValues, snapshot) : true;
26966
- })));
26967
- 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
+ }));
26968
27152
  }
26969
27153
  takeFormGroupsSnapshot() {
26970
27154
  this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
@@ -26984,6 +27168,43 @@ class QdFormGroupManagerService {
26984
27168
  }
26985
27169
  });
26986
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;
26987
27208
  }
26988
27209
  resetFormArrayToValues(array, newValues) {
26989
27210
  if (!Array.isArray(newValues))
@@ -27108,6 +27329,7 @@ class QdPageObjectHeaderComponent {
27108
27329
  _pageObjectDataSubject = new BehaviorSubject({});
27109
27330
  _isLoadingSubject = new BehaviorSubject(false);
27110
27331
  _customActionsSubject = new BehaviorSubject({ actions: [] });
27332
+ _customActionsSub;
27111
27333
  _destroyed$ = new Subject();
27112
27334
  _availableContexts = 0;
27113
27335
  pageObjectData$ = this._pageObjectDataSubject.asObservable();
@@ -27238,14 +27460,18 @@ class QdPageObjectHeaderComponent {
27238
27460
  .$hasValuesChanged()
27239
27461
  .pipe(take(1))
27240
27462
  .subscribe(hasChanged => {
27241
- if (hasChanged)
27463
+ if (hasChanged) {
27242
27464
  this.openCancelDialog();
27243
- else
27465
+ }
27466
+ else {
27467
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27244
27468
  this.pageStoreService.toggleViewonly(true);
27469
+ }
27245
27470
  });
27246
27471
  }
27247
27472
  save() {
27248
27473
  const handleSuccess = () => {
27474
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27249
27475
  this.pageStoreService.toggleViewonly(true);
27250
27476
  this.formGroupManagerService.takeFormGroupsSnapshot();
27251
27477
  };
@@ -27274,7 +27500,7 @@ class QdPageObjectHeaderComponent {
27274
27500
  setupResolverTrigger() {
27275
27501
  this.resolverTriggerService
27276
27502
  .shouldTriggerResolver(this.pageObjectResolver.config?.triggerOn ?? 'pathParamsChange')
27277
- .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()))
27278
27504
  .subscribe();
27279
27505
  }
27280
27506
  initContexts() {
@@ -27293,17 +27519,32 @@ class QdPageObjectHeaderComponent {
27293
27519
  .subscribe();
27294
27520
  }
27295
27521
  updateCustomActions() {
27296
- if (this.config.pageType === 'inspect') {
27297
- const operationMode = this.config.pageTypeConfig?.operationMode;
27298
- if (operationMode)
27299
- this.pageStoreService.toggleViewonly(operationMode === 'view');
27300
- this.pageStoreService.isViewonly$.pipe(takeUntil(this._destroyed$)).subscribe(isViewonly => {
27301
- const customActions = this.config.pageTypeConfig?.customActions ?? [];
27302
- this._customActionsSubject.next(this.getCustomActionsByMode(customActions, isViewonly ? 'view' : 'edit'));
27303
- });
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;
27304
27527
  }
27305
- if (this.config.pageType !== 'inspect')
27306
- this._customActionsSubject.next({ actions: this.config.pageTypeConfig?.customActions ?? [] });
27528
+ // 2. Setup Inspect-specific logic
27529
+ const config = this.config.pageTypeConfig;
27530
+ if (config?.operationMode) {
27531
+ this.pageStoreService.toggleViewonly(config.operationMode === 'view');
27532
+ }
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
+ });
27307
27548
  }
27308
27549
  getCustomActionsByMode(customActions, mode) {
27309
27550
  const actions = customActions
@@ -28302,6 +28543,7 @@ class QdPageSubmitActionService {
28302
28543
  _submitHandler;
28303
28544
  _isVisible = true;
28304
28545
  _destroyed$ = new Subject();
28546
+ _cancelTrackFormValidity$ = new Subject();
28305
28547
  ngOnDestroy() {
28306
28548
  this._destroyed$.next(null);
28307
28549
  this._destroyed$.complete();
@@ -28340,9 +28582,10 @@ class QdPageSubmitActionService {
28340
28582
  };
28341
28583
  }
28342
28584
  trackFormValidity() {
28585
+ this._cancelTrackFormValidity$.next();
28343
28586
  this.formGroupManagerService
28344
28587
  .$areFormGroupsValid()
28345
- .pipe(takeUntil(this._destroyed$), distinctUntilChanged())
28588
+ .pipe(takeUntil(this._cancelTrackFormValidity$), takeUntil(this._destroyed$), distinctUntilChanged())
28346
28589
  .subscribe(isValid => this.updateSubmitButtonState(isValid));
28347
28590
  }
28348
28591
  updateSubmitButtonState(isValid) {
@@ -28780,6 +29023,7 @@ class QdPageComponent {
28780
29023
  projectionGuardMessage = 'QD-UI | QdPage - This content is not supported. Please use QdSectionAdapterDirective for custom content projection.';
28781
29024
  _isInitialized = false;
28782
29025
  _destroyed$ = new Subject();
29026
+ _cancelSubmitValidation$ = new Subject();
28783
29027
  get isControlPanelVisible() {
28784
29028
  return this.controlPanel !== undefined && this.controlPanel.config?.isHidden !== true;
28785
29029
  }
@@ -28894,6 +29138,10 @@ class QdPageComponent {
28894
29138
  ]);
28895
29139
  }
28896
29140
  updateInspectPageOperationMode(pageTypeConfig, mode) {
29141
+ if (mode === 'view') {
29142
+ this.formGroupManagerService.cancelPendingAsyncValidation();
29143
+ setTimeout(() => this.formGroupManagerService.cancelPendingAsyncValidation());
29144
+ }
28897
29145
  if (pageTypeConfig?.submit) {
28898
29146
  const isVisible = mode === 'view' && !pageTypeConfig.submit.isHidden;
28899
29147
  this.submitActionService.setSubmitActionForInspect(pageTypeConfig, isVisible);
@@ -28921,9 +29169,10 @@ class QdPageComponent {
28921
29169
  .subscribe();
28922
29170
  }
28923
29171
  initSubmitValidation() {
29172
+ this._cancelSubmitValidation$.next();
28924
29173
  this.formGroupManagerService
28925
29174
  .$areFormGroupsValid()
28926
- .pipe(takeUntil(this._destroyed$), tap(isValid => {
29175
+ .pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
28927
29176
  const submitDisabledInfoText = this.config.pageType === 'inspect' ? this.config.pageTypeConfig?.submit?.disabledInfo : undefined;
28928
29177
  this.footerService.updateActions([
28929
29178
  {