@quadrel-enterprise-ui/framework 20.5.0 → 20.6.1-beta.127.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 {
@@ -6029,16 +6046,20 @@ class QdDataFacetsCurrencyComponent {
6029
6046
  return '';
6030
6047
  // Swiss speciality with a period instead of a comma
6031
6048
  const localeWithSuffix = ['de', 'fr', 'it'].includes(lang) ? `${lang}-CH` : lang;
6049
+ // Normalize U+0027 (apostrophe) to U+2019 (right single quotation mark), which is the
6050
+ // correct Swiss thousands separator. Older ICU versions (used in some CI environments)
6051
+ // incorrectly emit U+0027 instead.
6052
+ const normalize = (s) => s.replace(/\u0027/g, '\u2019');
6032
6053
  if (this.config?.showCurrencyUnit) {
6033
- return new Intl.NumberFormat(localeWithSuffix, {
6054
+ return normalize(new Intl.NumberFormat(localeWithSuffix, {
6034
6055
  style: 'currency',
6035
6056
  currency: this.config?.currency ?? 'chf'
6036
- }).format(this.data);
6057
+ }).format(this.data));
6037
6058
  }
6038
- return new Intl.NumberFormat(localeWithSuffix, {
6059
+ return normalize(new Intl.NumberFormat(localeWithSuffix, {
6039
6060
  minimumFractionDigits: 2,
6040
6061
  maximumFractionDigits: 2
6041
- }).format(this.data);
6062
+ }).format(this.data));
6042
6063
  }
6043
6064
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDataFacetsCurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6044
6065
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDataFacetsCurrencyComponent, isStandalone: false, selector: "qd-data-facets-currency", inputs: { config: "config", data: "data", testId: "testId" }, host: { properties: { "attr.data-test-id": "testId" }, classAttribute: "qd-data-facets" }, usesOnChanges: true, ngImport: i0, template: "{{ currency$ | async }}\n", styles: [":host{text-align:right}\n"], dependencies: [{ kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -17345,7 +17366,13 @@ class QdPageStoreService {
17345
17366
  }
17346
17367
  initPageState(config) {
17347
17368
  this.config = config;
17348
- this.config?.pageType === 'inspect' ? this.toggleViewonly(true) : this.toggleViewonly(false);
17369
+ if (this.config?.pageType === 'inspect') {
17370
+ const operationMode = this.config.pageTypeConfig?.operationMode;
17371
+ this.toggleViewonly(operationMode !== 'edit');
17372
+ }
17373
+ else {
17374
+ this.toggleViewonly(false);
17375
+ }
17349
17376
  }
17350
17377
  getConfig() {
17351
17378
  return this.config;
@@ -18305,7 +18332,6 @@ var QdUploadErrorType;
18305
18332
  QdUploadErrorType["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
18306
18333
  })(QdUploadErrorType || (QdUploadErrorType = {}));
18307
18334
 
18308
- // @ts-strict-ignore
18309
18335
  class QdFileCollectorValidationService {
18310
18336
  _config;
18311
18337
  get allowedMimeTypes() {
@@ -18342,7 +18368,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18342
18368
  type: Injectable
18343
18369
  }] });
18344
18370
 
18345
- // @ts-strict-ignore
18346
18371
  class QdFileCollectorService {
18347
18372
  fileManager = inject(QD_FILE_MANAGER_TOKEN);
18348
18373
  fileCollectorValidationService = inject(QdFileCollectorValidationService);
@@ -18386,8 +18411,8 @@ class QdFileCollectorService {
18386
18411
  }
18387
18412
  if (uploadProgress?.error) {
18388
18413
  fileUpload.error = {
18389
- type: QdUploadErrorType.UNKNOWN_SERVER_ERROR,
18390
- ...uploadProgress.error
18414
+ ...uploadProgress.error,
18415
+ type: uploadProgress.error.type ?? QdUploadErrorType.UNKNOWN_SERVER_ERROR
18391
18416
  };
18392
18417
  }
18393
18418
  this.changeDetectorRef.detectChanges();
@@ -18403,7 +18428,10 @@ class QdFileCollectorService {
18403
18428
  this.fileManager.delete(collectedFile).subscribe(deleted => {
18404
18429
  if (!deleted)
18405
18430
  return;
18406
- this.fileUploads.splice(this.fileUploads.findIndex(fileUpload => fileUpload.collectedFile === collectedFile), 1);
18431
+ const index = this.fileUploads.findIndex(fileUpload => fileUpload.collectedFile === collectedFile);
18432
+ if (index === -1)
18433
+ return;
18434
+ this.fileUploads.splice(index, 1);
18407
18435
  this.changeDetectorRef.detectChanges();
18408
18436
  });
18409
18437
  }
@@ -18452,7 +18480,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18452
18480
  }]
18453
18481
  }] });
18454
18482
 
18455
- // @ts-strict-ignore
18456
18483
  class QdFileCollectorAllowedFilesDescriptionComponent {
18457
18484
  fileCollectorService = inject(QdFileCollectorService);
18458
18485
  get allowedFileTypes() {
@@ -18487,10 +18514,13 @@ var QdUploadProgressState;
18487
18514
  QdUploadProgressState["ERROR"] = "error";
18488
18515
  })(QdUploadProgressState || (QdUploadProgressState = {}));
18489
18516
 
18490
- // @ts-strict-ignore
18491
18517
  class QdFileCollectorDialogItemProgressComponent {
18492
18518
  progress = 0;
18493
18519
  error;
18520
+ testId;
18521
+ get hostTestId() {
18522
+ return this.testId;
18523
+ }
18494
18524
  _uploadStateSubject = new BehaviorSubject(QdUploadProgressState.STARTING);
18495
18525
  _destroyed$ = new Subject();
18496
18526
  uploadState$ = this._uploadStateSubject.asObservable();
@@ -18533,7 +18563,7 @@ class QdFileCollectorDialogItemProgressComponent {
18533
18563
  }
18534
18564
  QdUploadState = QdUploadProgressState;
18535
18565
  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 });
18566
+ 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
18567
  }
18538
18568
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemProgressComponent, decorators: [{
18539
18569
  type: Component,
@@ -18542,12 +18572,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18542
18572
  type: Input
18543
18573
  }], error: [{
18544
18574
  type: Input
18575
+ }], testId: [{
18576
+ type: Input,
18577
+ args: [{ required: true, alias: 'data-test-id' }]
18578
+ }], hostTestId: [{
18579
+ type: HostBinding,
18580
+ args: ['attr.data-test-id']
18545
18581
  }] } });
18546
18582
 
18547
- // @ts-strict-ignore
18548
18583
  class QdFileCollectorDialogItemErrorComponent {
18549
18584
  fileCollectorService = inject(QdFileCollectorService);
18550
18585
  error;
18586
+ testId;
18587
+ get hostTestId() {
18588
+ return this.testId;
18589
+ }
18551
18590
  QdUploadErrorType = QdUploadErrorType;
18552
18591
  get maxFileSizeInBytes() {
18553
18592
  return this.config?.maxFileSizeInBytes;
@@ -18562,41 +18601,53 @@ class QdFileCollectorDialogItemErrorComponent {
18562
18601
  return this.fileCollectorService.config;
18563
18602
  }
18564
18603
  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 });
18604
+ 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
18605
  }
18567
18606
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemErrorComponent, decorators: [{
18568
18607
  type: Component,
18569
18608
  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
18609
  }], propDecorators: { error: [{
18571
18610
  type: Input
18611
+ }], testId: [{
18612
+ type: Input,
18613
+ args: [{ required: true, alias: 'data-test-id' }]
18614
+ }], hostTestId: [{
18615
+ type: HostBinding,
18616
+ args: ['attr.data-test-id']
18572
18617
  }] } });
18573
18618
 
18574
- // @ts-strict-ignore
18575
18619
  class QdFileCollectorDialogItemComponent {
18576
18620
  progress;
18577
18621
  collectedFile;
18578
18622
  error;
18623
+ testId;
18624
+ get hostTestId() {
18625
+ return this.testId;
18626
+ }
18579
18627
  get isSuccessful() {
18580
18628
  return !this.error && this.progress === 100;
18581
18629
  }
18582
18630
  get hasError() {
18583
18631
  return !!this.error;
18584
18632
  }
18585
- canUpload() {
18586
- return false; // TODO discuss whether we want this feature; return !!this.error;
18587
- }
18588
18633
  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 });
18634
+ 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
18635
  }
18591
18636
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogItemComponent, decorators: [{
18592
18637
  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"] }]
18638
+ 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
18639
  }], propDecorators: { progress: [{
18595
18640
  type: Input
18596
18641
  }], collectedFile: [{
18597
18642
  type: Input
18598
18643
  }], error: [{
18599
18644
  type: Input
18645
+ }], testId: [{
18646
+ type: Input,
18647
+ args: [{ required: true, alias: 'data-test-id' }]
18648
+ }], hostTestId: [{
18649
+ type: HostBinding,
18650
+ args: ['attr.data-test-id']
18600
18651
  }], isSuccessful: [{
18601
18652
  type: HostBinding,
18602
18653
  args: ['class.success']
@@ -18622,17 +18673,17 @@ class QdFileCollectorDialogComponent {
18622
18673
  this.dialogRef.close(this.fileUploads);
18623
18674
  }
18624
18675
  isCloseButtonDisabled() {
18625
- return !this.fileUploads.every(this._isFileUploadCompleted);
18676
+ return !this.fileUploads.every(this.isFileUploadCompleted);
18626
18677
  }
18627
- _isFileUploadCompleted(fileUpload) {
18678
+ isFileUploadCompleted(fileUpload) {
18628
18679
  return !!fileUpload.error || fileUpload.progress === 100;
18629
18680
  }
18630
18681
  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" }] });
18682
+ 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
18683
  }
18633
18684
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogComponent, decorators: [{
18634
18685
  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" }]
18686
+ 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
18687
  }], ctorParameters: () => [] });
18637
18688
 
18638
18689
  class QdSectionToolbarActionService {
@@ -18768,7 +18819,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18768
18819
  }]
18769
18820
  }] });
18770
18821
 
18771
- // @ts-strict-ignore
18772
18822
  class QdFileCollectorItemNameComponent {
18773
18823
  name;
18774
18824
  testId;
@@ -18791,7 +18841,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18791
18841
  args: ['attr.data-test-id']
18792
18842
  }] } });
18793
18843
 
18794
- // @ts-strict-ignore
18795
18844
  class QdFileCollectorItemSizeComponent {
18796
18845
  size;
18797
18846
  testId;
@@ -18828,14 +18877,13 @@ class QdFileDeleteDialogComponent {
18828
18877
  this.dialogRef.close(true);
18829
18878
  }
18830
18879
  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" }] });
18880
+ 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
18881
  }
18833
18882
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileDeleteDialogComponent, decorators: [{
18834
18883
  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"] }]
18884
+ 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
18885
  }] });
18837
18886
 
18838
- // @ts-strict-ignore
18839
18887
  class QdFileCollectorItemToolsComponent {
18840
18888
  fileCollectorService = inject(QdFileCollectorService);
18841
18889
  dialogService = inject(QdDialogService);
@@ -18843,16 +18891,13 @@ class QdFileCollectorItemToolsComponent {
18843
18891
  progress;
18844
18892
  collectedFile;
18845
18893
  error;
18846
- readonly;
18847
- viewonly;
18848
- downloadFilesWithHttpClient;
18894
+ readonly = false;
18895
+ viewonly = false;
18896
+ downloadFilesWithHttpClient = false;
18849
18897
  testId;
18850
18898
  get dataTestId() {
18851
18899
  return this.testId;
18852
18900
  }
18853
- canDownload() {
18854
- return !!this.collectedFile.downloadUrl;
18855
- }
18856
18901
  downloadFile(event) {
18857
18902
  if (!this.downloadFilesWithHttpClient)
18858
18903
  return;
@@ -18880,11 +18925,11 @@ class QdFileCollectorItemToolsComponent {
18880
18925
  });
18881
18926
  }
18882
18927
  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 });
18928
+ 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
18929
  }
18885
18930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorItemToolsComponent, decorators: [{
18886
18931
  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"] }]
18932
+ 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
18933
  }], propDecorators: { progress: [{
18889
18934
  type: Input
18890
18935
  }], collectedFile: [{
@@ -18927,7 +18972,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18927
18972
  }]
18928
18973
  }] });
18929
18974
 
18930
- // @ts-strict-ignore
18931
18975
  class QdFileCollectorUploadTimestampComponent {
18932
18976
  value;
18933
18977
  testId;
@@ -18950,15 +18994,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18950
18994
  args: ['attr.data-test-id']
18951
18995
  }] } });
18952
18996
 
18953
- // @ts-strict-ignore
18954
18997
  class QdFileCollectorItemComponent {
18955
18998
  progress;
18956
- newlyUploaded;
18999
+ newlyUploaded = false;
18957
19000
  collectedFile;
18958
19001
  error;
18959
- readonly;
18960
- viewonly;
18961
- downloadFilesWithHttpClient;
19002
+ readonly = false;
19003
+ viewonly = false;
19004
+ downloadFilesWithHttpClient = false;
18962
19005
  testId;
18963
19006
  get hostTestId() {
18964
19007
  return this.testId;
@@ -18997,88 +19040,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18997
19040
  args: ['class.newly-uploaded']
18998
19041
  }] } });
18999
19042
 
19000
- // @ts-strict-ignore
19001
19043
  /**
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.
19044
+ * **QdFileCollectorComponent** lets users view, upload, download, and remove files.
19004
19045
  *
19005
19046
  * #### **Features**
19006
19047
  *
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**
19048
+ * - **Upload**: select files, validate them, and upload with progress tracking via a custom file manager
19049
+ * - **Download**: directly via URL or through `HttpClient` (e.g. for authorization)
19050
+ * - **Delete**: with confirmation dialog
19051
+ * - **Upload Timestamp**: shows the upload date next to the file name when `uploadTimestamp` is set
19052
+ * - **Validation**: restricts file type, size, and name pattern with customizable error messages
19053
+ * - **Readonly / Viewonly**: disables upload and delete, allows only viewing and downloading
19054
+ * - **Standalone / Section**: works independently with its own add button, or inside a `QdSection` via the toolbar action
19014
19055
  *
19015
- * File interactions are managed through several key functions:
19056
+ * #### **Quick start**
19016
19057
  *
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.
19058
+ * **1. Add the collector to the template**
19020
19059
  *
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.
19060
+ * ```html
19061
+ * <qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>
19062
+ * ```
19031
19063
  *
19032
- * #### **Error Handling**
19064
+ * **2. Define the config in your component**
19033
19065
  *
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`.
19066
+ * ```typescript
19067
+ * @Component({
19068
+ * template: `<qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>`
19069
+ * })
19070
+ * export class MyComponent {
19071
+ * myFiles: QdCollectedFile[] = [
19072
+ * {
19073
+ * name: 'report.pdf',
19074
+ * size: 9432,
19075
+ * type: 'application/pdf',
19076
+ * downloadUrl: '/api/files/report.pdf',
19077
+ * uploadTimestamp: new Date()
19078
+ * }
19079
+ * ];
19038
19080
  *
19039
- * Error messages can be customized:
19040
- * - **invalidMimeTypeErrorMessageI18n**: Custom message for unsupported MIME types.
19041
- * - **invalidFileNameErrorMessageI18n**: Custom message for unsupported file names.
19081
+ * myFileCollectorConfig: QdFileCollectorConfig = {
19082
+ * files: this.myFiles
19083
+ * };
19084
+ * }
19085
+ * ```
19042
19086
  *
19043
- * #### **File Manager**
19087
+ * This is enough to show already existing files.
19044
19088
  *
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.
19089
+ * **3. Connect a file manager**
19048
19090
  *
19049
- * #### **Usage**
19091
+ * To enable upload and delete, provide a `QdFileManager` via `QD_FILE_MANAGER_TOKEN`.
19050
19092
  *
19051
19093
  * ```typescript
19052
19094
  * @Injectable()
19053
19095
  * export class MyFileManager implements QdFileManager {
19096
+ * private http = inject(HttpClient);
19097
+ *
19054
19098
  * upload(file: File): Observable<QdUploadProgress> {
19055
- * // custom implementation here
19099
+ * // Emit progress values from 0 to 100.
19100
+ * // When done, return the final QdCollectedFile from your backend response.
19101
+ * return this.http.post('/api/files', file).pipe(
19102
+ * map(response => {
19103
+ *
19104
+ * const myCollectedFile: QdCollectedFile = {
19105
+ * name: response.fileName,
19106
+ * size: response.fileSize,
19107
+ * type: response.mimeType,
19108
+ * downloadUrl: response.downloadUrl,
19109
+ * uploadTimestamp: new Date(response.storedAt)
19110
+ * };
19111
+ *
19112
+ * return { progress: 100, collectedFile: myCollectedFile };
19113
+ * })
19114
+ * );
19056
19115
  * }
19057
19116
  *
19058
19117
  * delete(collectedFile: QdCollectedFile): Observable<boolean> {
19059
- * // custom implementation here
19118
+ * return this.http.delete('/api/files/' + collectedFile.name).pipe(map(() => true));
19060
19119
  * }
19061
19120
  * }
19121
+ * ```
19062
19122
  *
19123
+ * Register the file manager in your component:
19124
+ *
19125
+ * ```typescript
19063
19126
  * @Component({
19064
- * selector: 'my-file-collector',
19065
- * providers: [
19066
- * {
19067
- * provide: QD_FILE_MANAGER_TOKEN,
19068
- * useClass: MyFileManager
19069
- * }
19070
- * ]
19127
+ * template: `<qd-file-collector [config]="myFileCollectorConfig"></qd-file-collector>`,
19128
+ * // Add the file manager here to connect the collector to your backend.
19129
+ * providers: [{ provide: QD_FILE_MANAGER_TOKEN, useClass: MyFileManager }]
19071
19130
  * })
19072
- * class MyFileCollectorComponent {
19073
- * fileCollectorConfig: QdFileCollectorConfig = {
19074
- * // configuration options here
19075
- * };
19131
+ * export class MyComponent {
19132
+ * // ...
19076
19133
  * }
19077
19134
  * ```
19078
19135
  *
19079
- * ```html
19080
- * <qd-file-collector [config]="fileCollectorConfig"></qd-file-collector>
19081
- * ```
19136
+ * #### **How the file manager works**
19137
+ *
19138
+ * The file manager connects the collector to your backend.
19139
+ *
19140
+ * It must support these two actions:
19141
+ *
19142
+ * - `upload(file)`: uploads a file and emits upload progress
19143
+ * - `delete(collectedFile)`: deletes a file and emits `true` if the deletion was successful
19144
+ *
19145
+ * For uploads:
19146
+ *
19147
+ * - emit `{ progress }` values from `0` to `100`
19148
+ * - when the upload is complete, return the final `collectedFile`
19149
+ *
19150
+ * > The example above uses a simple POST and returns one final result with `progress: 100`.
19151
+ * If you need a progress bar, use `HttpClient` with `reportProgress: true` to emit intermediate progress values.
19152
+ *
19153
+ * #### **Validation and file type rules**
19154
+ *
19155
+ * A file is rejected if:
19156
+ *
19157
+ * - its type is not allowed
19158
+ * - it is too large
19159
+ * - its name does not match the expected pattern
19160
+ *
19161
+ * You can provide custom error messages in the config.
19162
+ *
19163
+ * For file type validation, use one of these approaches:
19164
+ *
19165
+ * - `allowedFileTypes`
19166
+ * - `allowedMimeTypes` together with `allowedFileNamePattern`
19167
+ *
19168
+ * > Do not mix both approaches unless they describe the same rules.
19169
+ * > If the settings do not match, the component logs an error and does not use the config.
19170
+ *
19171
+ * #### **Upload behavior**
19172
+ *
19173
+ * Upload is triggered differently depending on where the component is used:
19174
+ *
19175
+ * - **outside a `QdSection`**: the component shows its own add button
19176
+ * - **inside a `QdSection`**: the component listens to the section action and opens the file picker when `addNew` is triggered
19177
+ *
19178
+ * In `readonly` or `viewonly` mode, upload and delete are disabled.
19179
+ *
19180
+ * If the component is connected to operation mode changes through the event broker,
19181
+ * `viewonly` can also change dynamically.
19182
+ *
19183
+ * #### **Download**
19184
+ *
19185
+ * Files can be downloaded directly from their `downloadUrl`.
19186
+ *
19187
+ * If your application needs authorization or custom download logic,
19188
+ * handle the download through your backend or file manager.
19082
19189
  */
19083
19190
  class QdFileCollectorComponent {
19084
19191
  sectionActionService = inject(QdSectionToolbarActionService, { optional: true });
@@ -19128,14 +19235,16 @@ class QdFileCollectorComponent {
19128
19235
  title: { i18n: 'i18n.qd.fileCollector.title' },
19129
19236
  viewContainerRef: this.viewContainerRef
19130
19237
  });
19131
- dialogRef.closed.subscribe((fileUploads = []) => {
19132
- fileUploads
19238
+ dialogRef.closed.subscribe((result) => {
19239
+ const fileUploads = result;
19240
+ (fileUploads ?? [])
19133
19241
  .filter(fileUpload => !fileUpload.error && fileUpload.progress === 100)
19134
19242
  .forEach(fileUpload => this.fileUploads.push({ ...fileUpload, newlyUploaded: true }));
19135
19243
  });
19136
19244
  const fileCollectorDialog = dialogRef.componentInstance;
19137
- fileCollectorDialog.uploadFiles(event.target.files);
19138
- event.target.value = '';
19245
+ const inputElement = event.target;
19246
+ fileCollectorDialog.uploadFiles(Array.from(inputElement.files ?? []));
19247
+ inputElement.value = '';
19139
19248
  }
19140
19249
  clickFileInput() {
19141
19250
  if (this.fileInput.nativeElement.disabled) {
@@ -19184,11 +19293,11 @@ class QdFileCollectorComponent {
19184
19293
  console.error(`QD-UI | QdFileCollector - ${message}`);
19185
19294
  }
19186
19295
  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" }] });
19296
+ 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
19297
  }
19189
19298
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorComponent, decorators: [{
19190
19299
  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"] }]
19300
+ 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
19301
  }], propDecorators: { config: [{
19193
19302
  type: Input
19194
19303
  }], testId: [{
@@ -22401,6 +22510,8 @@ const updateSort$1 = createAction('[QdUi Table] Update Sort', props());
22401
22510
  const setRequestState$1 = createAction('[QdUi Table] Set Loading State', props());
22402
22511
  const connect$1 = createAction('[QdUi Table]', props());
22403
22512
  const resetConnectorStates$1 = createAction('[QdUi Table] Reset Connector States', props());
22513
+ const setLastVisitedRow$1 = createAction('[QdUi Table] Set Last Visited Row', props());
22514
+ const clearLastVisitedRow$1 = createAction('[QdUi Table] Clear Last Visited Row', props());
22404
22515
  const deleteTableState = createAction('[QdUi Table] Delete Table State', props());
22405
22516
  const QdTableActions = {
22406
22517
  initTableState,
@@ -22420,6 +22531,8 @@ const QdTableActions = {
22420
22531
  setRequestState: setRequestState$1,
22421
22532
  connect: connect$1,
22422
22533
  resetConnectorStates: resetConnectorStates$1,
22534
+ setLastVisitedRow: setLastVisitedRow$1,
22535
+ clearLastVisitedRow: clearLastVisitedRow$1,
22423
22536
  deleteTableState
22424
22537
  };
22425
22538
 
@@ -22447,6 +22560,15 @@ var QdTableRequestState;
22447
22560
  QdTableRequestState[QdTableRequestState["SUCCESS"] = 1] = "SUCCESS";
22448
22561
  })(QdTableRequestState || (QdTableRequestState = {}));
22449
22562
 
22563
+ function resolveRowIdentifier(row, rowIndex, trackRowBy) {
22564
+ if (trackRowBy) {
22565
+ const value = trackRowBy(row);
22566
+ if (value !== undefined && value !== null && (typeof value === 'string' || typeof value === 'number'))
22567
+ return value;
22568
+ }
22569
+ return row.uid ?? rowIndex;
22570
+ }
22571
+
22450
22572
  class QdTableStoreSelectorService {
22451
22573
  _tableId;
22452
22574
  set tableId(tableId) {
@@ -22476,13 +22598,16 @@ class QdTableStoreSelectorService {
22476
22598
  selectedRows() {
22477
22599
  return createSelector(this.selectTables, tables => this.getTable(tables)?.selectedRows ?? []);
22478
22600
  }
22479
- isTableRowSelected(rowIndex) {
22601
+ isTableRowSelected(rowIndex, trackRowBy) {
22480
22602
  return createSelector(this.selectTables, tables => {
22481
22603
  const table = this.getTable(tables);
22482
22604
  if (!table)
22483
22605
  return false;
22484
- const tableUid = table.data[rowIndex]?.uid;
22485
- return table.selectedRows?.includes(rowIndex) || (tableUid && table.selectedRows?.includes(tableUid)) || false;
22606
+ const row = table.data[rowIndex];
22607
+ if (!row)
22608
+ return false;
22609
+ const identifier = resolveRowIdentifier(row, rowIndex, trackRowBy);
22610
+ return table.selectedRows?.includes(identifier) || false;
22486
22611
  });
22487
22612
  }
22488
22613
  recentSecondaryAction() {
@@ -22572,6 +22697,9 @@ class QdTableStoreSelectorService {
22572
22697
  };
22573
22698
  });
22574
22699
  }
22700
+ lastVisitedRowIdentifier() {
22701
+ return createSelector(this.selectTables, tables => this.getTable(tables)?.lastVisitedRowIdentifier);
22702
+ }
22575
22703
  selectTables(state) {
22576
22704
  return state.qdUiTable;
22577
22705
  }
@@ -22610,14 +22738,14 @@ class QdTableStoreService {
22610
22738
  updateTableStateData(data) {
22611
22739
  this.store.dispatch(QdTableActions.updateTableStateData({ tableId: this.tableId, data }));
22612
22740
  }
22613
- updateTableStateRowToSelected(rowIndex) {
22614
- this.store.dispatch(QdTableActions.updateTableStateRowToSelected({ tableId: this.tableId, rowIndex }));
22741
+ updateTableStateRowToSelected(rowIdentifier) {
22742
+ this.store.dispatch(QdTableActions.updateTableStateRowToSelected({ tableId: this.tableId, rowIdentifier }));
22615
22743
  }
22616
- updateTableStateSingleRowToSelected(rowIndex) {
22617
- this.store.dispatch(QdTableActions.updateTableStateSingleRowToSelected({ tableId: this.tableId, rowIndex }));
22744
+ updateTableStateSingleRowToSelected(rowIdentifier) {
22745
+ this.store.dispatch(QdTableActions.updateTableStateSingleRowToSelected({ tableId: this.tableId, rowIdentifier }));
22618
22746
  }
22619
- updateTableStateRowToUnselected(rowIndex) {
22620
- this.store.dispatch(QdTableActions.updateTableStateRowToUnselected({ tableId: this.tableId, rowIndex }));
22747
+ updateTableStateRowToUnselected(rowIdentifier) {
22748
+ this.store.dispatch(QdTableActions.updateTableStateRowToUnselected({ tableId: this.tableId, rowIdentifier }));
22621
22749
  }
22622
22750
  updateTableStateRecentSecondaryAction(action) {
22623
22751
  this.store.dispatch(QdTableActions.updateTableStateRecentSecondaryAction({ tableId: this.tableId, recentSecondaryAction: action }));
@@ -22649,6 +22777,12 @@ class QdTableStoreService {
22649
22777
  connect(connectorName, connectorState, connectorCriteria) {
22650
22778
  this.store.dispatch(QdTableActions.connect({ tableId: this.tableId, connectorCriteria, connectorName, connectorState }));
22651
22779
  }
22780
+ setLastVisitedRow(rowIdentifier) {
22781
+ this.store.dispatch(QdTableActions.setLastVisitedRow({ tableId: this.tableId, rowIdentifier }));
22782
+ }
22783
+ clearLastVisitedRow() {
22784
+ this.store.dispatch(QdTableActions.clearLastVisitedRow({ tableId: this.tableId }));
22785
+ }
22652
22786
  resetConnectorStates() {
22653
22787
  this.store.dispatch(QdTableActions.resetConnectorStates({ tableId: this.tableId }));
22654
22788
  }
@@ -22669,8 +22803,8 @@ class QdTableStoreService {
22669
22803
  .select(this.tableStoreSelectorService.selectedRows())
22670
22804
  .pipe(filter(selectedRows => selectedRows !== undefined));
22671
22805
  }
22672
- isTableRowSelected$(rowIndex) {
22673
- return this.store.select(this.tableStoreSelectorService.isTableRowSelected(rowIndex));
22806
+ isTableRowSelected$(rowIndex, trackRowBy) {
22807
+ return this.store.select(this.tableStoreSelectorService.isTableRowSelected(rowIndex, trackRowBy));
22674
22808
  }
22675
22809
  recentSecondaryAction$() {
22676
22810
  return this.store
@@ -22707,6 +22841,9 @@ class QdTableStoreService {
22707
22841
  selectDataResolutionCriteria$() {
22708
22842
  return this.store.select(this.tableStoreSelectorService.selectDataResolutionCriteria());
22709
22843
  }
22844
+ lastVisitedRowIdentifier$() {
22845
+ return this.store.select(this.tableStoreSelectorService.lastVisitedRowIdentifier());
22846
+ }
22710
22847
  selectConnectorState$(connectorName) {
22711
22848
  return this.store.select(this.tableStoreSelectorService.selectConnectorState(connectorName));
22712
22849
  }
@@ -23106,24 +23243,27 @@ class QdTableRowSelectionService {
23106
23243
  ]).pipe(skip(this._config.selection?.emitOutputInitially ? 0 : 1), map(([tableData]) => (tableData || []).map(this.getIndexedRows).filter(isRowSelected)));
23107
23244
  }
23108
23245
  isRowSelected(row, index) {
23109
- return this._selectedRows.includes(row.uid) || this._selectedRows.includes(index);
23246
+ const identifier = resolveRowIdentifier(row, index, this._config.trackRowBy);
23247
+ return this._selectedRows.includes(identifier);
23110
23248
  }
23111
23249
  getIndexedRows(row, index) {
23112
23250
  return { ...row, index };
23113
23251
  }
23114
23252
  isRowSelected$(rowIndex) {
23115
- return this.tableStoreService.isTableRowSelected$(rowIndex);
23253
+ return this.tableStoreService.isTableRowSelected$(rowIndex, this._config.trackRowBy);
23116
23254
  }
23117
- setRowSelected(rowIndex) {
23255
+ setRowSelected(rowIndex, rowData) {
23256
+ const identifier = resolveRowIdentifier(rowData, rowIndex, this._config.trackRowBy);
23118
23257
  if (this._config.selection.type === 'singleSelect') {
23119
- this.tableStoreService.updateTableStateSingleRowToSelected(rowIndex);
23258
+ this.tableStoreService.updateTableStateSingleRowToSelected(identifier);
23120
23259
  }
23121
23260
  else {
23122
- this.tableStoreService.updateTableStateRowToSelected(rowIndex);
23261
+ this.tableStoreService.updateTableStateRowToSelected(identifier);
23123
23262
  }
23124
23263
  }
23125
- setRowUnselected(rowIndex) {
23126
- this.tableStoreService.updateTableStateRowToUnselected(rowIndex);
23264
+ setRowUnselected(rowIndex, rowData) {
23265
+ const identifier = resolveRowIdentifier(rowData, rowIndex, this._config.trackRowBy);
23266
+ this.tableStoreService.updateTableStateRowToUnselected(identifier);
23127
23267
  }
23128
23268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
23129
23269
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionService });
@@ -23562,6 +23702,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
23562
23702
  class QdTableRowSelectionComponent {
23563
23703
  rowSelectionService = inject(QdTableRowSelectionService);
23564
23704
  rowIndex;
23705
+ rowData;
23565
23706
  testId;
23566
23707
  get dataTestId() {
23567
23708
  return this.testId;
@@ -23580,20 +23721,22 @@ class QdTableRowSelectionComponent {
23580
23721
  }
23581
23722
  handleChange(event) {
23582
23723
  if (event.target.checked) {
23583
- this.rowSelectionService.setRowSelected(this.rowIndex);
23724
+ this.rowSelectionService.setRowSelected(this.rowIndex, this.rowData);
23584
23725
  }
23585
23726
  else {
23586
- this.rowSelectionService.setRowUnselected(this.rowIndex);
23727
+ this.rowSelectionService.setRowUnselected(this.rowIndex, this.rowData);
23587
23728
  }
23588
23729
  }
23589
23730
  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 });
23731
+ 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
23732
  }
23592
23733
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowSelectionComponent, decorators: [{
23593
23734
  type: Component,
23594
23735
  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
23736
  }], propDecorators: { rowIndex: [{
23596
23737
  type: Input
23738
+ }], rowData: [{
23739
+ type: Input
23597
23740
  }], testId: [{
23598
23741
  type: Input,
23599
23742
  args: ['data-test-id']
@@ -23676,11 +23819,11 @@ class QdTableRowComponent {
23676
23819
  return this.config.columns.find(column => column.column === item)?.type || 'blank';
23677
23820
  }
23678
23821
  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 });
23822
+ 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
23823
  }
23681
23824
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableRowComponent, decorators: [{
23682
23825
  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"] }]
23826
+ 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
23827
  }], propDecorators: { config: [{
23685
23828
  type: Input
23686
23829
  }], rowData: [{
@@ -23698,20 +23841,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
23698
23841
  class QdTableBodyComponent {
23699
23842
  tableStoreService = inject(QdTableStoreService);
23700
23843
  resolverService = inject(QdTableResolverService);
23844
+ cdr = inject(ChangeDetectorRef);
23701
23845
  config;
23702
23846
  data;
23703
23847
  testId;
23704
23848
  isLoading$;
23849
+ highlightedRowIdentifier;
23705
23850
  _primaryActionRefreshSubscription;
23706
23851
  constructor() {
23707
23852
  this.isLoading$ = this.tableStoreService.selectIsLoading$().pipe(debounceTime$1(400));
23708
23853
  }
23854
+ ngOnInit() {
23855
+ this.initHighlight();
23856
+ }
23709
23857
  ngOnDestroy() {
23710
23858
  this._primaryActionRefreshSubscription?.unsubscribe();
23711
23859
  }
23860
+ isLastVisitedRow(rowData, rowIndex) {
23861
+ if (this.highlightedRowIdentifier === undefined)
23862
+ return false;
23863
+ return resolveRowIdentifier(rowData, rowIndex, this.config.trackRowBy) === this.highlightedRowIdentifier;
23864
+ }
23712
23865
  primaryAction(rowData, index) {
23713
23866
  if (!this.config.primaryAction?.handler)
23714
23867
  return console.warn('QD-UI | QdTable - No handler defined for primary action.');
23868
+ if (this.config.primaryAction?.highlightOnRevisit) {
23869
+ this.tableStoreService.setLastVisitedRow(resolveRowIdentifier(rowData, index, this.config.trackRowBy));
23870
+ }
23715
23871
  const result = this.config.primaryAction?.handler({ rowData, index });
23716
23872
  if (!this.config.primaryAction?.refresh?.isEnabled)
23717
23873
  return;
@@ -23721,16 +23877,28 @@ class QdTableBodyComponent {
23721
23877
  }
23722
23878
  this.triggerRefresh();
23723
23879
  }
23880
+ initHighlight() {
23881
+ this.tableStoreService
23882
+ .lastVisitedRowIdentifier$()
23883
+ .pipe(take(1))
23884
+ .subscribe(id => {
23885
+ if (id === undefined)
23886
+ return;
23887
+ this.highlightedRowIdentifier = id;
23888
+ this.tableStoreService.clearLastVisitedRow();
23889
+ this.cdr.markForCheck();
23890
+ });
23891
+ }
23724
23892
  triggerRefresh() {
23725
23893
  const pageIndex = this.config.primaryAction?.refresh?.page;
23726
23894
  this.resolverService.refresh(pageIndex !== undefined ? { pageIndex } : undefined);
23727
23895
  }
23728
23896
  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 });
23897
+ 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
23898
  }
23731
23899
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdTableBodyComponent, decorators: [{
23732
23900
  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"] }]
23901
+ 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
23902
  }], ctorParameters: () => [], propDecorators: { config: [{
23735
23903
  type: Input
23736
23904
  }], data: [{
@@ -24260,6 +24428,9 @@ class QdTableComponent {
24260
24428
  const action = this.config.secondaryActions?.find(a => a.type === recentAction.type);
24261
24429
  if (!action?.handler)
24262
24430
  return console.warn('QD-UI | QdTable - No handler defined for secondary action:', recentAction.type);
24431
+ if (action.highlightOnRevisit) {
24432
+ this.tableStoreService.setLastVisitedRow(resolveRowIdentifier(recentAction.rowData, recentAction.index, this.config.trackRowBy));
24433
+ }
24263
24434
  const result = action.handler(recentAction);
24264
24435
  if (action.refresh?.isEnabled)
24265
24436
  this.processSecondaryActionRefresh(result, action.refresh);
@@ -24273,13 +24444,14 @@ class QdTableComponent {
24273
24444
  'The table uid has to match the path of the server side event.');
24274
24445
  return;
24275
24446
  }
24276
- if (!this.pushEventsService.isConnectedOrConnecting()) {
24447
+ if (!this.pushEventsService || !this.pushEventsService.isConnectedOrConnecting()) {
24277
24448
  console.error('QD-UI | QdTable - You have to connect the QdPushEventsService on your own ' +
24278
24449
  'if you enable the refreshOnPushEvent flag in the table.');
24279
24450
  return;
24280
24451
  }
24452
+ const pushEventsService = this.pushEventsService;
24281
24453
  const events = [...(this.config?.refreshingEvents || []), 'RESOURCE_UPDATED'];
24282
- const combinedEvents$ = merge(...events.map(eventName => this.pushEventsService.observe(eventName)));
24454
+ const combinedEvents$ = merge(...events.map(eventName => pushEventsService.observe(eventName)));
24283
24455
  this._pushEventsSubscription = combinedEvents$
24284
24456
  .pipe(filter(messageEvent => {
24285
24457
  try {
@@ -24313,6 +24485,13 @@ class QdTableComponent {
24313
24485
  if (!this.config.i18ns) {
24314
24486
  console.warn('QD-UI | QdTable - No translations available for the secondaryActions.');
24315
24487
  }
24488
+ if (this.hasHighlightOnRevisit() && !this.config.uid) {
24489
+ console.warn('QD-UI | QdTable - A uid has to be defined when highlightOnRevisit is enabled. ' +
24490
+ 'Without a uid, the table state does not persist across navigation.');
24491
+ }
24492
+ }
24493
+ hasHighlightOnRevisit() {
24494
+ return (!!this.config.primaryAction?.highlightOnRevisit || !!this.config.secondaryActions?.some(a => a.highlightOnRevisit));
24316
24495
  }
24317
24496
  /**
24318
24497
  * @description Can be deleted when the property "mainColumnNotFillingWidth" is dropped.
@@ -25268,6 +25447,8 @@ const connect = (state, tableId, newConnectorCriteria, connectorName, connectorS
25268
25447
  connectors: updatedConnectors
25269
25448
  });
25270
25449
  };
25450
+ const setLastVisitedRow = (state, tableId, rowIdentifier) => updateStateWithNewTable(state, tableId, { ...state[tableId], lastVisitedRowIdentifier: rowIdentifier });
25451
+ const clearLastVisitedRow = (state, tableId) => updateStateWithNewTable(state, tableId, { ...state[tableId], lastVisitedRowIdentifier: undefined });
25271
25452
  const resetConnectorStates = (state, tableId) => {
25272
25453
  const { connectors } = state[tableId];
25273
25454
  return updateStateWithNewTable(state, tableId, {
@@ -25295,10 +25476,9 @@ const _tableReducer = createReducer(initialState, on(QdTableActions.initTableSta
25295
25476
  if (state[tableId].selectedRows)
25296
25477
  return state;
25297
25478
  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)));
25479
+ }),
25480
+ // TODO: Remove QdTreeData references from table — Tree has been extracted into its own component
25481
+ 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
25482
  function tableReducer(state, action) {
25303
25483
  return _tableReducer(state, action);
25304
25484
  }
@@ -26928,8 +27108,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
26928
27108
  class QdFormGroupManagerService {
26929
27109
  _formGroups = new Map();
26930
27110
  _formGroupsSnapshot = new Map();
27111
+ _formGroupsChanged$ = new BehaviorSubject(undefined);
26931
27112
  setFormGroup(key, formGroup) {
26932
27113
  this._formGroups.set(key, formGroup);
27114
+ this._formGroupsChanged$.next();
26933
27115
  }
26934
27116
  getFormGroup(key) {
26935
27117
  return this._formGroups.get(key);
@@ -26939,6 +27121,7 @@ class QdFormGroupManagerService {
26939
27121
  }
26940
27122
  tryRemoveFormGroup(key) {
26941
27123
  this._formGroups.delete(key);
27124
+ this._formGroupsChanged$.next();
26942
27125
  }
26943
27126
  hasFormGroups() {
26944
27127
  return this._formGroups.size > 0;
@@ -26952,19 +27135,24 @@ class QdFormGroupManagerService {
26952
27135
  return !this._formGroups.has(key);
26953
27136
  }
26954
27137
  $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)));
27138
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
27139
+ if (!this.hasFormGroups())
27140
+ return of(false);
27141
+ const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
27142
+ return combineLatest(obs).pipe(map(valids => valids.every(Boolean)));
27143
+ }));
26959
27144
  }
26960
27145
  $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)));
27146
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
27147
+ if (!this.hasFormGroups())
27148
+ return of(false);
27149
+ const obs = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(fg.getRawValue()), map(() => {
27150
+ const currentValues = fg.getRawValue();
27151
+ const snapshot = this._formGroupsSnapshot.get(key);
27152
+ return snapshot ? !isEqual$1(currentValues, snapshot) : true;
27153
+ })));
27154
+ return combineLatest(obs).pipe(map(changes => changes.some(Boolean)));
27155
+ }));
26968
27156
  }
26969
27157
  takeFormGroupsSnapshot() {
26970
27158
  this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
@@ -26984,6 +27172,43 @@ class QdFormGroupManagerService {
26984
27172
  }
26985
27173
  });
26986
27174
  });
27175
+ this.cancelPendingAsyncValidation();
27176
+ }
27177
+ /**
27178
+ * Cancels any in-flight async validators on all registered form groups.
27179
+ *
27180
+ * 1. Collect all PENDING controls and their async validators
27181
+ * 2. Clear all async validators, then update validity (sync-only, no cascade)
27182
+ * 3. Re-attach async validators for future use
27183
+ */
27184
+ cancelPendingAsyncValidation() {
27185
+ this._formGroups.forEach(fg => {
27186
+ const pendingControls = this.collectPendingControls(fg);
27187
+ if (pendingControls.length === 0)
27188
+ return;
27189
+ for (const { control } of pendingControls) {
27190
+ control.clearAsyncValidators();
27191
+ }
27192
+ for (const { control } of pendingControls) {
27193
+ control.updateValueAndValidity({ onlySelf: true });
27194
+ }
27195
+ for (const { control, asyncValidator } of pendingControls) {
27196
+ control.setAsyncValidators(asyncValidator);
27197
+ }
27198
+ });
27199
+ }
27200
+ collectPendingControls(control) {
27201
+ const result = [];
27202
+ if (control instanceof FormGroup) {
27203
+ Object.values(control.controls).forEach(c => result.push(...this.collectPendingControls(c)));
27204
+ }
27205
+ else if (control instanceof FormArray) {
27206
+ control.controls.forEach(c => result.push(...this.collectPendingControls(c)));
27207
+ }
27208
+ if (control.status === 'PENDING') {
27209
+ result.push({ control, asyncValidator: control.asyncValidator });
27210
+ }
27211
+ return result;
26987
27212
  }
26988
27213
  resetFormArrayToValues(array, newValues) {
26989
27214
  if (!Array.isArray(newValues))
@@ -27108,6 +27333,7 @@ class QdPageObjectHeaderComponent {
27108
27333
  _pageObjectDataSubject = new BehaviorSubject({});
27109
27334
  _isLoadingSubject = new BehaviorSubject(false);
27110
27335
  _customActionsSubject = new BehaviorSubject({ actions: [] });
27336
+ _customActionsSub;
27111
27337
  _destroyed$ = new Subject();
27112
27338
  _availableContexts = 0;
27113
27339
  pageObjectData$ = this._pageObjectDataSubject.asObservable();
@@ -27238,14 +27464,18 @@ class QdPageObjectHeaderComponent {
27238
27464
  .$hasValuesChanged()
27239
27465
  .pipe(take(1))
27240
27466
  .subscribe(hasChanged => {
27241
- if (hasChanged)
27467
+ if (hasChanged) {
27242
27468
  this.openCancelDialog();
27243
- else
27469
+ }
27470
+ else {
27471
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27244
27472
  this.pageStoreService.toggleViewonly(true);
27473
+ }
27245
27474
  });
27246
27475
  }
27247
27476
  save() {
27248
27477
  const handleSuccess = () => {
27478
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27249
27479
  this.pageStoreService.toggleViewonly(true);
27250
27480
  this.formGroupManagerService.takeFormGroupsSnapshot();
27251
27481
  };
@@ -27274,7 +27504,7 @@ class QdPageObjectHeaderComponent {
27274
27504
  setupResolverTrigger() {
27275
27505
  this.resolverTriggerService
27276
27506
  .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)))
27507
+ .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
27508
  .subscribe();
27279
27509
  }
27280
27510
  initContexts() {
@@ -27293,17 +27523,32 @@ class QdPageObjectHeaderComponent {
27293
27523
  .subscribe();
27294
27524
  }
27295
27525
  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
- });
27526
+ // 1. Handle non-inspect pages early
27527
+ if (this.config.pageType !== 'inspect') {
27528
+ const actions = this.config.pageTypeConfig?.customActions ?? [];
27529
+ this._customActionsSubject.next({ actions });
27530
+ return;
27304
27531
  }
27305
- if (this.config.pageType !== 'inspect')
27306
- this._customActionsSubject.next({ actions: this.config.pageTypeConfig?.customActions ?? [] });
27532
+ // 2. Setup Inspect-specific logic
27533
+ const config = this.config.pageTypeConfig;
27534
+ if (config?.operationMode) {
27535
+ this.pageStoreService.toggleViewonly(config.operationMode === 'view');
27536
+ }
27537
+ this.subscribeToViewOnlyMode();
27538
+ }
27539
+ subscribeToViewOnlyMode() {
27540
+ this._customActionsSub?.unsubscribe();
27541
+ this._customActionsSub = this.pageStoreService.isViewonly$
27542
+ .pipe(takeUntil(this._destroyed$))
27543
+ .subscribe(isViewonly => {
27544
+ const mode = isViewonly ? 'view' : 'edit';
27545
+ const config = this.config.pageTypeConfig;
27546
+ if (config) {
27547
+ config.operationMode = mode;
27548
+ }
27549
+ const actions = config?.customActions ?? [];
27550
+ this._customActionsSubject.next(this.getCustomActionsByMode(actions, mode));
27551
+ });
27307
27552
  }
27308
27553
  getCustomActionsByMode(customActions, mode) {
27309
27554
  const actions = customActions
@@ -28302,6 +28547,7 @@ class QdPageSubmitActionService {
28302
28547
  _submitHandler;
28303
28548
  _isVisible = true;
28304
28549
  _destroyed$ = new Subject();
28550
+ _cancelTrackFormValidity$ = new Subject();
28305
28551
  ngOnDestroy() {
28306
28552
  this._destroyed$.next(null);
28307
28553
  this._destroyed$.complete();
@@ -28340,9 +28586,10 @@ class QdPageSubmitActionService {
28340
28586
  };
28341
28587
  }
28342
28588
  trackFormValidity() {
28589
+ this._cancelTrackFormValidity$.next();
28343
28590
  this.formGroupManagerService
28344
28591
  .$areFormGroupsValid()
28345
- .pipe(takeUntil(this._destroyed$), distinctUntilChanged())
28592
+ .pipe(takeUntil(this._cancelTrackFormValidity$), takeUntil(this._destroyed$), distinctUntilChanged())
28346
28593
  .subscribe(isValid => this.updateSubmitButtonState(isValid));
28347
28594
  }
28348
28595
  updateSubmitButtonState(isValid) {
@@ -28780,6 +29027,7 @@ class QdPageComponent {
28780
29027
  projectionGuardMessage = 'QD-UI | QdPage - This content is not supported. Please use QdSectionAdapterDirective for custom content projection.';
28781
29028
  _isInitialized = false;
28782
29029
  _destroyed$ = new Subject();
29030
+ _cancelSubmitValidation$ = new Subject();
28783
29031
  get isControlPanelVisible() {
28784
29032
  return this.controlPanel !== undefined && this.controlPanel.config?.isHidden !== true;
28785
29033
  }
@@ -28894,6 +29142,10 @@ class QdPageComponent {
28894
29142
  ]);
28895
29143
  }
28896
29144
  updateInspectPageOperationMode(pageTypeConfig, mode) {
29145
+ if (mode === 'view') {
29146
+ this.formGroupManagerService.cancelPendingAsyncValidation();
29147
+ setTimeout(() => this.formGroupManagerService.cancelPendingAsyncValidation());
29148
+ }
28897
29149
  if (pageTypeConfig?.submit) {
28898
29150
  const isVisible = mode === 'view' && !pageTypeConfig.submit.isHidden;
28899
29151
  this.submitActionService.setSubmitActionForInspect(pageTypeConfig, isVisible);
@@ -28921,9 +29173,10 @@ class QdPageComponent {
28921
29173
  .subscribe();
28922
29174
  }
28923
29175
  initSubmitValidation() {
29176
+ this._cancelSubmitValidation$.next();
28924
29177
  this.formGroupManagerService
28925
29178
  .$areFormGroupsValid()
28926
- .pipe(takeUntil(this._destroyed$), tap(isValid => {
29179
+ .pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
28927
29180
  const submitDisabledInfoText = this.config.pageType === 'inspect' ? this.config.pageTypeConfig?.submit?.disabledInfo : undefined;
28928
29181
  this.footerService.updateActions([
28929
29182
  {