@quadrel-enterprise-ui/framework 20.27.5 → 20.27.6-beta.242.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.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, Directive, InjectionToken, HostBinding, Input, ViewEncapsulation, Component, Injectable, Injector, HostListener, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, NgModule, EventEmitter, Output, Renderer2, Pipe, ViewContainerRef, NO_ERRORS_SCHEMA, DestroyRef, SecurityContext, NgZone, ViewChildren, forwardRef, ContentChildren, ContentChild, isDevMode, QueryList, TemplateRef, CUSTOM_ELEMENTS_SCHEMA, provideAppInitializer } from '@angular/core';
3
- import { Dialog, DialogRef, DialogModule } from '@angular/cdk/dialog';
2
+ import { inject, ElementRef, Directive, InjectionToken, HostBinding, Input, ViewEncapsulation, Component, Injectable, Injector, HostListener, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, NgModule, EventEmitter, Output, Renderer2, Pipe, ViewContainerRef, NO_ERRORS_SCHEMA, DestroyRef, SecurityContext, NgZone, ViewChildren, forwardRef, ContentChildren, ContentChild, afterNextRender, isDevMode, QueryList, TemplateRef, CUSTOM_ELEMENTS_SCHEMA, provideAppInitializer } from '@angular/core';
3
+ import { Dialog, DialogRef, DIALOG_DATA, 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, tap as tap$1, delay, first, scan, queueScheduler, filter as filter$1, concatMap as concatMap$1, exhaustMap, finalize, forkJoin, delayWhen, combineLatestWith, withLatestFrom as withLatestFrom$1, async } from 'rxjs';
6
+ import { BehaviorSubject, pairwise, map as map$1, Subject, throwError, of, ReplaySubject, merge, fromEvent, isObservable, NEVER, Observable, EMPTY, shareReplay, Subscription, distinctUntilChanged as distinctUntilChanged$1, debounce, timer, switchMap, startWith as startWith$1, debounceTime as debounceTime$1, takeUntil as takeUntil$1, firstValueFrom, combineLatest, concat, take as take$1, tap as tap$1, from, delay, first, scan, queueScheduler, filter as filter$1, concatMap as concatMap$1, exhaustMap, finalize, forkJoin, delayWhen, combineLatestWith, withLatestFrom as withLatestFrom$1, async } from 'rxjs';
7
7
  import { map, takeUntil, take, filter, catchError, debounceTime, startWith, distinctUntilChanged, concatMap, skip, tap, auditTime, withLatestFrom, 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';
@@ -1143,13 +1143,41 @@ var QdDialogSize;
1143
1143
  QdDialogSize["FullWidth"] = "100%";
1144
1144
  })(QdDialogSize || (QdDialogSize = {}));
1145
1145
 
1146
+ /**
1147
+ * Tracks which dialogs currently hold unsaved changes.
1148
+ *
1149
+ * Pending changes are always bound to the dialog that owns them. A dialog is
1150
+ * identified by its `DialogRef`, so two dialogs open at the same time never
1151
+ * read or overwrite each other's state.
1152
+ *
1153
+ * #### Notes & Best Practices:
1154
+ *
1155
+ * - A dialog can hold several sources of pending changes, for example one per
1156
+ * form. The dialog counts as changed while at least one source is marked.
1157
+ * - Every source that marks changes must clear them again when it is destroyed.
1158
+ * - A dialog without any marked source reports no pending changes. This is why
1159
+ * the discard confirmation dialog can always close itself.
1160
+ * - Owners are held weakly, so a dialog that is destroyed without clearing its
1161
+ * sources cannot keep them alive.
1162
+ */
1146
1163
  class QdDialogChangeGuardService {
1147
- _hasChanges = false;
1148
- get hasPendingChanges() {
1149
- return this._hasChanges;
1150
- }
1151
- setPendingChangesStatus(status) {
1152
- this._hasChanges = status;
1164
+ _pendingChangeSourcesByOwner = new WeakMap();
1165
+ hasPendingChanges(owner) {
1166
+ const sources = this._pendingChangeSourcesByOwner.get(owner);
1167
+ return !!sources?.size;
1168
+ }
1169
+ markPendingChanges(source, owner) {
1170
+ const sources = this._pendingChangeSourcesByOwner.get(owner) ?? new Set();
1171
+ sources.add(source);
1172
+ this._pendingChangeSourcesByOwner.set(owner, sources);
1173
+ }
1174
+ clearPendingChanges(source, owner) {
1175
+ const sources = this._pendingChangeSourcesByOwner.get(owner);
1176
+ if (!sources)
1177
+ return;
1178
+ sources.delete(source);
1179
+ if (!sources.size)
1180
+ this._pendingChangeSourcesByOwner.delete(owner);
1153
1181
  }
1154
1182
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogChangeGuardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1155
1183
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogChangeGuardService, providedIn: 'root' });
@@ -1161,6 +1189,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1161
1189
  }]
1162
1190
  }] });
1163
1191
 
1192
+ /**
1193
+ * DI token that provides the component rendered inside the discard-confirmation
1194
+ * dialog.
1195
+ *
1196
+ * The discard dialog renders `<qd-dialog>`, `<qd-dialog>` opens the discard
1197
+ * dialog through this service, and the service opens the discard dialog
1198
+ * component — a compile-time template cycle. Binding the component through this
1199
+ * token (instead of the service importing it directly) breaks that cycle, so
1200
+ * the dialog can be bundled eagerly instead of lazy-loaded. This is the one
1201
+ * place a token is genuinely required.
1202
+ */
1203
+ const QD_DISCARD_CONFIRM_DIALOG_COMPONENT_TOKEN = new InjectionToken('QD_DISCARD_CONFIRM_DIALOG_COMPONENT_TOKEN');
1204
+
1164
1205
  const GENERIC_DEFAULTS = {
1165
1206
  title: { i18n: 'i18n.qd.dialog.confirm.title' },
1166
1207
  confirm: { i18n: 'i18n.qd.dialog.confirm.proceed' },
@@ -1174,6 +1215,7 @@ const CANCEL_DEFAULTS = {
1174
1215
  };
1175
1216
  class QdConfirmationDialogOpenerService {
1176
1217
  dialog = inject(Dialog);
1218
+ confirmDialogComponent = inject(QD_DISCARD_CONFIRM_DIALOG_COMPONENT_TOKEN);
1177
1219
  showConfirmDialog(config, testId = 'dialog-confirm') {
1178
1220
  const resolved = {
1179
1221
  title: config.title ?? GENERIC_DEFAULTS.title,
@@ -1181,14 +1223,12 @@ class QdConfirmationDialogOpenerService {
1181
1223
  confirm: config.confirm ?? GENERIC_DEFAULTS.confirm,
1182
1224
  cancel: config.cancel ?? GENERIC_DEFAULTS.cancel
1183
1225
  };
1184
- return from(import('./quadrel-enterprise-ui-framework-dialog-confirm.module-BQLiEzSo.mjs').then(m => m.QdDialogConfirmComponent)).pipe(switchMap(component => {
1185
- const dialogRef = this.open(component, {
1186
- title: resolved.title,
1187
- dialogSize: QdDialogSize.Small,
1188
- data: { ...resolved, testId }
1189
- });
1190
- return dialogRef.closed.pipe(map$1(result => !!result));
1191
- }));
1226
+ const dialogRef = this.open(this.confirmDialogComponent, {
1227
+ title: resolved.title,
1228
+ dialogSize: QdDialogSize.Small,
1229
+ data: { ...resolved, testId }
1230
+ });
1231
+ return dialogRef.closed.pipe(map$1(result => !!result));
1192
1232
  }
1193
1233
  showDiscardConfirmDialog(message, testId = 'cancel-confirmation') {
1194
1234
  return this.showConfirmDialog({
@@ -1458,6 +1498,15 @@ class QdDialogComponent {
1458
1498
  changeDetectorRef = inject(ChangeDetectorRef);
1459
1499
  dialogChangeGuard = inject(QdDialogChangeGuardService);
1460
1500
  dialogService = inject(QdDialogService);
1501
+ /**
1502
+ * A static test ID for integration tests can be set. <br />
1503
+ * The value for the HTML attribute [data-test-id].
1504
+ *
1505
+ * Every element of the dialog derives its own test ID from this root,
1506
+ * for example `<root>-close-button`. A nested dialog passes its own root
1507
+ * down, so a stacked dialog stays reachable without structural selectors.
1508
+ */
1509
+ testId = 'dialog';
1461
1510
  body;
1462
1511
  infoBanners;
1463
1512
  get hasSectionsClass() {
@@ -1468,6 +1517,7 @@ class QdDialogComponent {
1468
1517
  hasInfoBanner;
1469
1518
  _destroyed$ = new Subject();
1470
1519
  _pageDialogCanCloseFn = null;
1520
+ _isConfirmingClose = false;
1471
1521
  get isFullWidth() {
1472
1522
  return this.config.dialogSize === QdDialogSize.FullWidth;
1473
1523
  }
@@ -1509,10 +1559,14 @@ class QdDialogComponent {
1509
1559
  this.dialogRef.close();
1510
1560
  }
1511
1561
  confirmAndClose() {
1562
+ if (this._isConfirmingClose)
1563
+ return;
1564
+ this._isConfirmingClose = true;
1512
1565
  this.dialog
1513
1566
  .showDiscardConfirmDialog(this.config.cancel?.confirmationMessage, 'dialog-cancel-confirmation')
1514
1567
  .pipe(take(1))
1515
1568
  .subscribe(confirmed => {
1569
+ this._isConfirmingClose = false;
1516
1570
  if (!confirmed)
1517
1571
  return;
1518
1572
  this.config.cancel?.handler?.();
@@ -1533,7 +1587,7 @@ class QdDialogComponent {
1533
1587
  });
1534
1588
  }
1535
1589
  handleLegacyDialogClose() {
1536
- if (!this.dialogChangeGuard.hasPendingChanges)
1590
+ if (!this.dialogChangeGuard.hasPendingChanges(this.dialogRef))
1537
1591
  return this.runCancelHandlerAndClose();
1538
1592
  this.confirmAndClose();
1539
1593
  }
@@ -1550,12 +1604,15 @@ class QdDialogComponent {
1550
1604
  });
1551
1605
  }
1552
1606
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogComponent, isStandalone: false, selector: "qd-dialog", host: { properties: { "class.full-width": "isFullWidth", "class.has-sections": "this.hasSectionsClass" } }, providers: [QdDialogComponent], viewQueries: [{ propertyName: "body", first: true, predicate: ["body"], descendants: true }, { propertyName: "infoBanners", first: true, predicate: ["banners"], descendants: true }], ngImport: i0, template: "<div *ngIf=\"!isFullWidth\" class=\"dialog-header\">\n <div class=\"title-container\">\n {{ config?.title?.i18n | translate }}\n </div>\n <button\n *ngIf=\"!config?.hideCloseHeaderButton\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n >\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n </button>\n</div>\n\n<button\n *ngIf=\"!config?.hideCloseHeaderButton && isFullWidth\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n>\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n</button>\n\n<div class=\"wrapper-body\">\n <div [class.has-dialog-info-banners]=\"hasInfoBanner\" #banners>\n <ng-content select=\"qd-page-info-banner\"></ng-content>\n </div>\n <div class=\"body\" [class.full-width]=\"isFullWidth\" #body>\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"qd-dialog-actions\">\n <button *ngIf=\"config.cancel\" qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\">\n {{ config?.cancel?.label?.i18n ?? \"i18n.qd.dialog.action.cancel\" | translate }}\n </button>\n <button *ngIf=\"config.primary\" qdButton (click)=\"primaryActionClicked()\">\n {{ config?.primary?.label?.i18n ?? \"i18n.qd.dialog.action.primary\" | translate }}\n </button>\n <ng-content select=\"qd-dialog-action\"></ng-content>\n</div>\n", styles: ["qd-dialog{display:flex;width:100%;height:100%;flex-direction:column;background:#efefef}qd-dialog.full-width{position:relative;width:calc(100% - 3rem)!important;height:calc(100% - 3rem)!important;margin:1.5rem}qd-dialog.full-width .close{position:absolute;z-index:9999;top:1.25rem;right:1rem;color:#454545;cursor:pointer}.dialog-header{display:flex;height:3.125rem;flex-direction:row;align-items:center;justify-content:space-between;padding:.5rem 1.5rem;border-bottom:2px solid rgb(213,213,213);background:#fff}.dialog-header .title-container{display:block;color:#171717;font-weight:500}.dialog-header .close{cursor:pointer}.wrapper-body{overflow:auto;flex:1;background:#efefef}.wrapper-body .has-dialog-info-banners{padding:1rem 1.25rem .5rem;border-bottom:rgb(213,213,213) solid .0625rem;background-color:#fff}.wrapper-body .has-dialog-info-banners qd-page-info-banner:last-child{margin-bottom:0}.wrapper-body .body.full-width{height:100%}.wrapper-body .body:not(.full-width){max-height:80vh;padding:1rem 1.5rem}.wrapper-body .body:not(.full-width) qd-section{padding-right:1.5rem;padding-left:1.5rem}.wrapper-body .body:not(.full-width) qd-text-section{margin-right:0;margin-left:0}qd-dialog.has-sections .wrapper-body .body:not(.full-width){padding:0}.qd-dialog-actions{display:flex;justify-content:flex-end;padding:.5rem 1.5rem;border-top:2px solid rgb(213,213,213);background:#efefef;column-gap:.625rem}\n"], dependencies: [{ 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: QdIconButtonComponent, selector: "button[qdIconButton], a[qdIconButton], button[qd-icon-button]", inputs: ["color", "data-test-id"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon", "status"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None });
1607
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogComponent, isStandalone: false, selector: "qd-dialog", inputs: { testId: ["data-test-id", "testId"] }, host: { properties: { "class.full-width": "isFullWidth", "class.has-sections": "this.hasSectionsClass" } }, providers: [QdDialogComponent], viewQueries: [{ propertyName: "body", first: true, predicate: ["body"], descendants: true }, { propertyName: "infoBanners", first: true, predicate: ["banners"], descendants: true }], ngImport: i0, template: "<div *ngIf=\"!isFullWidth\" class=\"dialog-header\">\n <div class=\"title-container\">\n {{ config?.title?.i18n | translate }}\n </div>\n <button\n *ngIf=\"!config?.hideCloseHeaderButton\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n [attr.aria-label]=\"'i18n.qd.dialog.action.close' | translate\"\n [data-test-id]=\"testId + '-close-button'\"\n >\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n </button>\n</div>\n\n<button\n *ngIf=\"!config?.hideCloseHeaderButton && isFullWidth\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n [attr.aria-label]=\"'i18n.qd.dialog.action.close' | translate\"\n [data-test-id]=\"testId + '-close-button'\"\n>\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n</button>\n\n<div class=\"wrapper-body\">\n <div [class.has-dialog-info-banners]=\"hasInfoBanner\" #banners>\n <ng-content select=\"qd-page-info-banner\"></ng-content>\n </div>\n <div class=\"body\" [class.full-width]=\"isFullWidth\" #body>\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"qd-dialog-actions\">\n <button\n *ngIf=\"config.cancel\"\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n [data-test-id]=\"testId + '-cancel-button'\"\n >\n {{ config?.cancel?.label?.i18n ?? \"i18n.qd.dialog.action.cancel\" | translate }}\n </button>\n <button *ngIf=\"config.primary\" qdButton (click)=\"primaryActionClicked()\" [data-test-id]=\"testId + '-primary-button'\">\n {{ config?.primary?.label?.i18n ?? \"i18n.qd.dialog.action.primary\" | translate }}\n </button>\n <ng-content select=\"qd-dialog-action\"></ng-content>\n</div>\n", styles: ["qd-dialog{display:flex;width:100%;height:100%;flex-direction:column;background:#efefef}qd-dialog.full-width{position:relative;width:calc(100% - 3rem)!important;height:calc(100% - 3rem)!important;margin:1.5rem}qd-dialog.full-width .close{position:absolute;z-index:9999;top:1.25rem;right:1rem;color:#454545;cursor:pointer}.dialog-header{display:flex;height:3.125rem;flex-direction:row;align-items:center;justify-content:space-between;padding:.5rem 1.5rem;border-bottom:2px solid rgb(213,213,213);background:#fff}.dialog-header .title-container{display:block;color:#171717;font-weight:500}.dialog-header .close{cursor:pointer}.wrapper-body{overflow:auto;flex:1;background:#efefef}.wrapper-body .has-dialog-info-banners{padding:1rem 1.25rem .5rem;border-bottom:rgb(213,213,213) solid .0625rem;background-color:#fff}.wrapper-body .has-dialog-info-banners qd-page-info-banner:last-child{margin-bottom:0}.wrapper-body .body.full-width{height:100%}.wrapper-body .body:not(.full-width){max-height:80vh;padding:1rem 1.5rem}.wrapper-body .body:not(.full-width) qd-section{padding-right:1.5rem;padding-left:1.5rem}.wrapper-body .body:not(.full-width) qd-text-section{margin-right:0;margin-left:0}qd-dialog.has-sections .wrapper-body .body:not(.full-width){padding:0}.qd-dialog-actions{display:flex;justify-content:flex-end;padding:.5rem 1.5rem;border-top:2px solid rgb(213,213,213);background:#efefef;column-gap:.625rem}\n"], dependencies: [{ 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: QdIconButtonComponent, selector: "button[qdIconButton], a[qdIconButton], button[qd-icon-button]", inputs: ["color", "data-test-id"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon", "status"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None });
1554
1608
  }
1555
1609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogComponent, decorators: [{
1556
1610
  type: Component,
1557
- args: [{ selector: 'qd-dialog', host: { '[class.full-width]': 'isFullWidth' }, encapsulation: ViewEncapsulation.None, providers: [QdDialogComponent], standalone: false, template: "<div *ngIf=\"!isFullWidth\" class=\"dialog-header\">\n <div class=\"title-container\">\n {{ config?.title?.i18n | translate }}\n </div>\n <button\n *ngIf=\"!config?.hideCloseHeaderButton\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n >\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n </button>\n</div>\n\n<button\n *ngIf=\"!config?.hideCloseHeaderButton && isFullWidth\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n>\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n</button>\n\n<div class=\"wrapper-body\">\n <div [class.has-dialog-info-banners]=\"hasInfoBanner\" #banners>\n <ng-content select=\"qd-page-info-banner\"></ng-content>\n </div>\n <div class=\"body\" [class.full-width]=\"isFullWidth\" #body>\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"qd-dialog-actions\">\n <button *ngIf=\"config.cancel\" qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\">\n {{ config?.cancel?.label?.i18n ?? \"i18n.qd.dialog.action.cancel\" | translate }}\n </button>\n <button *ngIf=\"config.primary\" qdButton (click)=\"primaryActionClicked()\">\n {{ config?.primary?.label?.i18n ?? \"i18n.qd.dialog.action.primary\" | translate }}\n </button>\n <ng-content select=\"qd-dialog-action\"></ng-content>\n</div>\n", styles: ["qd-dialog{display:flex;width:100%;height:100%;flex-direction:column;background:#efefef}qd-dialog.full-width{position:relative;width:calc(100% - 3rem)!important;height:calc(100% - 3rem)!important;margin:1.5rem}qd-dialog.full-width .close{position:absolute;z-index:9999;top:1.25rem;right:1rem;color:#454545;cursor:pointer}.dialog-header{display:flex;height:3.125rem;flex-direction:row;align-items:center;justify-content:space-between;padding:.5rem 1.5rem;border-bottom:2px solid rgb(213,213,213);background:#fff}.dialog-header .title-container{display:block;color:#171717;font-weight:500}.dialog-header .close{cursor:pointer}.wrapper-body{overflow:auto;flex:1;background:#efefef}.wrapper-body .has-dialog-info-banners{padding:1rem 1.25rem .5rem;border-bottom:rgb(213,213,213) solid .0625rem;background-color:#fff}.wrapper-body .has-dialog-info-banners qd-page-info-banner:last-child{margin-bottom:0}.wrapper-body .body.full-width{height:100%}.wrapper-body .body:not(.full-width){max-height:80vh;padding:1rem 1.5rem}.wrapper-body .body:not(.full-width) qd-section{padding-right:1.5rem;padding-left:1.5rem}.wrapper-body .body:not(.full-width) qd-text-section{margin-right:0;margin-left:0}qd-dialog.has-sections .wrapper-body .body:not(.full-width){padding:0}.qd-dialog-actions{display:flex;justify-content:flex-end;padding:.5rem 1.5rem;border-top:2px solid rgb(213,213,213);background:#efefef;column-gap:.625rem}\n"] }]
1558
- }], propDecorators: { body: [{
1611
+ args: [{ selector: 'qd-dialog', host: { '[class.full-width]': 'isFullWidth' }, encapsulation: ViewEncapsulation.None, providers: [QdDialogComponent], standalone: false, template: "<div *ngIf=\"!isFullWidth\" class=\"dialog-header\">\n <div class=\"title-container\">\n {{ config?.title?.i18n | translate }}\n </div>\n <button\n *ngIf=\"!config?.hideCloseHeaderButton\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n [attr.aria-label]=\"'i18n.qd.dialog.action.close' | translate\"\n [data-test-id]=\"testId + '-close-button'\"\n >\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n </button>\n</div>\n\n<button\n *ngIf=\"!config?.hideCloseHeaderButton && isFullWidth\"\n qdIconButton\n class=\"close\"\n color=\"secondary\"\n tabindex=\"-1\"\n (click)=\"close()\"\n [attr.aria-label]=\"'i18n.qd.dialog.action.close' | translate\"\n [data-test-id]=\"testId + '-close-button'\"\n>\n <qd-icon icon=\"timesLargeLight\"></qd-icon>\n</button>\n\n<div class=\"wrapper-body\">\n <div [class.has-dialog-info-banners]=\"hasInfoBanner\" #banners>\n <ng-content select=\"qd-page-info-banner\"></ng-content>\n </div>\n <div class=\"body\" [class.full-width]=\"isFullWidth\" #body>\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"qd-dialog-actions\">\n <button\n *ngIf=\"config.cancel\"\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n [data-test-id]=\"testId + '-cancel-button'\"\n >\n {{ config?.cancel?.label?.i18n ?? \"i18n.qd.dialog.action.cancel\" | translate }}\n </button>\n <button *ngIf=\"config.primary\" qdButton (click)=\"primaryActionClicked()\" [data-test-id]=\"testId + '-primary-button'\">\n {{ config?.primary?.label?.i18n ?? \"i18n.qd.dialog.action.primary\" | translate }}\n </button>\n <ng-content select=\"qd-dialog-action\"></ng-content>\n</div>\n", styles: ["qd-dialog{display:flex;width:100%;height:100%;flex-direction:column;background:#efefef}qd-dialog.full-width{position:relative;width:calc(100% - 3rem)!important;height:calc(100% - 3rem)!important;margin:1.5rem}qd-dialog.full-width .close{position:absolute;z-index:9999;top:1.25rem;right:1rem;color:#454545;cursor:pointer}.dialog-header{display:flex;height:3.125rem;flex-direction:row;align-items:center;justify-content:space-between;padding:.5rem 1.5rem;border-bottom:2px solid rgb(213,213,213);background:#fff}.dialog-header .title-container{display:block;color:#171717;font-weight:500}.dialog-header .close{cursor:pointer}.wrapper-body{overflow:auto;flex:1;background:#efefef}.wrapper-body .has-dialog-info-banners{padding:1rem 1.25rem .5rem;border-bottom:rgb(213,213,213) solid .0625rem;background-color:#fff}.wrapper-body .has-dialog-info-banners qd-page-info-banner:last-child{margin-bottom:0}.wrapper-body .body.full-width{height:100%}.wrapper-body .body:not(.full-width){max-height:80vh;padding:1rem 1.5rem}.wrapper-body .body:not(.full-width) qd-section{padding-right:1.5rem;padding-left:1.5rem}.wrapper-body .body:not(.full-width) qd-text-section{margin-right:0;margin-left:0}qd-dialog.has-sections .wrapper-body .body:not(.full-width){padding:0}.qd-dialog-actions{display:flex;justify-content:flex-end;padding:.5rem 1.5rem;border-top:2px solid rgb(213,213,213);background:#efefef;column-gap:.625rem}\n"] }]
1612
+ }], propDecorators: { testId: [{
1613
+ type: Input,
1614
+ args: ['data-test-id']
1615
+ }], body: [{
1559
1616
  type: ViewChild,
1560
1617
  args: ['body']
1561
1618
  }], infoBanners: [{
@@ -1579,7 +1636,7 @@ class QdDialogAuthSessionEndComponent {
1579
1636
  this.dialogRef.close({ doLogoutViaIdentityBroker: true });
1580
1637
  }
1581
1638
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogAuthSessionEndComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1582
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogAuthSessionEndComponent, isStandalone: false, selector: "qd-dialog-auth-session-end", ngImport: i0, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ \"i18n.qd.authSupport.dialog.content\" | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button qdButton color=\"secondary\" (click)=\"backToEPortal()\">\n {{ \"i18n.qd.authSupport.dialog.action.backToEPortal\" | translate }}\n </button>\n <button qdButton (click)=\"reAuthenticate()\">\n {{ \"i18n.qd.authSupport.dialog.action.reAuthenticate\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", dependencies: [{ kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "component", type: QdTextSectionComponent, selector: "qd-text-section" }, { kind: "component", type: QdTextSectionParagraphComponent, selector: "qd-text-section-paragraph" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
1639
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogAuthSessionEndComponent, isStandalone: false, selector: "qd-dialog-auth-session-end", ngImport: i0, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ \"i18n.qd.authSupport.dialog.content\" | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button qdButton color=\"secondary\" (click)=\"backToEPortal()\">\n {{ \"i18n.qd.authSupport.dialog.action.backToEPortal\" | translate }}\n </button>\n <button qdButton (click)=\"reAuthenticate()\">\n {{ \"i18n.qd.authSupport.dialog.action.reAuthenticate\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", dependencies: [{ kind: "component", type: QdButtonComponent, selector: "button[qdButton], a[qdButton], button[qd-button]", inputs: ["disabled", "color", "icon", "data-test-id", "additionalInfo"] }, { kind: "component", type: QdTextSectionComponent, selector: "qd-text-section" }, { kind: "component", type: QdTextSectionParagraphComponent, selector: "qd-text-section-paragraph" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
1583
1640
  }
1584
1641
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogAuthSessionEndComponent, decorators: [{
1585
1642
  type: Component,
@@ -11547,6 +11604,7 @@ class QdInputComponent {
11547
11604
  controlContainer = inject(ControlContainer, { optional: true, host: true, skipSelf: true });
11548
11605
  eventBrokerService = inject(QdEventBrokerService, { optional: true });
11549
11606
  numberInputService = inject(QdNumberInputService);
11607
+ translateService = inject(TranslateService);
11550
11608
  /**
11551
11609
  * The form control name can be assigned here if you want to use Reactive Forms.
11552
11610
  */
@@ -11703,7 +11761,7 @@ class QdInputComponent {
11703
11761
  const displayValue = this.inputType === 'number'
11704
11762
  ? this.numberInputService.formatNumberForViewonly(this._value.value)
11705
11763
  : this._value.value;
11706
- return [`${displayValue}${this._value.unit ? ' ' + this._value.unit : ''}`];
11764
+ return [`${displayValue}${this._value.unit ? ' ' + this.getUnitLabel(this._value.unit) : ''}`];
11707
11765
  }
11708
11766
  ngOnInit() {
11709
11767
  this.subscribeToOptions();
@@ -11767,41 +11825,47 @@ class QdInputComponent {
11767
11825
  if (input?.validity?.badInput)
11768
11826
  return;
11769
11827
  const value = event.target.value;
11770
- if (this.inputType === 'number' && value && !this.numberInputService.isValidNumber(value)) {
11771
- this.control?.setErrors({
11772
- ...this.control.errors,
11773
- invalidCharacters: this.numberInputService.invalidCharactersErrorKey
11774
- });
11828
+ if (this.inputType === 'number' && value && !this.handleNumberInput(value))
11775
11829
  return;
11776
- }
11777
- if (this.inputType === 'number' && value) {
11778
- const valueWithoutGroups = value.split(this.numberInputService.groupSeparator).join('');
11779
- const filtered = this.numberInputService.filterValue(valueWithoutGroups);
11780
- const hasInvalidChars = filtered !== valueWithoutGroups;
11781
- if (hasInvalidChars) {
11782
- this._value = { ...this._value, value: filtered };
11783
- if (this.control) {
11784
- this.control.setErrors({
11785
- ...this.control.errors,
11786
- invalidCharacters: this.numberInputService.invalidCharactersErrorKey
11787
- });
11788
- }
11789
- this._isUserTyped = true;
11790
- this._onTouch();
11791
- this.emitValue();
11792
- return;
11793
- }
11794
- if (this.control?.errors?.['invalidCharacters']) {
11795
- const remainingErrors = Object.fromEntries(Object.entries(this.control.errors).filter(([key]) => key !== 'invalidCharacters'));
11796
- this.control.setErrors(Object.keys(remainingErrors).length ? remainingErrors : null);
11797
- }
11798
- }
11799
11830
  this._isUserTyped = true;
11800
11831
  this._value = { ...this._value, value };
11832
+ this._displayValue = String(value ?? '');
11801
11833
  this.loadOptionsForTypedValue(value);
11802
11834
  this.emitValue();
11803
11835
  this._onTouch();
11804
11836
  }
11837
+ handleNumberInput(value) {
11838
+ if (!this.numberInputService.isValidNumber(value)) {
11839
+ this.setInvalidCharactersError();
11840
+ return false;
11841
+ }
11842
+ const valueWithoutGroups = value.split(this.numberInputService.groupSeparator).join('');
11843
+ const filtered = this.numberInputService.filterValue(valueWithoutGroups);
11844
+ if (filtered !== valueWithoutGroups) {
11845
+ this._value = { ...this._value, value: filtered };
11846
+ this.setInvalidCharactersError();
11847
+ this._isUserTyped = true;
11848
+ this._onTouch();
11849
+ this.emitValue();
11850
+ return false;
11851
+ }
11852
+ this.clearInvalidCharactersError();
11853
+ return true;
11854
+ }
11855
+ setInvalidCharactersError() {
11856
+ if (!this.control)
11857
+ return;
11858
+ this.control.setErrors({
11859
+ ...this.control.errors,
11860
+ invalidCharacters: this.numberInputService.invalidCharactersErrorKey
11861
+ });
11862
+ }
11863
+ clearInvalidCharactersError() {
11864
+ if (!this.control?.errors?.['invalidCharacters'])
11865
+ return;
11866
+ const remainingErrors = Object.fromEntries(Object.entries(this.control.errors).filter(([key]) => key !== 'invalidCharacters'));
11867
+ this.control.setErrors(Object.keys(remainingErrors).length ? remainingErrors : null);
11868
+ }
11805
11869
  handleOptionSelected(option) {
11806
11870
  this._value = { ...this._value, value: option.i18n };
11807
11871
  this.popover?.close();
@@ -11903,6 +11967,10 @@ class QdInputComponent {
11903
11967
  this.popover?.close();
11904
11968
  }
11905
11969
  }
11970
+ getUnitLabel(unit) {
11971
+ const unitOption = this.config?.units?.find(option => option.value === unit);
11972
+ return unitOption ? this.translateService.instant(unitOption.i18n) : unit;
11973
+ }
11906
11974
  emitValue() {
11907
11975
  const emittable = this.inputType === 'number' ? this.numberInputService.parseValue(this._value.value) : this._value.value;
11908
11976
  if (this.hasUnits) {
@@ -18841,6 +18909,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18841
18909
  }]
18842
18910
  }] });
18843
18911
 
18912
+ class QdDialogConfirmComponent {
18913
+ dialogRef = inject(DialogRef);
18914
+ data = inject(DIALOG_DATA);
18915
+ config = this.data;
18916
+ testId = this.data?.testId ?? 'dialog-confirm';
18917
+ close() {
18918
+ this.dialogRef.close(false);
18919
+ }
18920
+ confirm() {
18921
+ this.dialogRef.close(true);
18922
+ }
18923
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogConfirmComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18924
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogConfirmComponent, isStandalone: false, selector: "qd-dialog-confirm", ngImport: i0, template: "<qd-dialog [data-test-id]=\"testId\">\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ config.message.i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\" [data-test-id]=\"testId + '-close'\">\n {{ config.cancel.i18n | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" [data-test-id]=\"testId + '-proceed'\">\n {{ config.confirm.i18n | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\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: QdTextSectionComponent, selector: "qd-text-section" }, { kind: "component", type: QdTextSectionParagraphComponent, selector: "qd-text-section-paragraph" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18925
+ }
18926
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogConfirmComponent, decorators: [{
18927
+ type: Component,
18928
+ args: [{ selector: 'qd-dialog-confirm', standalone: false, template: "<qd-dialog [data-test-id]=\"testId\">\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ config.message.i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\" [data-test-id]=\"testId + '-close'\">\n {{ config.cancel.i18n | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" [data-test-id]=\"testId + '-proceed'\">\n {{ config.confirm.i18n | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n" }]
18929
+ }] });
18930
+
18844
18931
  /**
18845
18932
  * **QdDialogRecordStepperComponent** provides a pagination for the fullscreen version of **QdDialog**. <br />
18846
18933
  * It is possible to navigate through a list of data provided by the dialog.
@@ -19103,7 +19190,7 @@ class QdDialogConfirmationComponent {
19103
19190
  });
19104
19191
  }
19105
19192
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19106
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogConfirmationComponent, isStandalone: false, selector: "qd-dialog-confirmation", inputs: { config: "config", testId: ["data-test-id", "testId"] }, ngImport: i0, template: "<qd-dialog [attr.data-test-id]=\"testId\">\n <div class=\"loading-overlay\" *ngIf=\"isLoading\">\n <qd-spinner></qd-spinner>\n </div>\n\n <div class=\"headline\" *ngIf=\"isInfoState\">{{ config.info.headline.i18n | translate }}</div>\n\n <div class=\"headline\" *ngIf=\"isSuccessState\">\n {{ (config.success.headline.i18n | translate) || (config.info.headline.i18n | translate) }}\n </div>\n\n <div class=\"headline\" *ngIf=\"isErrorState && config.error.headline.i18n\">\n {{ config.error.headline.i18n | translate }}\n </div>\n\n <div class=\"info\">\n <ng-container *ngIf=\"isInfoState\"> {{ config.info.info.i18n | translate }} </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <qd-icon class=\"success\" [icon]=\"'checkCircleSolid'\"></qd-icon>\n <span>{{ (config.success.info.i18n | translate) || (config.info.info.i18n | translate) }} </span>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState && config.error?.info?.i18n\">\n <qd-icon class=\"error\" [icon]=\"'timesCircleSolid'\"></qd-icon>\n <span>{{ config.error?.info?.i18n | translate }}</span>\n </ng-container>\n </div>\n\n <div class=\"confirm-content\">\n <ng-container *ngIf=\"isInfoState\">\n <ng-content select=\"[qdDialogConfirmInfo]\"></ng-content>\n <qd-checkboxes\n [attr.data-test-id]=\"testId + '-checkbox'\"\n *ngIf=\"useCheckbox\"\n [config]=\"checkboxConfig\"\n [(values)]=\"confirmChecked\"\n ></qd-checkboxes>\n </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <ng-content select=\"[qdDialogConfirmSuccess]\"></ng-content>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState\">\n <ng-content select=\"[qdDialogConfirmError]\"></ng-content>\n </ng-container>\n\n <qd-notifications [context]=\"notificationContext\"></qd-notifications>\n </div>\n\n <qd-dialog-action>\n <ng-container *ngIf=\"isInfoState\">\n <button qdButton qdButtonGhost (click)=\"close()\" [disabled]=\"isLoading\">\n {{ config.buttons.cancel.i18n | translate }}\n </button>\n\n <button qdButton color=\"primary\" (click)=\"confirm()\" [disabled]=\"(useCheckbox && !isConfirmed) || isLoading\">\n {{ config.buttons.next.i18n | translate }}\n </button>\n </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <button qdButton color=\"primary\" (click)=\"submit()\">\n {{ (config.buttons.success?.i18n | translate) || (config.buttons.next.i18n | translate) }}\n </button>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState\">\n <button qdButton color=\"primary\" (click)=\"submit()\">\n {{ (config.buttons.error?.i18n | translate) || (config.buttons.next.i18n | translate) }}\n </button>\n </ng-container>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host .headline{color:#171717;font-size:1rem;font-weight:700;line-height:1.5rem;margin-bottom:.75rem}:host .info{color:#171717;font-size:1rem;font-weight:400;line-height:1.5rem;display:flex}:host .info ::ng-deep .qd-icon{width:.875rem;margin-right:.75rem;font-size:1.25rem;font-weight:300}:host .info ::ng-deep .qd-icon.success{color:#00813a}:host .info ::ng-deep .qd-icon.error{color:#c70023}:host .loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\n"], dependencies: [{ 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: QdCheckboxesComponent, selector: "qd-checkboxes", inputs: ["formControlName", "values", "config", "data-test-id"], outputs: ["valuesChange", "clickHint", "clickReadonly", "clickViewonly"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon", "status"] }, { kind: "component", type: QdNotificationsComponent, selector: "qd-notifications", inputs: ["context"] }, { kind: "component", type: QdSpinnerComponent, selector: "qd-spinner" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19193
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdDialogConfirmationComponent, isStandalone: false, selector: "qd-dialog-confirmation", inputs: { config: "config", testId: ["data-test-id", "testId"] }, ngImport: i0, template: "<qd-dialog [attr.data-test-id]=\"testId\">\n <div class=\"loading-overlay\" *ngIf=\"isLoading\">\n <qd-spinner></qd-spinner>\n </div>\n\n <div class=\"headline\" *ngIf=\"isInfoState\">{{ config.info.headline.i18n | translate }}</div>\n\n <div class=\"headline\" *ngIf=\"isSuccessState\">\n {{ (config.success.headline.i18n | translate) || (config.info.headline.i18n | translate) }}\n </div>\n\n <div class=\"headline\" *ngIf=\"isErrorState && config.error.headline.i18n\">\n {{ config.error.headline.i18n | translate }}\n </div>\n\n <div class=\"info\">\n <ng-container *ngIf=\"isInfoState\"> {{ config.info.info.i18n | translate }} </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <qd-icon class=\"success\" [icon]=\"'checkCircleSolid'\"></qd-icon>\n <span>{{ (config.success.info.i18n | translate) || (config.info.info.i18n | translate) }} </span>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState && config.error?.info?.i18n\">\n <qd-icon class=\"error\" [icon]=\"'timesCircleSolid'\"></qd-icon>\n <span>{{ config.error?.info?.i18n | translate }}</span>\n </ng-container>\n </div>\n\n <div class=\"confirm-content\">\n <ng-container *ngIf=\"isInfoState\">\n <ng-content select=\"[qdDialogConfirmInfo]\"></ng-content>\n <qd-checkboxes\n [attr.data-test-id]=\"testId + '-checkbox'\"\n *ngIf=\"useCheckbox\"\n [config]=\"checkboxConfig\"\n [(values)]=\"confirmChecked\"\n ></qd-checkboxes>\n </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <ng-content select=\"[qdDialogConfirmSuccess]\"></ng-content>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState\">\n <ng-content select=\"[qdDialogConfirmError]\"></ng-content>\n </ng-container>\n\n <qd-notifications [context]=\"notificationContext\"></qd-notifications>\n </div>\n\n <qd-dialog-action>\n <ng-container *ngIf=\"isInfoState\">\n <button qdButton qdButtonGhost (click)=\"close()\" [disabled]=\"isLoading\">\n {{ config.buttons.cancel.i18n | translate }}\n </button>\n\n <button qdButton color=\"primary\" (click)=\"confirm()\" [disabled]=\"(useCheckbox && !isConfirmed) || isLoading\">\n {{ config.buttons.next.i18n | translate }}\n </button>\n </ng-container>\n\n <ng-container *ngIf=\"isSuccessState\">\n <button qdButton color=\"primary\" (click)=\"submit()\">\n {{ (config.buttons.success?.i18n | translate) || (config.buttons.next.i18n | translate) }}\n </button>\n </ng-container>\n\n <ng-container *ngIf=\"isErrorState\">\n <button qdButton color=\"primary\" (click)=\"submit()\">\n {{ (config.buttons.error?.i18n | translate) || (config.buttons.next.i18n | translate) }}\n </button>\n </ng-container>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [":host .headline{color:#171717;font-size:1rem;font-weight:700;line-height:1.5rem;margin-bottom:.75rem}:host .info{color:#171717;font-size:1rem;font-weight:400;line-height:1.5rem;display:flex}:host .info ::ng-deep .qd-icon{width:.875rem;margin-right:.75rem;font-size:1.25rem;font-weight:300}:host .info ::ng-deep .qd-icon.success{color:#00813a}:host .info ::ng-deep .qd-icon.error{color:#c70023}:host .loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#0003}\n"], dependencies: [{ 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: QdCheckboxesComponent, selector: "qd-checkboxes", inputs: ["formControlName", "values", "config", "data-test-id"], outputs: ["valuesChange", "clickHint", "clickReadonly", "clickViewonly"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon", "status"] }, { kind: "component", type: QdNotificationsComponent, selector: "qd-notifications", inputs: ["context"] }, { kind: "component", type: QdSpinnerComponent, selector: "qd-spinner" }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19107
19194
  }
19108
19195
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogConfirmationComponent, decorators: [{
19109
19196
  type: Component,
@@ -19118,7 +19205,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
19118
19205
 
19119
19206
  class QdPageDialogWithBreadcrumbsComponent {
19120
19207
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageDialogWithBreadcrumbsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19121
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdPageDialogWithBreadcrumbsComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: "<qd-dialog>\n <div class=\"wrapper\">\n <qd-breadcrumbs></qd-breadcrumbs>\n <div class=\"router-outlet-wrapper\">\n <router-outlet name=\"dialog\"></router-outlet>\n </div>\n </div>\n</qd-dialog>\n", styles: [".wrapper{display:flex;height:100%;flex-flow:column}qd-breadcrumbs{z-index:99;flex:0 1 auto;padding-right:2.25rem;padding-bottom:.75rem;margin-bottom:-.75rem}.router-outlet-wrapper{position:relative;height:100%;flex:1 1 auto}\n"], dependencies: [{ kind: "directive", type: i2$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: QdBreadcrumbsComponent, selector: "qd-breadcrumbs" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }] });
19208
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdPageDialogWithBreadcrumbsComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: "<qd-dialog>\n <div class=\"wrapper\">\n <qd-breadcrumbs></qd-breadcrumbs>\n <div class=\"router-outlet-wrapper\">\n <router-outlet name=\"dialog\"></router-outlet>\n </div>\n </div>\n</qd-dialog>\n", styles: [".wrapper{display:flex;height:100%;flex-flow:column}qd-breadcrumbs{z-index:99;flex:0 1 auto;padding-right:2.25rem;padding-bottom:.75rem;margin-bottom:-.75rem}.router-outlet-wrapper{position:relative;height:100%;flex:1 1 auto}\n"], dependencies: [{ kind: "directive", type: i2$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: QdBreadcrumbsComponent, selector: "qd-breadcrumbs" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }] });
19122
19209
  }
19123
19210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageDialogWithBreadcrumbsComponent, decorators: [{
19124
19211
  type: Component,
@@ -19129,10 +19216,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
19129
19216
  * Tracks pending changes inside a QdDialog by comparing the current form value with the initial value.
19130
19217
  * Supports both Reactive Forms (FormGroupDirective) and Template-Driven Forms (NgForm).
19131
19218
  *
19132
- * Relevant when closing the dialog (X icon or Cancel button):
19219
+ * Relevant when closing the dialog (X icon, ESC or Cancel button):
19133
19220
  * - if the current value equals the initial value, closing will not trigger a pending-changes warning
19134
19221
  * - the form is reset to pristine, so the dialog behaves like “unchanged”
19135
19222
  *
19223
+ * #### Notes & Best Practices:
19224
+ *
19225
+ * - Pending changes are reported for the dialog that hosts the form. Other open
19226
+ * dialogs are never affected.
19227
+ * - Outside a dialog there is nothing to guard. The form is still reset to
19228
+ * pristine, but no pending changes are reported.
19229
+ *
19136
19230
  * #### **Usage (Reactive Form)**
19137
19231
  *
19138
19232
  * ```html
@@ -19146,6 +19240,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
19146
19240
  class QdPendingChangesGuardDirective {
19147
19241
  ngForm = inject(NgForm, { optional: true });
19148
19242
  formGroupDirective = inject(FormGroupDirective, { optional: true });
19243
+ dialogRef = inject(DialogRef, { optional: true });
19149
19244
  changeGuard = inject(QdDialogChangeGuardService);
19150
19245
  statusSubscription;
19151
19246
  initialValue;
@@ -19165,19 +19260,25 @@ class QdPendingChangesGuardDirective {
19165
19260
  });
19166
19261
  }
19167
19262
  }
19263
+ ngOnDestroy() {
19264
+ this.statusSubscription?.unsubscribe();
19265
+ this.reportPendingChanges(false);
19266
+ }
19168
19267
  updateStatus() {
19169
19268
  if (!this.formGroup)
19170
19269
  return;
19171
19270
  const isPristine = isEqual(this.formGroup.value, this.initialValue);
19172
- const hasChanges = !isPristine;
19173
- this.changeGuard.setPendingChangesStatus(hasChanges);
19271
+ this.reportPendingChanges(!isPristine);
19174
19272
  if (isPristine && this.formGroup.dirty) {
19175
19273
  this.formGroup.markAsPristine();
19176
19274
  }
19177
19275
  }
19178
- ngOnDestroy() {
19179
- this.statusSubscription?.unsubscribe();
19180
- this.changeGuard.setPendingChangesStatus(false);
19276
+ reportPendingChanges(hasChanges) {
19277
+ if (!this.dialogRef)
19278
+ return;
19279
+ if (hasChanges)
19280
+ return this.changeGuard.markPendingChanges(this, this.dialogRef);
19281
+ this.changeGuard.clearPendingChanges(this, this.dialogRef);
19181
19282
  }
19182
19283
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPendingChangesGuardDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
19183
19284
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.18", type: QdPendingChangesGuardDirective, isStandalone: false, selector: "[qdPendingChangesGuard]", ngImport: i0 });
@@ -19195,6 +19296,7 @@ class QdDialogModule {
19195
19296
  static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: QdDialogModule, declarations: [QdDialogActionComponent,
19196
19297
  QdDialogAuthSessionEndComponent,
19197
19298
  QdDialogComponent,
19299
+ QdDialogConfirmComponent,
19198
19300
  QdDialogConfirmationComponent,
19199
19301
  QdDialogConfirmationErrorDirective,
19200
19302
  QdDialogConfirmationInfoDirective,
@@ -19224,6 +19326,10 @@ class QdDialogModule {
19224
19326
  {
19225
19327
  provide: QD_PAGE_DIALOG_WITH_BREADCRUMBS_HOST,
19226
19328
  useValue: QdPageDialogWithBreadcrumbsComponent
19329
+ },
19330
+ {
19331
+ provide: QD_DISCARD_CONFIRM_DIALOG_COMPONENT_TOKEN,
19332
+ useValue: QdDialogConfirmComponent
19227
19333
  }
19228
19334
  ], imports: [CommonModule,
19229
19335
  TranslateModule,
@@ -19259,6 +19365,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
19259
19365
  QdDialogActionComponent,
19260
19366
  QdDialogAuthSessionEndComponent,
19261
19367
  QdDialogComponent,
19368
+ QdDialogConfirmComponent,
19262
19369
  QdDialogConfirmationComponent,
19263
19370
  QdDialogConfirmationErrorDirective,
19264
19371
  QdDialogConfirmationInfoDirective,
@@ -19281,6 +19388,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
19281
19388
  {
19282
19389
  provide: QD_PAGE_DIALOG_WITH_BREADCRUMBS_HOST,
19283
19390
  useValue: QdPageDialogWithBreadcrumbsComponent
19391
+ },
19392
+ {
19393
+ provide: QD_DISCARD_CONFIRM_DIALOG_COMPONENT_TOKEN,
19394
+ useValue: QdDialogConfirmComponent
19284
19395
  }
19285
19396
  ]
19286
19397
  }]
@@ -19716,7 +19827,7 @@ class QdFileCollectorDialogComponent {
19716
19827
  return !!fileUpload.error || fileUpload.progress === 100;
19717
19828
  }
19718
19829
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19719
- 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" }] });
19830
+ 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", inputs: ["data-test-id"] }, { kind: "component", type: QdFileCollectorDialogItemComponent, selector: "qd-file-collector-dialog-item", inputs: ["progress", "collectedFile", "error", "data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19720
19831
  }
19721
19832
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileCollectorDialogComponent, decorators: [{
19722
19833
  type: Component,
@@ -19919,7 +20030,7 @@ class QdFileDeleteDialogComponent {
19919
20030
  this.dialogRef.close(true);
19920
20031
  }
19921
20032
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileDeleteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19922
- 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" }] });
20033
+ 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", inputs: ["data-test-id"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
19923
20034
  }
19924
20035
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFileDeleteDialogComponent, decorators: [{
19925
20036
  type: Component,
@@ -28279,6 +28390,7 @@ class QdTableModule {
28279
28390
  TranslateModule, i1$1.StoreFeatureModule, QdButtonModule,
28280
28391
  QdChipModule,
28281
28392
  QdDataFacetsModule,
28393
+ QdDialogModule,
28282
28394
  QdIconModule,
28283
28395
  QdPopoverModule,
28284
28396
  QdStatusIndicatorModule,
@@ -28291,6 +28403,7 @@ class QdTableModule {
28291
28403
  QdButtonModule,
28292
28404
  QdChipModule,
28293
28405
  QdDataFacetsModule,
28406
+ QdDialogModule,
28294
28407
  QdIconModule,
28295
28408
  QdPopoverModule,
28296
28409
  QdStatusIndicatorModule,
@@ -28308,6 +28421,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
28308
28421
  QdButtonModule,
28309
28422
  QdChipModule,
28310
28423
  QdDataFacetsModule,
28424
+ QdDialogModule,
28311
28425
  QdIconModule,
28312
28426
  QdPopoverModule,
28313
28427
  QdStatusIndicatorModule,
@@ -28713,7 +28827,7 @@ class QdContextSelectDialogComponent {
28713
28827
  });
28714
28828
  }
28715
28829
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdContextSelectDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
28716
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdContextSelectDialogComponent, isStandalone: false, selector: "qd-context-select-dialog", providers: [QdSearchService], ngImport: i0, template: "<qd-dialog>\n <div class=\"search\" *ngIf=\"data.config.hasSearch\">\n <qd-search [configData]=\"searchConfig\"></qd-search>\n </div>\n <qd-table\n [data]=\"tableData\"\n [config]=\"tableConfig\"\n (selectedRowsOutput)=\"select($event)\"\n data-test-id=\"context-select-dialog-table\"\n ></qd-table>\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"cancel()\">\n {{ \"i18n.qd.context.dialog.cancelButtonLabel\" | translate }}\n </button>\n <button qdButton (click)=\"confirm()\" data-test-id=\"context-select-dialog-submit\">\n {{ \"i18n.qd.context.dialog.confirmButtonLabel\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [".search{display:flex;justify-content:end;padding-bottom:.9375rem}\n"], dependencies: [{ 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: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "component", type: QdTableComponent, selector: "qd-table", inputs: ["config", "data", "data-test-id"], outputs: ["secondaryActionOutput", "selectedRowsOutput", "emptyStateActionOutput"] }, { kind: "component", type: QdSearchComponent, selector: "qd-search", inputs: ["configData"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
28830
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdContextSelectDialogComponent, isStandalone: false, selector: "qd-context-select-dialog", providers: [QdSearchService], ngImport: i0, template: "<qd-dialog>\n <div class=\"search\" *ngIf=\"data.config.hasSearch\">\n <qd-search [configData]=\"searchConfig\"></qd-search>\n </div>\n <qd-table\n [data]=\"tableData\"\n [config]=\"tableConfig\"\n (selectedRowsOutput)=\"select($event)\"\n data-test-id=\"context-select-dialog-table\"\n ></qd-table>\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"cancel()\">\n {{ \"i18n.qd.context.dialog.cancelButtonLabel\" | translate }}\n </button>\n <button qdButton (click)=\"confirm()\" data-test-id=\"context-select-dialog-submit\">\n {{ \"i18n.qd.context.dialog.confirmButtonLabel\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [".search{display:flex;justify-content:end;padding-bottom:.9375rem}\n"], dependencies: [{ 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: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }, { kind: "component", type: QdTableComponent, selector: "qd-table", inputs: ["config", "data", "data-test-id"], outputs: ["secondaryActionOutput", "selectedRowsOutput", "emptyStateActionOutput"] }, { kind: "component", type: QdSearchComponent, selector: "qd-search", inputs: ["configData"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
28717
28831
  }
28718
28832
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdContextSelectDialogComponent, decorators: [{
28719
28833
  type: Component,
@@ -28997,8 +29111,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
28997
29111
 
28998
29112
  class QdPageCommitActionExecutor {
28999
29113
  static execute(action, values, context) {
29000
- const { formGroupManager, navigationInterceptor, destroyed$, onAfterSnapshot } = context;
29001
- const captured = formGroupManager.captureFormValues();
29114
+ const { formGroupManager, navigationInterceptor, destroyed$, onAfterSnapshot, scheduleFrameworkRebuildStop } = context;
29115
+ const captured = formGroupManager.createCurrentValuesSnapshot();
29002
29116
  let result$;
29003
29117
  navigationInterceptor.executeWithBypass(() => {
29004
29118
  const handlerResult = action.handler(values);
@@ -29006,7 +29120,9 @@ class QdPageCommitActionExecutor {
29006
29120
  result$ = handlerResult;
29007
29121
  });
29008
29122
  const applySuccess = () => {
29009
- formGroupManager.setFormGroupsSnapshot(captured);
29123
+ formGroupManager.updateSavedSnapshotFromValues(captured);
29124
+ formGroupManager.startAdoptingFrameworkRebuild();
29125
+ scheduleFrameworkRebuildStop?.();
29010
29126
  navigationInterceptor.executeWithBypass(() => {
29011
29127
  try {
29012
29128
  onAfterSnapshot?.();
@@ -29050,136 +29166,170 @@ class QdPageCommitActionExecutor {
29050
29166
  }
29051
29167
 
29052
29168
  /**
29053
- * The QdFormGroupManagerService provides centralized registration, snapshotting, and value tracking
29054
- * for multiple Angular `FormGroup` instances. It supports efficient state restoration, including dynamic
29055
- * resizing of embedded `FormArray` controls, while avoiding any structural cloning in snapshots.
29056
- *
29057
- * #### Purpose:
29058
- * This service is useful when managing multiple reactive forms that must be:
29059
- * - Observed for changes
29060
- * - Checked for validity
29061
- * - Compared against a previous state (snapshot)
29062
- * - Reset to a previous state, including dynamic form arrays
29063
- *
29064
- * #### Value Snapshot
29065
- * Snapshots store only **raw values** (via `getRawValue()`), not any control instances.
29066
- * This allows for minimal memory usage and easy comparison using deep equality checks.
29067
- *
29068
- * #### Smart FormArray Reset
29069
- * When restoring a `FormArray`, the service ensures that the number and structure of controls
29070
- * matches the snapshot values. Controls are cloned dynamically from an existing prototype.
29071
- * If a `FormArray` was emptied (e.g. via `removeAt()`), a minimal control structure is inferred
29072
- * from the snapshot data.
29073
- *
29074
- * #### Example Usage:
29075
- *
29076
- * **Register a FormGroup**
29077
- * ```ts
29078
- * service.setFormGroup('userForm', formGroup);
29079
- * ```
29080
- *
29081
- * **Take a Snapshot**
29082
- * ```ts
29083
- * service.takeFormGroupsSnapshot();
29084
- * ```
29085
- *
29086
- * **Detect Changes**
29087
- * ```ts
29088
- * service.$hasValuesChanged().subscribe(changed => {
29089
- * if (changed) {
29090
- * console.log('Form data has changed');
29091
- * }
29092
- * });
29093
- * ```
29094
- *
29095
- * **Restore from Snapshot**
29096
- * ```ts
29097
- * service.restoreFormGroupsFromSnapshot();
29098
- * ```
29099
- *
29100
- * **Check if All Forms Are Valid**
29101
- * ```ts
29102
- * service.$areFormGroupsValid().subscribe(valid => {
29103
- * if (valid) {
29104
- * console.log('All forms valid!');
29105
- * }
29106
- * });
29107
- * ```
29108
- *
29109
- * #### Notes:
29110
- * - FormArrays are not replaced during restore. Their length is adjusted and their content reset.
29111
- * - Control cloning is recursive and structure-preserving.
29112
- * - No structural information is stored in the snapshot; only data.
29169
+ * Central tracker for all reactive forms on a single QdPage.
29170
+ *
29171
+ * Its main job is to answer one question: **has the user changed anything since the last save?**
29172
+ * The page uses that answer to warn before leaving with unsaved changes.
29173
+ *
29174
+ * #### What it does
29175
+ * - **Registry** holds every form of the page and gives access to it.
29176
+ * - **Change detection** — keeps a snapshot of the saved values and compares the live form against it.
29177
+ * - **Restore** resets the forms back to the snapshot when the user discards changes.
29178
+ * - **Validity** reports whether all forms are valid and can cancel in-flight async validators.
29179
+ *
29180
+ * #### Adopting framework rebuilds
29181
+ * After a save, the consumer often rebuilds the form itself — a `FormArray` cleared and pushed
29182
+ * again, or a control replaced via `setControl`. A plain value compare would wrongly read that as a
29183
+ * user edit. To prevent that:
29184
+ *
29185
+ * - **What it does** right after each save or snapshot write, the framework briefly *adopts its
29186
+ * own rebuild* (`startAdoptingFrameworkRebuild`) and folds those control replacements into the
29187
+ * snapshot, so they count as already saved.
29188
+ * - **Why it is needed** — otherwise a rebuilt value (text `"99"` becoming number `99`) would look
29189
+ * like an unsaved change.
29190
+ * - **When it ends** — the page stops adopting once the rebuild has rendered
29191
+ * (`stopAdoptingFrameworkRebuild`).
29192
+ * - **User edits stay unsaved** — they always happen after that window, so they are still reported.
29193
+ *
29194
+ * #### Notes
29195
+ * - Snapshots store only **raw values** (`getRawValue()`), never control instances — cheap to keep and compare.
29196
+ * - Restoring a `FormArray` adjusts its length and resets its contents; it never replaces the array instance.
29197
+ * - No structural information is stored in the snapshot, only data.
29113
29198
  */
29114
29199
  class QdFormGroupManagerService {
29115
29200
  _formGroups = new Map();
29116
29201
  _formGroupsSnapshot = new Map();
29202
+ _knownControlInstances = new Map();
29203
+ _isAdoptingFrameworkRebuild = false;
29204
+ _frameworkRebuildSub;
29117
29205
  _formGroupsChanged$ = new BehaviorSubject(undefined);
29206
+ _keysInInitialBuildup = new Set();
29207
+ /**
29208
+ * Registers a form under a key so the manager can track it.
29209
+ *
29210
+ * On first registration it also seeds the saved snapshot from the current values, so a freshly
29211
+ * registered form counts as unchanged until the user actually edits it.
29212
+ */
29118
29213
  setFormGroup(key, formGroup) {
29119
29214
  this._formGroups.set(key, formGroup);
29215
+ if (!this._formGroupsSnapshot.has(key)) {
29216
+ this._formGroupsSnapshot.set(key, formGroup.getRawValue());
29217
+ this.rememberControlInstances(key, formGroup);
29218
+ this._keysInInitialBuildup.add(key);
29219
+ }
29120
29220
  this._formGroupsChanged$.next();
29121
29221
  }
29222
+ /**
29223
+ * Returns the form registered under the given key, or `undefined` if none is registered.
29224
+ */
29122
29225
  getFormGroup(key) {
29123
29226
  return this._formGroups.get(key);
29124
29227
  }
29228
+ /**
29229
+ * Returns all registered forms, keyed by their registration key.
29230
+ */
29125
29231
  getAllFormGroups() {
29126
29232
  return this._formGroups;
29127
29233
  }
29128
- tryRemoveFormGroup(key) {
29129
- this._formGroups.delete(key);
29130
- this._formGroupsChanged$.next();
29131
- }
29234
+ /**
29235
+ * Returns `true` if at least one form is registered.
29236
+ */
29132
29237
  hasFormGroups() {
29133
29238
  return this._formGroups.size > 0;
29134
29239
  }
29240
+ /**
29241
+ * Returns `true` if no form is registered under the given key yet.
29242
+ */
29243
+ isFormGroupKeyUnique(key) {
29244
+ return !this._formGroups.has(key);
29245
+ }
29246
+ /**
29247
+ * Returns the current `.value` of every registered form, keyed by registration key.
29248
+ *
29249
+ * Uses Angular's `.value`, so disabled controls are omitted. Use the snapshot methods when you
29250
+ * need the raw values including disabled controls.
29251
+ */
29135
29252
  getAllValues() {
29136
29253
  const allValues = {};
29137
29254
  this._formGroups.forEach((formGroup, key) => (allValues[key] = formGroup.value));
29138
29255
  return allValues;
29139
29256
  }
29140
- isFormGroupKeyUnique(key) {
29141
- return !this._formGroups.has(key);
29257
+ /**
29258
+ * Unregisters the form under the given key and drops its instance tracking.
29259
+ *
29260
+ * Does nothing if no form is registered for the key.
29261
+ */
29262
+ tryRemoveFormGroup(key) {
29263
+ this._formGroups.delete(key);
29264
+ this._knownControlInstances.delete(key);
29265
+ this._keysInInitialBuildup.delete(key);
29266
+ this._formGroupsChanged$.next();
29142
29267
  }
29143
- $areFormGroupsValid() {
29144
- return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
29145
- if (!this.hasFormGroups())
29146
- return of(false);
29147
- const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
29148
- return combineLatest(obs).pipe(map(valids => valids.every(Boolean)));
29149
- }));
29268
+ /**
29269
+ * Ends the initial-buildup phase for a key and fixes its baseline from the form as it is now.
29270
+ *
29271
+ * During that phase the key never counts as changed, so a form filled right after registration
29272
+ * (e.g. by an `effect`) is not flagged as unsaved. This stores the current values as the baseline,
29273
+ * so later edits are measured against the filled form.
29274
+ *
29275
+ * Does nothing if the key is not in its initial-buildup phase.
29276
+ */
29277
+ endInitialBuildup(key) {
29278
+ if (!this._keysInInitialBuildup.delete(key))
29279
+ return;
29280
+ const formGroup = this._formGroups.get(key);
29281
+ if (formGroup) {
29282
+ this._formGroupsSnapshot.set(key, formGroup.getRawValue());
29283
+ this.rememberControlInstances(key, formGroup);
29284
+ }
29285
+ this._formGroupsChanged$.next();
29150
29286
  }
29287
+ /**
29288
+ * Emits whether any registered form differs from its saved snapshot — i.e. whether the user has
29289
+ * unsaved changes.
29290
+ *
29291
+ * Framework-driven rebuilds are folded into the snapshot first while the framework is adopting
29292
+ * them (see the class docs), so they are not reported as changes.
29293
+ */
29151
29294
  $hasValuesChanged() {
29152
- return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
29153
- if (!this.hasFormGroups())
29154
- return of(false);
29155
- const obs = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(fg.getRawValue()), map(() => {
29156
- const currentValues = fg.getRawValue();
29157
- const snapshot = this._formGroupsSnapshot.get(key);
29158
- return snapshot ? !isEqual$1(currentValues, snapshot) : true;
29159
- })));
29160
- return combineLatest(obs).pipe(map(changes => changes.some(Boolean)));
29161
- }));
29295
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => this.anyRegisteredFormChanged$()));
29162
29296
  }
29163
- takeFormGroupsSnapshot() {
29164
- this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
29297
+ /**
29298
+ * Overwrites the saved snapshot with the **current** form values — for a **synchronous** save
29299
+ * that is already finished.
29300
+ *
29301
+ * - Stores each form's raw values (`getRawValue`) as the new saved snapshot.
29302
+ * - Takes them **directly, without a deep copy** — synchronous means no edits can slip in
29303
+ * between, so the cheap path is safe (only the async path needs the copy).
29304
+ * - Afterwards the current state counts as saved; later edits are measured against it.
29305
+ *
29306
+ * For an **asynchronous** save, use `createCurrentValuesSnapshot()` + `updateSavedSnapshotFromValues()`.
29307
+ */
29308
+ updateSavedSnapshotFromCurrentValues() {
29309
+ this._formGroups.forEach((fg, key) => {
29310
+ this._formGroupsSnapshot.set(key, fg.getRawValue());
29311
+ this.rememberControlInstances(key, fg);
29312
+ });
29165
29313
  }
29166
29314
  /**
29167
- * Captures a deep-cloned snapshot of all current form values without mutating internal state.
29315
+ * Creates an independent snapshot of the **current** form values and returns it — for the
29316
+ * **click moment** of an **asynchronous** save.
29168
29317
  *
29169
- * Used by framework orchestration code that needs to remember the "at click time" state of a form
29170
- * so the snapshot can later be set to exactly those values even if the user edits the form
29171
- * while an async commit action is pending.
29318
+ * - Deep-copies each form's raw values (`structuredClone`), detached from the live form.
29319
+ * - Writes **nothing** internally — the saved snapshot stays unchanged.
29320
+ * - Freezes the click-time state: later edits (while the backend runs) do not change the copy.
29321
+ * - Hand the result to `updateSavedSnapshotFromValues()` once the save succeeds.
29172
29322
  *
29173
29323
  * Throws if any form value is not structured-cloneable (functions, symbols, DOM nodes).
29174
29324
  */
29175
- captureFormValues() {
29325
+ createCurrentValuesSnapshot() {
29176
29326
  const captured = new Map();
29177
29327
  this._formGroups.forEach((fg, key) => {
29178
29328
  try {
29179
29329
  captured.set(key, structuredClone(fg.getRawValue()));
29180
29330
  }
29181
29331
  catch (err) {
29182
- throw new Error(`Quadrel Framework | QdFormGroupManager - captureFormValues() failed for group "${key}". ` +
29332
+ throw new Error(`Quadrel Framework | QdFormGroupManager - createCurrentValuesSnapshot() failed for group "${key}". ` +
29183
29333
  `Form values must be structured-cloneable. Non-cloneable values like functions, ` +
29184
29334
  `symbols, or DOM nodes are not supported. Original error: ${String(err)}`);
29185
29335
  }
@@ -29187,15 +29337,27 @@ class QdFormGroupManagerService {
29187
29337
  return captured;
29188
29338
  }
29189
29339
  /**
29190
- * Replaces the internal saved snapshot with the provided values.
29340
+ * Overwrites the saved snapshot with the **given** snapshot — on **success** of an
29341
+ * **asynchronous** save, using the snapshot taken earlier by `createCurrentValuesSnapshot()`.
29191
29342
  *
29192
- * Intended to be paired with `captureFormValues()` so that the snapshot can be set to the
29193
- * values present when a commit action was initiatedindependent of whether the user edited
29194
- * the form in the meantime.
29195
- */
29196
- setFormGroupsSnapshot(values) {
29197
- values.forEach((snapshot, key) => this._formGroupsSnapshot.set(key, snapshot));
29343
+ * - Stores the passed snapshot as the new saved state.
29344
+ * - Uses the click-time snapshot, **not** the current form so edits the user made while the
29345
+ * backend was running stay correctly marked as unsaved.
29346
+ */
29347
+ updateSavedSnapshotFromValues(snapshot) {
29348
+ snapshot.forEach((values, key) => {
29349
+ this._formGroupsSnapshot.set(key, values);
29350
+ const formGroup = this._formGroups.get(key);
29351
+ if (formGroup)
29352
+ this.rememberControlInstances(key, formGroup);
29353
+ });
29198
29354
  }
29355
+ /**
29356
+ * Resets every registered form back to its saved snapshot — used when the user discards changes.
29357
+ *
29358
+ * FormArrays are resized to match the snapshot length; pending async validators are cancelled
29359
+ * afterwards so no stale validation result survives the restore.
29360
+ */
29199
29361
  restoreFormGroupsFromSnapshot() {
29200
29362
  this._formGroups.forEach((fg, key) => {
29201
29363
  const snapshot = this._formGroupsSnapshot.get(key);
@@ -29206,68 +29368,171 @@ class QdFormGroupManagerService {
29206
29368
  this.cancelPendingAsyncValidation();
29207
29369
  }
29208
29370
  /**
29209
- * Cancels any in-flight async validators on all registered form groups.
29371
+ * Starts adopting framework rebuilds into the snapshot. While active, a framework-driven control
29372
+ * replacement (a `setControl` on a leaf or a whole `FormArray` rebuilt via clear/push) is folded
29373
+ * into the saved snapshot, so it is not reported as an unsaved change.
29374
+ *
29375
+ * User edits never fall in this short window, so they stay dirty. The framework starts it after a
29376
+ * successful commit and after every snapshot write; the page stops it deterministically once the
29377
+ * rebuild has rendered.
29378
+ */
29379
+ startAdoptingFrameworkRebuild() {
29380
+ this._isAdoptingFrameworkRebuild = true;
29381
+ this._frameworkRebuildSub?.unsubscribe();
29382
+ const valueStreams = Array.from(this._formGroups.values()).map(fg => fg.valueChanges);
29383
+ if (valueStreams.length)
29384
+ this._frameworkRebuildSub = merge(...valueStreams).subscribe(() => this.adoptFrameworkRebuild());
29385
+ this.adoptFrameworkRebuild();
29386
+ }
29387
+ /**
29388
+ * Stops adopting framework rebuilds. After this, control replacements count as user edits again.
29389
+ *
29390
+ * The page calls this once the framework rebuild has rendered (via `afterNextRender`).
29391
+ */
29392
+ stopAdoptingFrameworkRebuild() {
29393
+ this._isAdoptingFrameworkRebuild = false;
29394
+ this._frameworkRebuildSub?.unsubscribe();
29395
+ }
29396
+ /**
29397
+ * Emits whether every registered form is currently valid.
29398
+ *
29399
+ * A disabled control without validators is treated as valid.
29400
+ */
29401
+ $areFormGroupsValid() {
29402
+ return this._formGroupsChanged$.pipe(observeOn(queueScheduler), switchMap$1(() => {
29403
+ if (!this.hasFormGroups())
29404
+ return of(false);
29405
+ const obs = Array.from(this._formGroups.values()).map(fg => fg.statusChanges.pipe(startWith(fg.status), map(() => this.areFormGroupsValid(fg))));
29406
+ return combineLatest(obs).pipe(map(valids => valids.every(Boolean)));
29407
+ }));
29408
+ }
29409
+ /**
29410
+ * Cancels every in-flight (PENDING) async validator across all registered forms, so no stale
29411
+ * validation result can land after a restore or a commit.
29210
29412
  *
29211
- * 1. Collect all PENDING controls and their async validators
29212
- * 2. Clear all async validators, then update validity (sync-only, no cascade)
29213
- * 3. Re-attach async validators for future use
29413
+ * The async validators themselves are kept only the pending run is dropped. Each phase runs
29414
+ * over **all** pending controls before the next starts (detach → re-evaluate sync-only, no
29415
+ * cascade → re-attach), so validity is never recomputed while a run is still settling.
29214
29416
  */
29215
29417
  cancelPendingAsyncValidation() {
29216
29418
  this._formGroups.forEach(fg => {
29217
29419
  const pendingControls = this.collectPendingControls(fg);
29218
29420
  if (pendingControls.length === 0)
29219
29421
  return;
29220
- for (const { control } of pendingControls) {
29221
- control.clearAsyncValidators();
29222
- }
29223
- for (const { control } of pendingControls) {
29224
- control.updateValueAndValidity({ onlySelf: true });
29225
- }
29226
- for (const { control, asyncValidator } of pendingControls) {
29227
- control.setAsyncValidators(asyncValidator);
29228
- }
29422
+ pendingControls.forEach(({ control }) => control.clearAsyncValidators());
29423
+ pendingControls.forEach(({ control }) => control.updateValueAndValidity({ onlySelf: true }));
29424
+ pendingControls.forEach(({ control, asyncValidator }) => control.setAsyncValidators(asyncValidator));
29229
29425
  });
29230
29426
  }
29231
- restoreFormGroup(fg, snapshot) {
29232
- Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => {
29233
- const newValue = snapshot[ctrlKey];
29234
- if (ctrl instanceof FormArray && Array.isArray(newValue)) {
29235
- this.resetFormArrayToValues(ctrl, newValue);
29236
- }
29237
- else if (ctrl instanceof FormGroup && newValue && typeof newValue === 'object') {
29238
- this.restoreFormGroup(ctrl, newValue);
29239
- }
29240
- else {
29241
- ctrl.reset(newValue);
29242
- }
29427
+ anyRegisteredFormChanged$() {
29428
+ if (!this.hasFormGroups())
29429
+ return of(false);
29430
+ const perForm = Array.from(this._formGroups.entries()).map(([key, fg]) => fg.valueChanges.pipe(startWith(null), map(() => this.hasFormChanged(key, fg))));
29431
+ return combineLatest(perForm).pipe(map(flags => flags.some(Boolean)));
29432
+ }
29433
+ hasFormChanged(key, fg) {
29434
+ if (this._keysInInitialBuildup.has(key))
29435
+ return false;
29436
+ const snapshot = this._formGroupsSnapshot.get(key);
29437
+ if (snapshot)
29438
+ this.adoptRebuildInGroup(key, fg, snapshot);
29439
+ return snapshot ? !isEqual$1(fg.getRawValue(), snapshot) : true;
29440
+ }
29441
+ adoptFrameworkRebuild() {
29442
+ this._formGroups.forEach((fg, key) => {
29443
+ const snapshot = this._formGroupsSnapshot.get(key);
29444
+ if (snapshot)
29445
+ this.adoptRebuildInGroup(key, fg, snapshot);
29243
29446
  });
29244
29447
  }
29245
- collectPendingControls(control) {
29246
- const result = [];
29247
- if (control instanceof FormGroup) {
29248
- Object.values(control.controls).forEach(c => result.push(...this.collectPendingControls(c)));
29249
- }
29250
- else if (control instanceof FormArray) {
29251
- control.controls.forEach(c => result.push(...this.collectPendingControls(c)));
29252
- }
29253
- if (control.status === 'PENDING') {
29254
- result.push({ control, asyncValidator: control.asyncValidator });
29448
+ adoptRebuildInGroup(key, formGroup, snapshot) {
29449
+ const tracked = this._knownControlInstances.get(key) ?? new Map();
29450
+ this.adoptRebuiltControls(formGroup, snapshot, tracked, '');
29451
+ this._knownControlInstances.set(key, tracked);
29452
+ }
29453
+ adoptRebuiltControls(group, snapshot, tracked, prefix) {
29454
+ Object.entries(group.controls).forEach(([name, control]) => {
29455
+ const path = prefix ? `${prefix}.${name}` : name;
29456
+ const isNewKey = !(name in snapshot);
29457
+ if (control instanceof FormArray)
29458
+ this.adoptRebuiltList(control, snapshot, name, isNewKey);
29459
+ else if (control instanceof FormGroup)
29460
+ this.adoptRebuiltGroup(control, snapshot, name, isNewKey, tracked, path);
29461
+ else
29462
+ this.adoptRebuiltField(control, snapshot, name, isNewKey, tracked.get(path));
29463
+ tracked.set(path, control);
29464
+ });
29465
+ }
29466
+ adoptRebuiltList(control, snapshot, name, isNewKey) {
29467
+ if (isNewKey) {
29468
+ this.adoptNewPristineControl(control, snapshot, name);
29469
+ return;
29255
29470
  }
29256
- return result;
29471
+ if (!this.isAdoptableArrayRebuild(control, snapshot, name))
29472
+ return;
29473
+ const snapshotArray = snapshot[name];
29474
+ const currentArray = control.getRawValue();
29475
+ if (currentArray.length === snapshotArray.length && !isEqual$1(currentArray, snapshotArray))
29476
+ snapshot[name] = currentArray;
29477
+ }
29478
+ adoptRebuiltGroup(group, snapshot, name, isNewKey, tracked, path) {
29479
+ if (isNewKey)
29480
+ this.adoptNewPristineControl(group, snapshot, name);
29481
+ const nested = snapshot[name];
29482
+ if (this.isPlainObject(nested))
29483
+ this.adoptRebuiltControls(group, nested, tracked, path);
29484
+ }
29485
+ adoptRebuiltField(control, snapshot, name, isNewKey, known) {
29486
+ if (isNewKey)
29487
+ this.adoptNewPristineControl(control, snapshot, name);
29488
+ else if (this.isReplacedPristineControl(control, known))
29489
+ snapshot[name] = control.getRawValue();
29490
+ }
29491
+ adoptNewPristineControl(control, snapshot, name) {
29492
+ if (control.pristine)
29493
+ snapshot[name] = control.getRawValue();
29494
+ }
29495
+ isAdoptableArrayRebuild(control, snapshot, name) {
29496
+ return this._isAdoptingFrameworkRebuild && control.pristine && Array.isArray(snapshot[name]);
29497
+ }
29498
+ isReplacedPristineControl(control, known) {
29499
+ return this._isAdoptingFrameworkRebuild && !!known && known !== control && control.pristine;
29500
+ }
29501
+ rememberControlInstances(key, formGroup) {
29502
+ const tracked = new Map();
29503
+ this.collectControlInstances(formGroup, tracked, '');
29504
+ this._knownControlInstances.set(key, tracked);
29505
+ }
29506
+ collectControlInstances(group, tracked, prefix) {
29507
+ Object.entries(group.controls).forEach(([name, control]) => {
29508
+ const path = prefix ? `${prefix}.${name}` : name;
29509
+ tracked.set(path, control);
29510
+ if (control instanceof FormGroup)
29511
+ this.collectControlInstances(control, tracked, path);
29512
+ });
29513
+ }
29514
+ restoreFormGroup(fg, snapshot) {
29515
+ Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => this.restoreControl(ctrl, snapshot[ctrlKey]));
29516
+ }
29517
+ restoreControl(ctrl, newValue) {
29518
+ if (ctrl instanceof FormArray && Array.isArray(newValue))
29519
+ this.resetFormArrayToValues(ctrl, newValue);
29520
+ else if (ctrl instanceof FormGroup && this.isPlainObject(newValue))
29521
+ this.restoreFormGroup(ctrl, newValue);
29522
+ else
29523
+ ctrl.reset(newValue);
29524
+ }
29525
+ isPlainObject(value) {
29526
+ return !!value && typeof value === 'object';
29257
29527
  }
29258
29528
  resetFormArrayToValues(array, newValues) {
29259
29529
  if (!Array.isArray(newValues))
29260
29530
  return;
29261
- const oldLen = array.length;
29262
- const newLen = newValues.length;
29263
- if (oldLen === 0 && newLen === 0)
29531
+ if (array.length === 0 && newValues.length === 0)
29264
29532
  return;
29265
- if (oldLen === 0 && newLen > 0)
29533
+ if (array.length === 0)
29266
29534
  return this.initFormArrayFromValues(array, newValues);
29267
- while (array.length < newLen)
29268
- array.push(this.deepCloneControl(array.at(0)));
29269
- while (array.length > newLen)
29270
- array.removeAt(array.length - 1);
29535
+ this.resizeFormArray(array, newValues.length);
29271
29536
  array.reset(newValues);
29272
29537
  }
29273
29538
  initFormArrayFromValues(array, values) {
@@ -29283,6 +29548,12 @@ class QdFormGroupManagerService {
29283
29548
  }
29284
29549
  array.reset(values);
29285
29550
  }
29551
+ resizeFormArray(array, length) {
29552
+ while (array.length < length)
29553
+ array.push(this.deepCloneControl(array.at(0)));
29554
+ while (array.length > length)
29555
+ array.removeAt(array.length - 1);
29556
+ }
29286
29557
  deepCloneControl(control) {
29287
29558
  if (control instanceof FormControl)
29288
29559
  return new FormControl(undefined, control.validator, control.asyncValidator);
@@ -29300,6 +29571,19 @@ class QdFormGroupManagerService {
29300
29571
  areFormGroupsValid(fg) {
29301
29572
  return Object.values(fg.controls).every(c => (c.disabled && !c.validator && !c.asyncValidator ? true : c.valid));
29302
29573
  }
29574
+ collectPendingControls(control) {
29575
+ const result = this.childControlsOf(control).flatMap(child => this.collectPendingControls(child));
29576
+ if (control.status === 'PENDING')
29577
+ result.push({ control, asyncValidator: control.asyncValidator });
29578
+ return result;
29579
+ }
29580
+ childControlsOf(control) {
29581
+ if (control instanceof FormGroup)
29582
+ return Object.values(control.controls);
29583
+ if (control instanceof FormArray)
29584
+ return control.controls;
29585
+ return [];
29586
+ }
29303
29587
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFormGroupManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
29304
29588
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdFormGroupManagerService });
29305
29589
  }
@@ -29328,7 +29612,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
29328
29612
  * - The page switches to view mode via `deactivate()`.
29329
29613
  *
29330
29614
  * The service has no concept of "pending actions" or bypass windows. Framework actions update the
29331
- * saved snapshot directly via `QdFormGroupManagerService.setFormGroupsSnapshot(...)`; cancel-discard
29615
+ * saved snapshot directly via `QdFormGroupManagerService.updateSavedSnapshotFromValues(...)`; cancel-discard
29332
29616
  * flows reset the form to the saved snapshot via `restoreFormGroupsFromSnapshot()`. Either way, the
29333
29617
  * interceptor only observes the resulting form state — there is no time window where a bypass is active.
29334
29618
  */
@@ -29478,6 +29762,7 @@ class QdPageObjectHeaderComponent {
29478
29762
  confirmationDialogService = inject(QdConfirmationDialogOpenerService);
29479
29763
  contextService = inject(QdContextService);
29480
29764
  resolverTriggerService = inject(QdResolverTriggerService);
29765
+ injector = inject(Injector);
29481
29766
  pageStoreService = inject(QdPageStoreService);
29482
29767
  config;
29483
29768
  hasNavigation = false;
@@ -29598,7 +29883,7 @@ class QdPageObjectHeaderComponent {
29598
29883
  this.setupResolverTrigger();
29599
29884
  this.updateCustomActions();
29600
29885
  this.subscribeToMetadataStream();
29601
- this.formGroupManagerService.takeFormGroupsSnapshot();
29886
+ this.formGroupManagerService.updateSavedSnapshotFromCurrentValues();
29602
29887
  this.initContexts();
29603
29888
  }
29604
29889
  ngOnChanges(changes) {
@@ -29623,7 +29908,7 @@ class QdPageObjectHeaderComponent {
29623
29908
  return this.config.headerFacets != null;
29624
29909
  }
29625
29910
  edit() {
29626
- this.formGroupManagerService.takeFormGroupsSnapshot();
29911
+ this.formGroupManagerService.updateSavedSnapshotFromCurrentValues();
29627
29912
  this.pageStoreService.toggleViewonly(false);
29628
29913
  if (this.editButton?.handler)
29629
29914
  this.editButton.handler();
@@ -29653,7 +29938,8 @@ class QdPageObjectHeaderComponent {
29653
29938
  onAfterSnapshot: () => {
29654
29939
  this.formGroupManagerService.cancelPendingAsyncValidation();
29655
29940
  this.pageStoreService.toggleViewonly(true);
29656
- }
29941
+ },
29942
+ scheduleFrameworkRebuildStop: () => afterNextRender(() => this.formGroupManagerService.stopAdoptingFrameworkRebuild(), { injector: this.injector })
29657
29943
  });
29658
29944
  }
29659
29945
  changeContext(context, selection, event) {
@@ -29681,7 +29967,7 @@ class QdPageObjectHeaderComponent {
29681
29967
  .pipe(takeUntil(this._destroyed$), filter(shouldTrigger => shouldTrigger), tap(() => this._isLoadingSubject.next(true)), switchMap(() => this.pageObjectResolver.resolve()), tap(objectData => {
29682
29968
  this._pageObjectDataSubject.next({ ...objectData, ...this._pendingMetadata });
29683
29969
  this._pendingMetadata = {};
29684
- }), tap(() => this._isLoadingSubject.next(false)), tap(() => this.formGroupManagerService.takeFormGroupsSnapshot()))
29970
+ }), tap(() => this._isLoadingSubject.next(false)), tap(() => this.formGroupManagerService.updateSavedSnapshotFromCurrentValues()))
29685
29971
  .subscribe();
29686
29972
  }
29687
29973
  initContexts() {
@@ -29872,7 +30158,7 @@ class QdPageStepperCancelDialogComponent {
29872
30158
  </button>
29873
30159
  <button qdButton (click)="close(true)">{{ 'i18n.qd.stepper.cancel.dialog.delete' | translate }}</button>
29874
30160
  </qd-dialog-action>
29875
- </qd-dialog> `, isInline: true, 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: "component", type: QdTextSectionComponent, selector: "qd-text-section" }, { kind: "component", type: QdTextSectionParagraphComponent, selector: "qd-text-section-paragraph" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
30161
+ </qd-dialog> `, isInline: true, 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", inputs: ["data-test-id"] }, { kind: "component", type: QdTextSectionComponent, selector: "qd-text-section" }, { kind: "component", type: QdTextSectionParagraphComponent, selector: "qd-text-section-paragraph" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29876
30162
  }
29877
30163
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageStepperCancelDialogComponent, decorators: [{
29878
30164
  type: Component,
@@ -31459,6 +31745,7 @@ class QdPageComponent {
31459
31745
  bottomOffset$ = inject(QD_SAFE_BOTTOM_OFFSET, { optional: true });
31460
31746
  dialogRef = inject(DialogRef, { optional: true });
31461
31747
  navigationInterceptor = inject(QdPageNavigationInterceptorService);
31748
+ injector = inject(Injector);
31462
31749
  confirmationDialogService = inject(QdConfirmationDialogOpenerService);
31463
31750
  /**
31464
31751
  * This property defines the configuration for the QdPage component, including the page type,
@@ -31655,7 +31942,10 @@ class QdPageComponent {
31655
31942
  QdPageCommitActionExecutor.execute(action, values, {
31656
31943
  formGroupManager: this.formGroupManagerService,
31657
31944
  navigationInterceptor: this.navigationInterceptor,
31658
- destroyed$: this._destroyed$
31945
+ destroyed$: this._destroyed$,
31946
+ scheduleFrameworkRebuildStop: () => afterNextRender(() => this.formGroupManagerService.stopAdoptingFrameworkRebuild(), {
31947
+ injector: this.injector
31948
+ })
31659
31949
  });
31660
31950
  };
31661
31951
  }
@@ -31767,6 +32057,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
31767
32057
  * - Snapshots store only raw values and are taken by the page/header workflow (e.g. on init, edit, save). The confirmation dialog may restore all registered form groups from the snapshot.
31768
32058
  * - FormArrays are restored with dynamic resizing (length is aligned to snapshot before values are reset).
31769
32059
  *
32060
+ * ### **Filling a form right after connecting it**
32061
+ * - You can fill a connected form right after registration — e.g. an `effect` that adds controls or pushes `FormArray` rows. This first fill is the form's starting point, not a user change.
32062
+ * - So loading data into a fresh form never triggers a false "unsaved changes" prompt. Only later edits count.
32063
+ *
31770
32064
  * ### **Page-Dialog (FullWidth) – Unsaved-Changes Close Contract**
31771
32065
  * - When a QdPage is embedded in a full-width page dialog, the directive’s registered form groups are also used to guard dialog closing.
31772
32066
  * - The page dialog can only close (e.g. via ESC or backdrop) if no unsaved changes are detected ($hasValuesChanged() is false).
@@ -31779,8 +32073,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
31779
32073
  * ```html
31780
32074
  * <qd-page [config]="pageConfig">
31781
32075
  * <qd-section [config]="sectionConfig">
31782
- * <qd-grid [formGroup]="myFormGroup" qdConnectFormStateToPage="myFormGroup">
31783
- * My Section
32076
+ * <qd-grid
32077
+ * qdConnectFormStateToPage="myFormGroup"
32078
+ * [formGroup]="myFormGroup">
32079
+ * My Section
31784
32080
  * </qd-grid>
31785
32081
  * </qd-section>
31786
32082
  * </qd-page>
@@ -31800,36 +32096,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
31800
32096
  class QdConnectFormStateToPageDirective {
31801
32097
  formGroupManagerService = inject(QdFormGroupManagerService, { optional: true });
31802
32098
  formGroupDirective = inject(FormGroupDirective, { host: true, optional: true });
32099
+ closeInitialBuildupHandle;
31803
32100
  /**
31804
32101
  * This key is used to manage and track the form state within the page. It has to be unique for each FormGroup Connector. Use the property name of the FormGroup in your component.
31805
32102
  */
31806
32103
  qdConnectFormStateToPage;
31807
32104
  ngOnInit() {
31808
- if (!this.isValid())
31809
- return;
31810
32105
  const formGroup = this.formGroupDirective?.control;
31811
- if (formGroup && this.qdConnectFormStateToPage) {
31812
- this.formGroupManagerService.setFormGroup(this.qdConnectFormStateToPage, formGroup);
31813
- }
31814
- }
31815
- ngOnDestroy() {
31816
- this.formGroupManagerService?.tryRemoveFormGroup(this.qdConnectFormStateToPage);
31817
- }
31818
- isValid() {
31819
- const hasFormGroup = this.formGroupDirective?.control;
31820
- if (!hasFormGroup) {
32106
+ if (!formGroup) {
31821
32107
  console.error('Quadrel Framework | QdConnectFormStateToPage - Either a [formGroup] binding or <qd-quick-edit> with a FormGroup is required.');
31822
- return false;
32108
+ return;
31823
32109
  }
31824
32110
  if (!this.formGroupManagerService) {
31825
32111
  console.error('Quadrel Framework | QdConnectFormStateToPage - The connector must be used within <qd-page>.');
31826
- return false;
32112
+ return;
31827
32113
  }
31828
32114
  if (!this.formGroupManagerService.isFormGroupKeyUnique(this.qdConnectFormStateToPage)) {
31829
32115
  console.error(`Quadrel Framework | QdConnectFormStateToPage - The FormGroup key "${this.qdConnectFormStateToPage}" is not unique.`);
31830
- return false;
32116
+ return;
31831
32117
  }
31832
- return true;
32118
+ this.formGroupManagerService.setFormGroup(this.qdConnectFormStateToPage, formGroup);
32119
+ this.scheduleInitialBaseline(this.formGroupManagerService);
32120
+ }
32121
+ ngOnDestroy() {
32122
+ if (this.closeInitialBuildupHandle)
32123
+ clearTimeout(this.closeInitialBuildupHandle);
32124
+ this.formGroupManagerService?.tryRemoveFormGroup(this.qdConnectFormStateToPage);
32125
+ }
32126
+ scheduleInitialBaseline(manager) {
32127
+ const key = this.qdConnectFormStateToPage;
32128
+ this.closeInitialBuildupHandle = setTimeout(() => manager.endInitialBuildup(key));
31833
32129
  }
31834
32130
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdConnectFormStateToPageDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
31835
32131
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.18", type: QdConnectFormStateToPageDirective, isStandalone: false, selector: "[qdConnectFormStateToPage]", inputs: { qdConnectFormStateToPage: "qdConnectFormStateToPage" }, ngImport: i0 });
@@ -33762,7 +34058,7 @@ class AddCommentDialogComponent {
33762
34058
  };
33763
34059
  }
33764
34060
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AddCommentDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
33765
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: AddCommentDialogComponent, isStandalone: false, selector: "qd-comment-dialog", ngImport: i0, template: "<qd-dialog>\n <form [formGroup]=\"form\">\n <qd-input [config]=\"authorInputConfig\"></qd-input>\n <qd-dropdown\n *ngIf=\"customInputConfig\"\n [config]=\"customInputConfig\"\n [formControlName]=\"'custom'\"\n [attr.data-test-id]=\"'comment-custom'\"\n ></qd-dropdown>\n\n <qd-richtext [config]=\"richtextConfig\" #commentText [formControlName]=\"'comment'\"> </qd-richtext>\n </form>\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\" [data-test-id]=\"'button-cancel'\">\n {{ \"i18n.qd.comments.add.dialog.cancel\" | translate }}\n </button>\n <button\n [disabled]=\"!commentText.value || commentText.hasError\"\n qdButton\n (click)=\"close('Yes')\"\n [data-test-id]=\"'button-submit'\"\n >\n {{ \"i18n.qd.comments.add.dialog.submit\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [".validation-message{margin-top:4px;color:red;font-size:.875em}\n"], dependencies: [{ 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: QdDropdownComponent, selector: "qd-dropdown", inputs: ["value", "id", "formControlName", "config", "data-test-id", "qdPopoverMaxHeight", "dense"], outputs: ["valueChange", "enterClick", "clickHint", "clickReadonly", "clickViewonly", "optionsResolved"] }, { kind: "component", type: QdInputComponent, selector: "qd-input", inputs: ["formControlName", "value", "config", "isError", "data-test-id"], outputs: ["valueChange", "enterClick", "clickClear", "clickHint", "clickReadonly", "clickViewonly", "optionsResolved"] }, { kind: "component", type: QdRichtextComponent, selector: "qd-richtext", inputs: ["formControlName", "value", "config", "data-test-id"], outputs: ["valueChange", "clickHint", "clickReadonly", "clickViewonly"] }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog" }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: AddCommentDialogComponent, isStandalone: false, selector: "qd-comment-dialog", ngImport: i0, template: "<qd-dialog>\n <form [formGroup]=\"form\">\n <qd-input [config]=\"authorInputConfig\"></qd-input>\n <qd-dropdown\n *ngIf=\"customInputConfig\"\n [config]=\"customInputConfig\"\n [formControlName]=\"'custom'\"\n [attr.data-test-id]=\"'comment-custom'\"\n ></qd-dropdown>\n\n <qd-richtext [config]=\"richtextConfig\" #commentText [formControlName]=\"'comment'\"> </qd-richtext>\n </form>\n <qd-dialog-action>\n <button qdButton qdButtonGhost color=\"secondary\" (click)=\"close()\" [data-test-id]=\"'button-cancel'\">\n {{ \"i18n.qd.comments.add.dialog.cancel\" | translate }}\n </button>\n <button\n [disabled]=\"!commentText.value || commentText.hasError\"\n qdButton\n (click)=\"close('Yes')\"\n [data-test-id]=\"'button-submit'\"\n >\n {{ \"i18n.qd.comments.add.dialog.submit\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [".validation-message{margin-top:4px;color:red;font-size:.875em}\n"], dependencies: [{ 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: QdDropdownComponent, selector: "qd-dropdown", inputs: ["value", "id", "formControlName", "config", "data-test-id", "qdPopoverMaxHeight", "dense"], outputs: ["valueChange", "enterClick", "clickHint", "clickReadonly", "clickViewonly", "optionsResolved"] }, { kind: "component", type: QdInputComponent, selector: "qd-input", inputs: ["formControlName", "value", "config", "isError", "data-test-id"], outputs: ["valueChange", "enterClick", "clickClear", "clickHint", "clickReadonly", "clickViewonly", "optionsResolved"] }, { kind: "component", type: QdRichtextComponent, selector: "qd-richtext", inputs: ["formControlName", "value", "config", "data-test-id"], outputs: ["valueChange", "clickHint", "clickReadonly", "clickViewonly"] }, { kind: "component", type: QdDialogActionComponent, selector: "qd-dialog-action" }, { kind: "component", type: QdDialogComponent, selector: "qd-dialog", inputs: ["data-test-id"] }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33766
34062
  }
33767
34063
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AddCommentDialogComponent, decorators: [{
33768
34064
  type: Component,