@quadrel-enterprise-ui/framework 20.8.0 → 20.9.1-beta.137.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.
@@ -1146,18 +1146,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1146
1146
  }]
1147
1147
  }] });
1148
1148
 
1149
+ const GENERIC_DEFAULTS = {
1150
+ title: { i18n: 'i18n.qd.dialog.confirm.title' },
1151
+ confirm: { i18n: 'i18n.qd.dialog.confirm.proceed' },
1152
+ cancel: { i18n: 'i18n.qd.dialog.action.cancel' }
1153
+ };
1154
+ const CANCEL_DEFAULTS = {
1155
+ title: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.title' },
1156
+ message: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.message' },
1157
+ confirm: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.proceed' },
1158
+ cancel: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.close' }
1159
+ };
1149
1160
  class QdConfirmationDialogOpenerService {
1150
1161
  dialog = inject(Dialog);
1151
- showCancelConfirmation(config) {
1152
- return from(Promise.resolve().then(function () { return cancelDialog_component; }).then(m => m.QdCancelDialogComponent)).pipe(switchMap(component => {
1162
+ showConfirmDialog(config, testId = 'dialog-confirm') {
1163
+ const resolved = {
1164
+ title: config.title ?? GENERIC_DEFAULTS.title,
1165
+ message: config.message,
1166
+ confirm: config.confirm ?? GENERIC_DEFAULTS.confirm,
1167
+ cancel: config.cancel ?? GENERIC_DEFAULTS.cancel
1168
+ };
1169
+ return from(import('./quadrel-enterprise-ui-framework-dialog-confirm.module-BQLiEzSo.mjs').then(m => m.QdDialogConfirmComponent)).pipe(switchMap(component => {
1153
1170
  const dialogRef = this.open(component, {
1154
- title: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.title' },
1171
+ title: resolved.title,
1155
1172
  dialogSize: QdDialogSize.Small,
1156
- data: config
1173
+ data: { ...resolved, testId }
1157
1174
  });
1158
1175
  return dialogRef.closed.pipe(map$1(result => !!result));
1159
1176
  }));
1160
1177
  }
1178
+ showDiscardConfirmDialog(message, testId = 'cancel-confirmation') {
1179
+ return this.showConfirmDialog({
1180
+ title: CANCEL_DEFAULTS.title,
1181
+ message: message ?? CANCEL_DEFAULTS.message,
1182
+ confirm: CANCEL_DEFAULTS.confirm,
1183
+ cancel: CANCEL_DEFAULTS.cancel
1184
+ }, testId);
1185
+ }
1161
1186
  open(component, config) {
1162
1187
  config.panelClass = 'qd-custom-panel';
1163
1188
  config.width = config.dialogSize || QdDialogSize.Default;
@@ -1470,7 +1495,7 @@ class QdDialogComponent {
1470
1495
  }
1471
1496
  confirmAndClose() {
1472
1497
  this.dialog
1473
- .showCancelConfirmation(this.config)
1498
+ .showDiscardConfirmDialog(this.config.cancel?.confirmationMessage, 'dialog-cancel-confirmation')
1474
1499
  .pipe(take(1))
1475
1500
  .subscribe(confirmed => {
1476
1501
  if (!confirmed)
@@ -9186,7 +9211,7 @@ class QdDateAdapter {
9186
9211
  static parseToDate(formatted) {
9187
9212
  if (!formatted)
9188
9213
  return new Date();
9189
- return moment(formatted, 'l', 'de').toDate();
9214
+ return moment(formatted, 'DD.MM.YYYY').toDate();
9190
9215
  }
9191
9216
  /**
9192
9217
  * Converts a localized Swiss date and time string into a JavaScript Date object.
@@ -9206,9 +9231,9 @@ class QdDateAdapter {
9206
9231
  return new Date(new Date().setHours(hours, minutes, 0, 0));
9207
9232
  }
9208
9233
  else if (date && !time) {
9209
- return moment(date, 'l', 'de').toDate();
9234
+ return moment(date, 'DD.MM.YYYY').toDate();
9210
9235
  }
9211
- let format = 'l ';
9236
+ let format = 'DD.MM.YYYY ';
9212
9237
  switch (time.length) {
9213
9238
  case 8:
9214
9239
  format = format + 'HH:mm:ss';
@@ -9217,14 +9242,14 @@ class QdDateAdapter {
9217
9242
  default:
9218
9243
  format = format + 'HH:mm';
9219
9244
  }
9220
- return moment(`${date} ${time}`, format, 'de').toDate();
9245
+ return moment(`${date} ${time}`, format).toDate();
9221
9246
  }
9222
9247
  /**
9223
9248
  * Converts a localized date string into a Moment.js object.
9224
9249
  * If the input is null or undefined, it returns undefined.
9225
9250
  */
9226
9251
  static parseToMoment(formatted) {
9227
- return formatted ? moment(formatted, 'l', 'de').locale('de') : undefined;
9252
+ return formatted ? moment(formatted, 'DD.MM.YYYY').locale('de') : undefined;
9228
9253
  }
9229
9254
  /**
9230
9255
  * Formats a Moment.js object into a localized date string.
@@ -9233,7 +9258,7 @@ class QdDateAdapter {
9233
9258
  static formatToLocalizedDateString(momentObj) {
9234
9259
  if (!momentObj)
9235
9260
  return '';
9236
- return momentObj.locale('de').format('L');
9261
+ return momentObj.format('DD.MM.YYYY');
9237
9262
  }
9238
9263
  /**
9239
9264
  * Formats a JavaScript Date object into a localized date string.
@@ -9258,7 +9283,7 @@ class QdDateAdapter {
9258
9283
  * Returns true if the date is valid, otherwise false.
9259
9284
  */
9260
9285
  static isLocalizedDateStringValid(formatted) {
9261
- return moment(formatted, 'L', 'de', true).isValid();
9286
+ return moment(formatted, 'DD.MM.YYYY', true).isValid();
9262
9287
  }
9263
9288
  /**
9264
9289
  * Validates if a date-time string matches the Swiss `DD.MM.YYYY HH:mm:ss` format.
@@ -9314,9 +9339,8 @@ class QdCalendarComponent {
9314
9339
  this.setWeekdays();
9315
9340
  this.todayDate = moment();
9316
9341
  this.months = moment.months();
9317
- this.years = this.getYears((QdDateAdapter.parseToMoment(this.selectedDate)?.isValid()
9318
- ? moment(this.selectedDate, 'L', this.language)
9319
- : this.todayDate).year());
9342
+ const parsedSelected = QdDateAdapter.parseToMoment(this.selectedDate);
9343
+ this.years = this.getYears((parsedSelected?.isValid() ? parsedSelected : this.todayDate).year());
9320
9344
  this.setCurrentDate();
9321
9345
  this.generateCalendar(this.currentDate);
9322
9346
  }
@@ -9406,9 +9430,8 @@ class QdCalendarComponent {
9406
9430
  return this.todayDate.isSame(momentObj, 'day');
9407
9431
  }
9408
9432
  setCurrentDate() {
9409
- const currentDate = (this.currentDate = QdDateAdapter.parseToMoment(this.selectedDate)?.isValid()
9410
- ? moment(this.selectedDate, 'L', 'de')
9411
- : this.todayDate);
9433
+ const parsedDate = QdDateAdapter.parseToMoment(this.selectedDate);
9434
+ const currentDate = (this.currentDate = parsedDate?.isValid() ? parsedDate : this.todayDate);
9412
9435
  this.currentDate = currentDate;
9413
9436
  this.selectedDate = QdDateAdapter.formatToLocalizedDateString(currentDate);
9414
9437
  }
@@ -10970,6 +10993,7 @@ class QdInputComponent {
10970
10993
  hasAutofocus;
10971
10994
  hasOptions = false;
10972
10995
  _value = { value: '' };
10996
+ _displayValue = '';
10973
10997
  control;
10974
10998
  _optionsResolver;
10975
10999
  _subs = new Subscription();
@@ -11039,6 +11063,7 @@ class QdInputComponent {
11039
11063
  }
11040
11064
  writeValue(value) {
11041
11065
  this._value = getValueWithUnit(value, this.config);
11066
+ this._displayValue = String(this._value.value ?? '');
11042
11067
  }
11043
11068
  setDisabledState(disabled) {
11044
11069
  this.disabled = disabled;
@@ -11169,7 +11194,7 @@ class QdInputComponent {
11169
11194
  provide: QD_FOCUSABLE_TOKEN,
11170
11195
  useExisting: QdInputComponent
11171
11196
  }
11172
- ], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<qd-form-label\n [label]=\"label\"\n [readonly]=\"readonly\"\n [viewonly]=\"viewonly\"\n [control]=\"control\"\n [tooltip]=\"config?.tooltip\"\n [data-test-id]=\"testId\"\n></qd-form-label>\n\n<ng-container *ngIf=\"!readonly && !viewonly && !hasOptions\">\n <div class=\"qd-input-input\" (keydown.enter)=\"emitEnterClick()\">\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</ng-container>\n\n<div *ngIf=\"!readonly && !viewonly && hasOptions\">\n <div\n class=\"qd-input-input\"\n qdInputOptions\n [qdPopoverMinWidth]=\"200\"\n [config]=\"config\"\n [value]=\"_value.value\"\n (optionSelected)=\"handleOptionSelected($event)\"\n (enterClick)=\"emitEnterClick()\"\n >\n <!-- handle (enterClick) by options directive here because event has to be fired after selection -->\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</div>\n\n<qd-form-readonly\n *ngIf=\"readonly\"\n [values]=\"valueAsList\"\n [readonlyAction]=\"readonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-readonly>\n\n<qd-form-viewonly\n *ngIf=\"viewonly\"\n [values]=\"valueAsList\"\n [viewonlyAction]=\"viewonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-viewonly>\n\n<ng-template #inputBox>\n <input\n #input\n [placeholder]=\"placeholder | translate\"\n [value]=\"hasOptions ? (_value.value.toString() | translate) : _value.value\"\n (input)=\"handleInput($event)\"\n (focus)=\"handleInputFocus()\"\n (blur)=\"handleInputBlur()\"\n [disabled]=\"disabled || readonly\"\n [type]=\"inputType\"\n [attr.inputmode]=\"inputMode\"\n [qdAutofocus]=\"hasAutofocus\"\n [attr.data-test-id]=\"testId + '-input'\"\n [step]=\"config?.step\"\n required\n />\n <div class=\"qd-input-suffix\">\n <qd-icon *ngIf=\"hasError && !hasOnlyUnitsError\" class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n <qd-icon *ngIf=\"clearable\" class=\"qd-input-clearable-icon\" icon=\"timesLarge\" (click)=\"clearInput()\"></qd-icon>\n <ng-content select=\"[qdIconButton]\"></ng-content>\n </div>\n <qd-input-units\n *ngIf=\"hasUnits\"\n [unit]=\"_value.unit\"\n [config]=\"config\"\n (unitChange)=\"handleUnitChange($event)\"\n (opened)=\"handleUnitsOpened()\"\n (closed)=\"handleUnitsClosed()\"\n ></qd-input-units>\n <div class=\"qd-input-suffix\" *ngIf=\"hasError && hasOnlyUnitsError\">\n <qd-icon class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n </div>\n</ng-template>\n", styles: [":host{position:relative;display:block;width:100%;flex-direction:column;margin-bottom:.75rem}:host .qd-input-input{display:flex;overflow:hidden;height:2.25rem;align-items:center;padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(180,180,180);border-radius:0;margin-bottom:.375rem;background-color:#fff}:host .qd-input-input:hover,:host .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(23,23,23);border-radius:0}:host .qd-input-input input{overflow:hidden;width:100%;flex-grow:1;border:none;background-color:#fff;color:#171717;font-size:.875rem;line-height:1.875rem}:host .qd-input-input input:hover,:host .qd-input-input input:focus,:host .qd-input-input input:active{border:none;outline:none}:host .qd-input-input input::placeholder{color:#b4b4b4}:host .qd-input-input input:focus::placeholder{display:block;color:#b4b4b4}:host .qd-input-input .qd-input-suffix{display:flex;align-items:center;margin-left:.75rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon{padding-right:.5rem;color:#c70023}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon+.qd-input-clearable-icon{margin-left:-.25rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon{margin-right:.5625rem;color:#979797;cursor:pointer;font-size:1.25rem;line-height:2rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:hover,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:focus,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:active{color:#171717}:host .qd-input-input qd-input-units+.qd-input-suffix{margin:0 0 0 -.1875rem}:host .qd-input-character-counter{padding-left:.125rem;color:#757575;float:right;font-size:.75rem;font-weight:300;line-height:.75rem}:host:after{display:block;height:0;clear:both;content:\".\";visibility:hidden}:host.qd-input-focus .qd-input-input{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(0,102,153);border-radius:0;outline:none}:host.qd-input-readonly .qd-input-readonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-viewonly .qd-input-viewonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-disabled .qd-input-input{border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled .qd-input-input input{background-color:#f5f5f5;color:#979797}:host.qd-input-disabled .qd-input-input input::placeholder{opacity:0}:host.qd-input-disabled .qd-input-input:hover,:host.qd-input-disabled .qd-input-input:active{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled.qd-input-focus{border-color:#ff9b00}:host.qd-input--readonly-action .qd-input-readonly{color:#069;cursor:pointer}:host.qd-input--readonly-action .qd-input-readonly:hover,:host.qd-input--readonly-action .qd-input-readonly:active,:host.qd-input--readonly-action .qd-input-readonly:focus{text-decoration:underline}:host.qd-input--viewonly-action .qd-input-viewonly{color:#069;cursor:pointer}:host.qd-input--viewonly-action .qd-input-viewonly:hover,:host.qd-input--viewonly-action .qd-input-viewonly:active,:host.qd-input--viewonly-action .qd-input-viewonly:focus{text-decoration:underline}:host.qd-input-error .qd-input-input,:host.qd-input-error-from-outside .qd-input-input{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(199,0,35)}:host.qd-input-error .qd-input-input:hover,:host.qd-input-error .qd-input-input:active,:host.qd-input-error-from-outside .qd-input-input:hover,:host.qd-input-error-from-outside .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(199,0,35)}:host.qd-input-error.qd-input-focus,:host.qd-input-error-from-outside.qd-input-focus{border-color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: QdAutofocusDirective, selector: "[qdAutofocus]", inputs: ["qdAutofocus"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }, { kind: "component", type: QdFormHintComponent, selector: "qd-form-hint", inputs: ["hint", "control", "hasError", "hintAction", "data-test-id"] }, { kind: "component", type: QdFormLabelComponent, selector: "qd-form-label", inputs: ["label", "isDisabled", "readonly", "viewonly", "control", "tooltip", "data-test-id"] }, { kind: "component", type: QdFormReadonlyComponent, selector: "qd-form-readonly", inputs: ["values", "readonlyAction", "data-test-id"] }, { kind: "component", type: QdFormViewonlyComponent, selector: "qd-form-viewonly", inputs: ["values", "viewonlyAction", "data-test-id"] }, { kind: "component", type: QdInputUnitsComponent, selector: "qd-input-units", inputs: ["config", "unit"], outputs: ["unitChange", "opened", "closed"] }, { kind: "directive", type: QdInputOptionsDirective, selector: "[qdInputOptions]", inputs: ["value", "config"], outputs: ["enterClick", "optionSelected"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
11197
+ ], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<qd-form-label\n [label]=\"label\"\n [readonly]=\"readonly\"\n [viewonly]=\"viewonly\"\n [control]=\"control\"\n [tooltip]=\"config?.tooltip\"\n [data-test-id]=\"testId\"\n></qd-form-label>\n\n<ng-container *ngIf=\"!readonly && !viewonly && !hasOptions\">\n <div class=\"qd-input-input\" (keydown.enter)=\"emitEnterClick()\">\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</ng-container>\n\n<div *ngIf=\"!readonly && !viewonly && hasOptions\">\n <div\n class=\"qd-input-input\"\n qdInputOptions\n [qdPopoverMinWidth]=\"200\"\n [config]=\"config\"\n [value]=\"_value.value\"\n (optionSelected)=\"handleOptionSelected($event)\"\n (enterClick)=\"emitEnterClick()\"\n >\n <!-- handle (enterClick) by options directive here because event has to be fired after selection -->\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</div>\n\n<qd-form-readonly\n *ngIf=\"readonly\"\n [values]=\"valueAsList\"\n [readonlyAction]=\"readonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-readonly>\n\n<qd-form-viewonly\n *ngIf=\"viewonly\"\n [values]=\"valueAsList\"\n [viewonlyAction]=\"viewonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-viewonly>\n\n<ng-template #inputBox>\n <input\n #input\n [placeholder]=\"placeholder | translate\"\n [value]=\"hasOptions ? (_value.value.toString() | translate) : _displayValue\"\n (input)=\"handleInput($event)\"\n (focus)=\"handleInputFocus()\"\n (blur)=\"handleInputBlur()\"\n [disabled]=\"disabled || readonly\"\n [type]=\"inputType\"\n [attr.inputmode]=\"inputMode\"\n [qdAutofocus]=\"hasAutofocus\"\n [attr.data-test-id]=\"testId + '-input'\"\n [step]=\"config?.step\"\n required\n />\n <div class=\"qd-input-suffix\">\n <qd-icon *ngIf=\"hasError && !hasOnlyUnitsError\" class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n <qd-icon *ngIf=\"clearable\" class=\"qd-input-clearable-icon\" icon=\"timesLarge\" (click)=\"clearInput()\"></qd-icon>\n <ng-content select=\"[qdIconButton]\"></ng-content>\n </div>\n <qd-input-units\n *ngIf=\"hasUnits\"\n [unit]=\"_value.unit\"\n [config]=\"config\"\n (unitChange)=\"handleUnitChange($event)\"\n (opened)=\"handleUnitsOpened()\"\n (closed)=\"handleUnitsClosed()\"\n ></qd-input-units>\n <div class=\"qd-input-suffix\" *ngIf=\"hasError && hasOnlyUnitsError\">\n <qd-icon class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n </div>\n</ng-template>\n", styles: [":host{position:relative;display:block;width:100%;flex-direction:column;margin-bottom:.75rem}:host .qd-input-input{display:flex;overflow:hidden;height:2.25rem;align-items:center;padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(180,180,180);border-radius:0;margin-bottom:.375rem;background-color:#fff}:host .qd-input-input:hover,:host .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(23,23,23);border-radius:0}:host .qd-input-input input{overflow:hidden;width:100%;flex-grow:1;border:none;background-color:#fff;color:#171717;font-size:.875rem;line-height:1.875rem}:host .qd-input-input input:hover,:host .qd-input-input input:focus,:host .qd-input-input input:active{border:none;outline:none}:host .qd-input-input input::placeholder{color:#b4b4b4}:host .qd-input-input input:focus::placeholder{display:block;color:#b4b4b4}:host .qd-input-input .qd-input-suffix{display:flex;align-items:center;margin-left:.75rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon{padding-right:.5rem;color:#c70023}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon+.qd-input-clearable-icon{margin-left:-.25rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon{margin-right:.5625rem;color:#979797;cursor:pointer;font-size:1.25rem;line-height:2rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:hover,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:focus,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:active{color:#171717}:host .qd-input-input qd-input-units+.qd-input-suffix{margin:0 0 0 -.1875rem}:host .qd-input-character-counter{padding-left:.125rem;color:#757575;float:right;font-size:.75rem;font-weight:300;line-height:.75rem}:host:after{display:block;height:0;clear:both;content:\".\";visibility:hidden}:host.qd-input-focus .qd-input-input{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(0,102,153);border-radius:0;outline:none}:host.qd-input-readonly .qd-input-readonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-viewonly .qd-input-viewonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-disabled .qd-input-input{border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled .qd-input-input input{background-color:#f5f5f5;color:#979797}:host.qd-input-disabled .qd-input-input input::placeholder{opacity:0}:host.qd-input-disabled .qd-input-input:hover,:host.qd-input-disabled .qd-input-input:active{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled.qd-input-focus{border-color:#ff9b00}:host.qd-input--readonly-action .qd-input-readonly{color:#069;cursor:pointer}:host.qd-input--readonly-action .qd-input-readonly:hover,:host.qd-input--readonly-action .qd-input-readonly:active,:host.qd-input--readonly-action .qd-input-readonly:focus{text-decoration:underline}:host.qd-input--viewonly-action .qd-input-viewonly{color:#069;cursor:pointer}:host.qd-input--viewonly-action .qd-input-viewonly:hover,:host.qd-input--viewonly-action .qd-input-viewonly:active,:host.qd-input--viewonly-action .qd-input-viewonly:focus{text-decoration:underline}:host.qd-input-error .qd-input-input,:host.qd-input-error-from-outside .qd-input-input{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(199,0,35)}:host.qd-input-error .qd-input-input:hover,:host.qd-input-error .qd-input-input:active,:host.qd-input-error-from-outside .qd-input-input:hover,:host.qd-input-error-from-outside .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(199,0,35)}:host.qd-input-error.qd-input-focus,:host.qd-input-error-from-outside.qd-input-focus{border-color:#c70023}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: QdAutofocusDirective, selector: "[qdAutofocus]", inputs: ["qdAutofocus"] }, { kind: "component", type: QdIconComponent, selector: "qd-icon", inputs: ["icon"] }, { kind: "component", type: QdFormHintComponent, selector: "qd-form-hint", inputs: ["hint", "control", "hasError", "hintAction", "data-test-id"] }, { kind: "component", type: QdFormLabelComponent, selector: "qd-form-label", inputs: ["label", "isDisabled", "readonly", "viewonly", "control", "tooltip", "data-test-id"] }, { kind: "component", type: QdFormReadonlyComponent, selector: "qd-form-readonly", inputs: ["values", "readonlyAction", "data-test-id"] }, { kind: "component", type: QdFormViewonlyComponent, selector: "qd-form-viewonly", inputs: ["values", "viewonlyAction", "data-test-id"] }, { kind: "component", type: QdInputUnitsComponent, selector: "qd-input-units", inputs: ["config", "unit"], outputs: ["unitChange", "opened", "closed"] }, { kind: "directive", type: QdInputOptionsDirective, selector: "[qdInputOptions]", inputs: ["value", "config"], outputs: ["enterClick", "optionSelected"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
11173
11198
  }
11174
11199
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdInputComponent, decorators: [{
11175
11200
  type: Component,
@@ -11187,7 +11212,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
11187
11212
  provide: QD_FOCUSABLE_TOKEN,
11188
11213
  useExisting: QdInputComponent
11189
11214
  }
11190
- ], standalone: false, template: "<qd-form-label\n [label]=\"label\"\n [readonly]=\"readonly\"\n [viewonly]=\"viewonly\"\n [control]=\"control\"\n [tooltip]=\"config?.tooltip\"\n [data-test-id]=\"testId\"\n></qd-form-label>\n\n<ng-container *ngIf=\"!readonly && !viewonly && !hasOptions\">\n <div class=\"qd-input-input\" (keydown.enter)=\"emitEnterClick()\">\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</ng-container>\n\n<div *ngIf=\"!readonly && !viewonly && hasOptions\">\n <div\n class=\"qd-input-input\"\n qdInputOptions\n [qdPopoverMinWidth]=\"200\"\n [config]=\"config\"\n [value]=\"_value.value\"\n (optionSelected)=\"handleOptionSelected($event)\"\n (enterClick)=\"emitEnterClick()\"\n >\n <!-- handle (enterClick) by options directive here because event has to be fired after selection -->\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</div>\n\n<qd-form-readonly\n *ngIf=\"readonly\"\n [values]=\"valueAsList\"\n [readonlyAction]=\"readonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-readonly>\n\n<qd-form-viewonly\n *ngIf=\"viewonly\"\n [values]=\"valueAsList\"\n [viewonlyAction]=\"viewonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-viewonly>\n\n<ng-template #inputBox>\n <input\n #input\n [placeholder]=\"placeholder | translate\"\n [value]=\"hasOptions ? (_value.value.toString() | translate) : _value.value\"\n (input)=\"handleInput($event)\"\n (focus)=\"handleInputFocus()\"\n (blur)=\"handleInputBlur()\"\n [disabled]=\"disabled || readonly\"\n [type]=\"inputType\"\n [attr.inputmode]=\"inputMode\"\n [qdAutofocus]=\"hasAutofocus\"\n [attr.data-test-id]=\"testId + '-input'\"\n [step]=\"config?.step\"\n required\n />\n <div class=\"qd-input-suffix\">\n <qd-icon *ngIf=\"hasError && !hasOnlyUnitsError\" class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n <qd-icon *ngIf=\"clearable\" class=\"qd-input-clearable-icon\" icon=\"timesLarge\" (click)=\"clearInput()\"></qd-icon>\n <ng-content select=\"[qdIconButton]\"></ng-content>\n </div>\n <qd-input-units\n *ngIf=\"hasUnits\"\n [unit]=\"_value.unit\"\n [config]=\"config\"\n (unitChange)=\"handleUnitChange($event)\"\n (opened)=\"handleUnitsOpened()\"\n (closed)=\"handleUnitsClosed()\"\n ></qd-input-units>\n <div class=\"qd-input-suffix\" *ngIf=\"hasError && hasOnlyUnitsError\">\n <qd-icon class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n </div>\n</ng-template>\n", styles: [":host{position:relative;display:block;width:100%;flex-direction:column;margin-bottom:.75rem}:host .qd-input-input{display:flex;overflow:hidden;height:2.25rem;align-items:center;padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(180,180,180);border-radius:0;margin-bottom:.375rem;background-color:#fff}:host .qd-input-input:hover,:host .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(23,23,23);border-radius:0}:host .qd-input-input input{overflow:hidden;width:100%;flex-grow:1;border:none;background-color:#fff;color:#171717;font-size:.875rem;line-height:1.875rem}:host .qd-input-input input:hover,:host .qd-input-input input:focus,:host .qd-input-input input:active{border:none;outline:none}:host .qd-input-input input::placeholder{color:#b4b4b4}:host .qd-input-input input:focus::placeholder{display:block;color:#b4b4b4}:host .qd-input-input .qd-input-suffix{display:flex;align-items:center;margin-left:.75rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon{padding-right:.5rem;color:#c70023}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon+.qd-input-clearable-icon{margin-left:-.25rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon{margin-right:.5625rem;color:#979797;cursor:pointer;font-size:1.25rem;line-height:2rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:hover,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:focus,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:active{color:#171717}:host .qd-input-input qd-input-units+.qd-input-suffix{margin:0 0 0 -.1875rem}:host .qd-input-character-counter{padding-left:.125rem;color:#757575;float:right;font-size:.75rem;font-weight:300;line-height:.75rem}:host:after{display:block;height:0;clear:both;content:\".\";visibility:hidden}:host.qd-input-focus .qd-input-input{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(0,102,153);border-radius:0;outline:none}:host.qd-input-readonly .qd-input-readonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-viewonly .qd-input-viewonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-disabled .qd-input-input{border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled .qd-input-input input{background-color:#f5f5f5;color:#979797}:host.qd-input-disabled .qd-input-input input::placeholder{opacity:0}:host.qd-input-disabled .qd-input-input:hover,:host.qd-input-disabled .qd-input-input:active{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled.qd-input-focus{border-color:#ff9b00}:host.qd-input--readonly-action .qd-input-readonly{color:#069;cursor:pointer}:host.qd-input--readonly-action .qd-input-readonly:hover,:host.qd-input--readonly-action .qd-input-readonly:active,:host.qd-input--readonly-action .qd-input-readonly:focus{text-decoration:underline}:host.qd-input--viewonly-action .qd-input-viewonly{color:#069;cursor:pointer}:host.qd-input--viewonly-action .qd-input-viewonly:hover,:host.qd-input--viewonly-action .qd-input-viewonly:active,:host.qd-input--viewonly-action .qd-input-viewonly:focus{text-decoration:underline}:host.qd-input-error .qd-input-input,:host.qd-input-error-from-outside .qd-input-input{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(199,0,35)}:host.qd-input-error .qd-input-input:hover,:host.qd-input-error .qd-input-input:active,:host.qd-input-error-from-outside .qd-input-input:hover,:host.qd-input-error-from-outside .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(199,0,35)}:host.qd-input-error.qd-input-focus,:host.qd-input-error-from-outside.qd-input-focus{border-color:#c70023}\n"] }]
11215
+ ], standalone: false, template: "<qd-form-label\n [label]=\"label\"\n [readonly]=\"readonly\"\n [viewonly]=\"viewonly\"\n [control]=\"control\"\n [tooltip]=\"config?.tooltip\"\n [data-test-id]=\"testId\"\n></qd-form-label>\n\n<ng-container *ngIf=\"!readonly && !viewonly && !hasOptions\">\n <div class=\"qd-input-input\" (keydown.enter)=\"emitEnterClick()\">\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</ng-container>\n\n<div *ngIf=\"!readonly && !viewonly && hasOptions\">\n <div\n class=\"qd-input-input\"\n qdInputOptions\n [qdPopoverMinWidth]=\"200\"\n [config]=\"config\"\n [value]=\"_value.value\"\n (optionSelected)=\"handleOptionSelected($event)\"\n (enterClick)=\"emitEnterClick()\"\n >\n <!-- handle (enterClick) by options directive here because event has to be fired after selection -->\n <ng-container *ngTemplateOutlet=\"inputBox\"></ng-container>\n </div>\n\n <span class=\"qd-input-character-counter\" *ngIf=\"hasMaxLength\">{{ numberOfCharacters }} / {{ maxLength }}</span>\n\n <qd-form-hint\n *ngIf=\"hint || hasError\"\n [hint]=\"hint\"\n [control]=\"control\"\n [hasError]=\"hasError\"\n [hintAction]=\"hintAction\"\n [data-test-id]=\"testId\"\n ></qd-form-hint>\n</div>\n\n<qd-form-readonly\n *ngIf=\"readonly\"\n [values]=\"valueAsList\"\n [readonlyAction]=\"readonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-readonly>\n\n<qd-form-viewonly\n *ngIf=\"viewonly\"\n [values]=\"valueAsList\"\n [viewonlyAction]=\"viewonlyAction\"\n [data-test-id]=\"testId\"\n></qd-form-viewonly>\n\n<ng-template #inputBox>\n <input\n #input\n [placeholder]=\"placeholder | translate\"\n [value]=\"hasOptions ? (_value.value.toString() | translate) : _displayValue\"\n (input)=\"handleInput($event)\"\n (focus)=\"handleInputFocus()\"\n (blur)=\"handleInputBlur()\"\n [disabled]=\"disabled || readonly\"\n [type]=\"inputType\"\n [attr.inputmode]=\"inputMode\"\n [qdAutofocus]=\"hasAutofocus\"\n [attr.data-test-id]=\"testId + '-input'\"\n [step]=\"config?.step\"\n required\n />\n <div class=\"qd-input-suffix\">\n <qd-icon *ngIf=\"hasError && !hasOnlyUnitsError\" class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n <qd-icon *ngIf=\"clearable\" class=\"qd-input-clearable-icon\" icon=\"timesLarge\" (click)=\"clearInput()\"></qd-icon>\n <ng-content select=\"[qdIconButton]\"></ng-content>\n </div>\n <qd-input-units\n *ngIf=\"hasUnits\"\n [unit]=\"_value.unit\"\n [config]=\"config\"\n (unitChange)=\"handleUnitChange($event)\"\n (opened)=\"handleUnitsOpened()\"\n (closed)=\"handleUnitsClosed()\"\n ></qd-input-units>\n <div class=\"qd-input-suffix\" *ngIf=\"hasError && hasOnlyUnitsError\">\n <qd-icon class=\"qd-input-error-icon\" icon=\"exclamationCircleSolid\"></qd-icon>\n </div>\n</ng-template>\n", styles: [":host{position:relative;display:block;width:100%;flex-direction:column;margin-bottom:.75rem}:host .qd-input-input{display:flex;overflow:hidden;height:2.25rem;align-items:center;padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(180,180,180);border-radius:0;margin-bottom:.375rem;background-color:#fff}:host .qd-input-input:hover,:host .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(23,23,23);border-radius:0}:host .qd-input-input input{overflow:hidden;width:100%;flex-grow:1;border:none;background-color:#fff;color:#171717;font-size:.875rem;line-height:1.875rem}:host .qd-input-input input:hover,:host .qd-input-input input:focus,:host .qd-input-input input:active{border:none;outline:none}:host .qd-input-input input::placeholder{color:#b4b4b4}:host .qd-input-input input:focus::placeholder{display:block;color:#b4b4b4}:host .qd-input-input .qd-input-suffix{display:flex;align-items:center;margin-left:.75rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon{padding-right:.5rem;color:#c70023}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-error-icon+.qd-input-clearable-icon{margin-left:-.25rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon{margin-right:.5625rem;color:#979797;cursor:pointer;font-size:1.25rem;line-height:2rem}:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:hover,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:focus,:host .qd-input-input .qd-input-suffix ::ng-deep .qd-input-clearable-icon:active{color:#171717}:host .qd-input-input qd-input-units+.qd-input-suffix{margin:0 0 0 -.1875rem}:host .qd-input-character-counter{padding-left:.125rem;color:#757575;float:right;font-size:.75rem;font-weight:300;line-height:.75rem}:host:after{display:block;height:0;clear:both;content:\".\";visibility:hidden}:host.qd-input-focus .qd-input-input{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(0,102,153);border-radius:0;outline:none}:host.qd-input-readonly .qd-input-readonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-viewonly .qd-input-viewonly{color:#171717;font-size:.875rem;line-height:2.25rem}:host.qd-input-disabled .qd-input-input{border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled .qd-input-input input{background-color:#f5f5f5;color:#979797}:host.qd-input-disabled .qd-input-input input::placeholder{opacity:0}:host.qd-input-disabled .qd-input-input:hover,:host.qd-input-disabled .qd-input-input:active{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(151,151,151);background-color:#f5f5f5}:host.qd-input-disabled.qd-input-focus{border-color:#ff9b00}:host.qd-input--readonly-action .qd-input-readonly{color:#069;cursor:pointer}:host.qd-input--readonly-action .qd-input-readonly:hover,:host.qd-input--readonly-action .qd-input-readonly:active,:host.qd-input--readonly-action .qd-input-readonly:focus{text-decoration:underline}:host.qd-input--viewonly-action .qd-input-viewonly{color:#069;cursor:pointer}:host.qd-input--viewonly-action .qd-input-viewonly:hover,:host.qd-input--viewonly-action .qd-input-viewonly:active,:host.qd-input--viewonly-action .qd-input-viewonly:focus{text-decoration:underline}:host.qd-input-error .qd-input-input,:host.qd-input-error-from-outside .qd-input-input{padding:0 .0625rem 0 .5rem;border:.0625rem solid rgb(199,0,35)}:host.qd-input-error .qd-input-input:hover,:host.qd-input-error .qd-input-input:active,:host.qd-input-error-from-outside .qd-input-input:hover,:host.qd-input-error-from-outside .qd-input-input:active{padding:.0625rem 0 .0625rem .4375rem;border:.125rem solid rgb(199,0,35)}:host.qd-input-error.qd-input-focus,:host.qd-input-error-from-outside.qd-input-focus{border-color:#c70023}\n"] }]
11191
11216
  }], propDecorators: { formControlName: [{
11192
11217
  type: Input
11193
11218
  }], value: [{
@@ -11409,8 +11434,6 @@ class QdDatepickerComponent {
11409
11434
  this.displayedDateTime = date ? QdDateAdapter.formatToDateTimeLocaleString(date, this.config?.enableSeconds) : '';
11410
11435
  this.displayedTime = date ? QdDateAdapter.formatToTimeLocaleString(date, this.config?.enableSeconds) : '';
11411
11436
  this.changeDetectorRef.detectChanges();
11412
- this.valueChange.emit(date);
11413
- this._onChange(date);
11414
11437
  }
11415
11438
  setDisabledState(disabled) {
11416
11439
  this.disabled = disabled;
@@ -12848,7 +12871,7 @@ class QdValidators {
12848
12871
  return ({ value }) => {
12849
12872
  if (!value)
12850
12873
  return null;
12851
- const localizedFormat = moment().locale('de').format('L');
12874
+ const localizedFormat = moment().format('DD.MM.YYYY');
12852
12875
  const formattedErrorKey = { date: { key: errorKey, params: { format: localizedFormat } } };
12853
12876
  if (value === 'Invalid Date')
12854
12877
  return formattedErrorKey;
@@ -18161,37 +18184,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18161
18184
  }]
18162
18185
  }], ctorParameters: () => [] });
18163
18186
 
18164
- class QdCancelDialogComponent {
18165
- dialogRef = inject(DialogRef);
18166
- i18n;
18167
- handler;
18168
- ngOnInit() {
18169
- this.i18n =
18170
- this.dialogRef.config.data?.cancel?.confirmationMessage?.i18n ??
18171
- 'i18n.qd.page.cancel.confirmation.dialog.message';
18172
- this.handler = this.dialogRef.config.data?.cancel?.handler;
18173
- }
18174
- close() {
18175
- this.dialogRef.close(false);
18176
- }
18177
- confirm() {
18178
- if (this.handler)
18179
- this.handler();
18180
- this.dialogRef.close(true);
18181
- }
18182
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdCancelDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdCancelDialogComponent, isStandalone: false, selector: "qd-cancel-dialog", ngImport: i0, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n data-test-id=\"dialog-cancel-confirmation-dialog-close\"\n >\n {{ \"i18n.qd.page.cancel.confirmation.dialog.close\" | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" data-test-id=\"dialog-cancel-confirmation-dialog-proceed\">\n {{ \"i18n.qd.page.cancel.confirmation.dialog.proceed\" | 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" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
18184
- }
18185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdCancelDialogComponent, decorators: [{
18186
- type: Component,
18187
- args: [{ selector: 'qd-cancel-dialog', standalone: false, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n data-test-id=\"dialog-cancel-confirmation-dialog-close\"\n >\n {{ \"i18n.qd.page.cancel.confirmation.dialog.close\" | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" data-test-id=\"dialog-cancel-confirmation-dialog-proceed\">\n {{ \"i18n.qd.page.cancel.confirmation.dialog.proceed\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n" }]
18188
- }] });
18189
-
18190
- var cancelDialog_component = /*#__PURE__*/Object.freeze({
18191
- __proto__: null,
18192
- QdCancelDialogComponent: QdCancelDialogComponent
18193
- });
18194
-
18195
18187
  class QdDialogModule {
18196
18188
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
18197
18189
  static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: QdDialogModule, declarations: [QdDialogActionComponent,
@@ -18203,8 +18195,7 @@ class QdDialogModule {
18203
18195
  QdDialogConfirmationSuccessDirective,
18204
18196
  QdDialogRecordStepperComponent,
18205
18197
  QdPageDialogWithBreadcrumbsComponent,
18206
- QdPendingChangesGuardDirective,
18207
- QdCancelDialogComponent], imports: [CommonModule,
18198
+ QdPendingChangesGuardDirective], imports: [CommonModule,
18208
18199
  TranslateModule,
18209
18200
  RouterModule,
18210
18201
  DialogModule,
@@ -18268,8 +18259,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
18268
18259
  QdDialogConfirmationSuccessDirective,
18269
18260
  QdDialogRecordStepperComponent,
18270
18261
  QdPageDialogWithBreadcrumbsComponent,
18271
- QdPendingChangesGuardDirective,
18272
- QdCancelDialogComponent
18262
+ QdPendingChangesGuardDirective
18273
18263
  ],
18274
18264
  exports: [
18275
18265
  QdDialogActionComponent,
@@ -24270,6 +24260,7 @@ class QdTableComponent {
24270
24260
  fillingWidthService = inject(QdTableFillingWidthService);
24271
24261
  breakpointService = inject(QdBreakpointService);
24272
24262
  resolverService = inject(QdTableResolverService);
24263
+ confirmationDialogService = inject(QdConfirmationDialogOpenerService);
24273
24264
  /**
24274
24265
  * Configuration of the table. The generic type specifies the column definition. <br />
24275
24266
  *
@@ -24472,6 +24463,17 @@ class QdTableComponent {
24472
24463
  if (action.highlightOnRevisit) {
24473
24464
  this.tableStoreService.setLastVisitedRow(resolveRowIdentifier(recentAction.rowData, recentAction.index, this.config.trackRowBy));
24474
24465
  }
24466
+ if (action.confirmationMessage) {
24467
+ this.confirmationDialogService
24468
+ .showConfirmDialog({ message: action.confirmationMessage }, this.testId + '-confirmation')
24469
+ .pipe(filter(confirmed => confirmed))
24470
+ .subscribe(() => {
24471
+ const result = action.handler(recentAction);
24472
+ if (action.refresh?.isEnabled)
24473
+ this.processSecondaryActionRefresh(result, action.refresh);
24474
+ });
24475
+ return;
24476
+ }
24475
24477
  const result = action.handler(recentAction);
24476
24478
  if (action.refresh?.isEnabled)
24477
24479
  this.processSecondaryActionRefresh(result, action.refresh);
@@ -26465,30 +26467,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
26465
26467
  }]
26466
26468
  }] });
26467
26469
 
26468
- class QdPageCancelConfirmationDialogComponent {
26469
- dialogRef = inject(DialogRef);
26470
- i18n;
26471
- handler;
26472
- ngOnInit() {
26473
- this.i18n =
26474
- this.dialogRef.config.data.cancel.confirmationMessage?.i18n ?? 'i18n.qd.page.cancel.confirmation.dialog.message';
26475
- this.handler = this.dialogRef.config.data.cancel.handler;
26476
- }
26477
- close() {
26478
- this.dialogRef.close(false);
26479
- }
26480
- confirm() {
26481
- this.dialogRef.close(true);
26482
- this.handler();
26483
- }
26484
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageCancelConfirmationDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26485
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: QdPageCancelConfirmationDialogComponent, isStandalone: false, selector: "qd-page-cancel-confirmation-dialog", ngImport: i0, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n data-test-id=\"page-cancel-confirmation-dialog-close\"\n >\n {{ \"i18n.qd.page.cancel.confirmation.dialog.close\" | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" data-test-id=\"page-cancel-confirmation-dialog-proceed\">\n {{ \"i18n.qd.page.cancel.confirmation.dialog.proceed\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n", styles: [""], 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" }] });
26486
- }
26487
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageCancelConfirmationDialogComponent, decorators: [{
26488
- type: Component,
26489
- args: [{ selector: 'qd-page-cancel-confirmation-dialog', standalone: false, template: "<qd-dialog>\n <qd-text-section>\n <qd-text-section-paragraph>\n {{ i18n | translate }}\n </qd-text-section-paragraph>\n </qd-text-section>\n\n <qd-dialog-action>\n <button\n qdButton\n qdButtonGhost\n color=\"secondary\"\n (click)=\"close()\"\n data-test-id=\"page-cancel-confirmation-dialog-close\"\n >\n {{ \"i18n.qd.page.cancel.confirmation.dialog.close\" | translate }}\n </button>\n\n <button qdButton color=\"error\" (click)=\"confirm()\" data-test-id=\"page-cancel-confirmation-dialog-proceed\">\n {{ \"i18n.qd.page.cancel.confirmation.dialog.proceed\" | translate }}\n </button>\n </qd-dialog-action>\n</qd-dialog>\n" }]
26490
- }] });
26491
-
26492
26470
  /**
26493
26471
  * The **QdPageControlPanel** is used to display information and functions relevant to a specific business case, which is related to a specific Object Model.
26494
26472
  *
@@ -27198,6 +27176,39 @@ class QdFormGroupManagerService {
27198
27176
  takeFormGroupsSnapshot() {
27199
27177
  this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
27200
27178
  }
27179
+ /**
27180
+ * Captures a deep-cloned snapshot of all current form values without mutating internal state.
27181
+ *
27182
+ * Used by framework orchestration code that needs to remember the "at click time" state of a form
27183
+ * so the baseline can later be committed to exactly that state — even if the user edits the form
27184
+ * while an async commit action is pending.
27185
+ *
27186
+ * Throws if any form value is not structured-cloneable (functions, symbols, DOM nodes).
27187
+ */
27188
+ captureFormValues() {
27189
+ const captured = new Map();
27190
+ this._formGroups.forEach((fg, key) => {
27191
+ try {
27192
+ captured.set(key, structuredClone(fg.getRawValue()));
27193
+ }
27194
+ catch (err) {
27195
+ throw new Error(`QdFormGroupManagerService: captureFormValues() failed for group "${key}". ` +
27196
+ `Form values must be structured-cloneable. Non-cloneable values like functions, ` +
27197
+ `symbols, or DOM nodes are not supported. Original error: ${String(err)}`);
27198
+ }
27199
+ });
27200
+ return captured;
27201
+ }
27202
+ /**
27203
+ * Replaces the internal baseline snapshot with the provided values.
27204
+ *
27205
+ * Intended to be paired with `captureFormValues()` so that the baseline can be advanced to the
27206
+ * values present when a commit action was initiated — independent of whether the user edited the
27207
+ * form in the meantime.
27208
+ */
27209
+ commitBaseline(values) {
27210
+ values.forEach((snapshot, key) => this._formGroupsSnapshot.set(key, snapshot));
27211
+ }
27201
27212
  restoreFormGroupsFromSnapshot() {
27202
27213
  this._formGroups.forEach((fg, key) => {
27203
27214
  const snapshot = this._formGroupsSnapshot.get(key);
@@ -27309,6 +27320,122 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
27309
27320
  type: Injectable
27310
27321
  }] });
27311
27322
 
27323
+ /**
27324
+ * Intercepts router navigation when unsaved form changes exist on a QdPage.
27325
+ *
27326
+ * Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
27327
+ * (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
27328
+ *
27329
+ * #### When navigation is intercepted
27330
+ *
27331
+ * - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
27332
+ * - The registered form groups report unsaved changes via `QdFormGroupManagerService.$hasValuesChanged()`.
27333
+ *
27334
+ * The navigation is cancelled and a confirmation dialog is shown. The user can either discard
27335
+ * changes and proceed or stay on the page.
27336
+ *
27337
+ * #### When navigation is allowed
27338
+ *
27339
+ * - No unsaved changes exist — the form is either pristine or the framework has advanced the
27340
+ * baseline after a successful commit action (Submit, Save, SaveDraft).
27341
+ * - The page switches to view mode via `deactivate()`.
27342
+ *
27343
+ * The service has no concept of "pending actions" or bypass windows. Framework actions advance the
27344
+ * baseline directly via `QdFormGroupManagerService.commitBaseline(...)`; cancel-discard flows reset
27345
+ * the baseline via `restoreFormGroupsFromSnapshot()`. Either way, the interceptor only observes the
27346
+ * resulting form state — there is no time window where a bypass is active.
27347
+ */
27348
+ class QdPageNavigationInterceptorService {
27349
+ router = inject(Router);
27350
+ formGroupManager = inject(QdFormGroupManagerService);
27351
+ confirmationDialog = inject(QdConfirmationDialogOpenerService);
27352
+ _destroy$ = new Subject();
27353
+ _deactivate$ = new Subject();
27354
+ /**
27355
+ * Guards the re-dispatched navigation issued after the user confirms discard. While the dialog
27356
+ * is open and while the confirmed target is being re-dispatched, the router events must not
27357
+ * re-enter the interception pipeline — otherwise the dialog would open recursively.
27358
+ */
27359
+ _bypassInterception = false;
27360
+ /**
27361
+ * URL of the navigation target accepted by the user in the most recent discard-confirm dialog.
27362
+ * Set by `navigateToConfirmedTarget()` and consumed once by `shouldIntercept()` when the
27363
+ * corresponding `NavigationStart` arrives.
27364
+ */
27365
+ _confirmedTargetUrl;
27366
+ _confirmationMessage;
27367
+ ngOnDestroy() {
27368
+ this._confirmedTargetUrl = undefined;
27369
+ this._destroy$.next();
27370
+ this._destroy$.complete();
27371
+ this._deactivate$.complete();
27372
+ }
27373
+ /**
27374
+ * Starts intercepting navigation events. Replaces any previously active listener.
27375
+ */
27376
+ activate(confirmationMessage) {
27377
+ this._confirmationMessage = confirmationMessage;
27378
+ this._deactivate$.next();
27379
+ this.listenForUnsavedNavigationAttempts();
27380
+ }
27381
+ /**
27382
+ * Stops intercepting and clears the pending confirmed-target bypass.
27383
+ */
27384
+ deactivate() {
27385
+ this._deactivate$.next();
27386
+ this._confirmedTargetUrl = undefined;
27387
+ }
27388
+ /**
27389
+ * Runs the given action while temporarily suppressing navigation interception.
27390
+ *
27391
+ * Used by framework commit-action orchestration to wrap synchronous handler invocations and
27392
+ * post-commit `onSuccess` callbacks: any router navigation that occurs during `action()` is
27393
+ * passed through without raising the unsaved-changes dialog. Restoration happens in a `finally`
27394
+ * block, so the flag is released even if `action()` throws.
27395
+ */
27396
+ executeWithBypass(action) {
27397
+ this._bypassInterception = true;
27398
+ try {
27399
+ action();
27400
+ }
27401
+ finally {
27402
+ this._bypassInterception = false;
27403
+ }
27404
+ }
27405
+ listenForUnsavedNavigationAttempts() {
27406
+ this.router.events
27407
+ .pipe(filter$1((event) => event instanceof NavigationStart), filter$1(() => !this._bypassInterception), filter$1(event => this.shouldIntercept(event)), concatMap$1(event => this.checkForPendingChanges(event)), filter$1(({ hasChanges }) => hasChanges), exhaustMap(({ event }) => this.cancelNavigationAndConfirm(event)), filter$1(({ confirmed }) => confirmed), takeUntil$1(this._deactivate$), takeUntil$1(this._destroy$))
27408
+ .subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
27409
+ }
27410
+ shouldIntercept(event) {
27411
+ if (this._confirmedTargetUrl !== undefined && this._confirmedTargetUrl === event.url) {
27412
+ this._confirmedTargetUrl = undefined;
27413
+ return false;
27414
+ }
27415
+ return true;
27416
+ }
27417
+ checkForPendingChanges(event) {
27418
+ return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
27419
+ }
27420
+ cancelNavigationAndConfirm(event) {
27421
+ this._bypassInterception = true;
27422
+ void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
27423
+ return this.confirmationDialog
27424
+ .showDiscardConfirmDialog(this._confirmationMessage, 'page-navigation-confirmation')
27425
+ .pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
27426
+ }
27427
+ navigateToConfirmedTarget(targetUrl) {
27428
+ this._bypassInterception = false;
27429
+ this._confirmedTargetUrl = targetUrl;
27430
+ void this.router.navigateByUrl(targetUrl);
27431
+ }
27432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
27433
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
27434
+ }
27435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
27436
+ type: Injectable
27437
+ }] });
27438
+
27312
27439
  class QdResolverTriggerService {
27313
27440
  route = inject(ActivatedRoute, { optional: true });
27314
27441
  shouldTriggerResolver(triggerOn) {
@@ -27358,8 +27485,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
27358
27485
  class QdPageObjectHeaderComponent {
27359
27486
  pageObjectResolver = inject(QD_PAGE_OBJECT_RESOLVER_TOKEN, { optional: true });
27360
27487
  formGroupManagerService = inject(QdFormGroupManagerService, { optional: true });
27488
+ navigationInterceptor = inject(QdPageNavigationInterceptorService);
27361
27489
  dialogComponent = inject(QdDialogComponent, { optional: true });
27362
27490
  dialog = inject(QdDialogService);
27491
+ confirmationDialogService = inject(QdConfirmationDialogOpenerService);
27363
27492
  contextService = inject(QdContextService);
27364
27493
  resolverTriggerService = inject(QdResolverTriggerService);
27365
27494
  pageStoreService = inject(QdPageStoreService);
@@ -27521,13 +27650,39 @@ class QdPageObjectHeaderComponent {
27521
27650
  });
27522
27651
  }
27523
27652
  save() {
27524
- const handleSuccess = () => {
27653
+ const saveAction = this.saveButton;
27654
+ const applySuccess = (captured) => {
27525
27655
  this.formGroupManagerService.cancelPendingAsyncValidation();
27526
27656
  this.pageStoreService.toggleViewonly(true);
27527
- this.formGroupManagerService.takeFormGroupsSnapshot();
27657
+ if (captured) {
27658
+ this.formGroupManagerService.commitBaseline(captured);
27659
+ }
27660
+ else {
27661
+ this.formGroupManagerService.takeFormGroupsSnapshot();
27662
+ }
27663
+ saveAction?.onSuccess?.();
27528
27664
  };
27529
- const result = this.saveButton?.handler?.(this.formGroupManagerService.getAllValues());
27530
- isObservable(result) ? result.pipe(take(1)).subscribe((ok) => ok && handleSuccess()) : handleSuccess();
27665
+ if (!saveAction?.handler) {
27666
+ this.navigationInterceptor.executeWithBypass(() => applySuccess(null));
27667
+ return;
27668
+ }
27669
+ const captured = this.formGroupManagerService.captureFormValues();
27670
+ let result;
27671
+ this.navigationInterceptor.executeWithBypass(() => {
27672
+ result = saveAction.handler(this.formGroupManagerService.getAllValues());
27673
+ });
27674
+ if (isObservable(result)) {
27675
+ result.pipe(take(1), takeUntil(this._destroyed$)).subscribe({
27676
+ next: ok => {
27677
+ if (ok)
27678
+ this.navigationInterceptor.executeWithBypass(() => applySuccess(captured));
27679
+ },
27680
+ error: err => console.error('QdPage save action observable errored — baseline not committed. Catch errors via catchError() in your handler.', err)
27681
+ });
27682
+ }
27683
+ else {
27684
+ this.navigationInterceptor.executeWithBypass(() => applySuccess(captured));
27685
+ }
27531
27686
  }
27532
27687
  changeContext(context, selection, event) {
27533
27688
  event.stopPropagation();
@@ -27555,7 +27710,7 @@ class QdPageObjectHeaderComponent {
27555
27710
  .subscribe();
27556
27711
  }
27557
27712
  initContexts() {
27558
- this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts?.map(({ selection, context }) => ({
27713
+ this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts.map(({ selection, context }) => ({
27559
27714
  label: context.label.i18n,
27560
27715
  value: Array.isArray(selection?.value)
27561
27716
  ? selection?.value.filter(item => item !== null && item !== undefined)
@@ -27566,7 +27721,7 @@ class QdPageObjectHeaderComponent {
27566
27721
  selection: selection?.value ?? []
27567
27722
  }))));
27568
27723
  this.contexts$
27569
- .pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts?.length ?? 0)))
27724
+ .pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts.length)))
27570
27725
  .subscribe();
27571
27726
  }
27572
27727
  updateCustomActions() {
@@ -27608,15 +27763,13 @@ class QdPageObjectHeaderComponent {
27608
27763
  return { actions };
27609
27764
  }
27610
27765
  openCancelDialog() {
27611
- this.dialog
27612
- .open(QdPageCancelConfirmationDialogComponent, {
27613
- title: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.title' },
27614
- dialogSize: QdDialogSize.Small,
27615
- data: this.config.pageTypeConfig
27616
- })
27617
- .closed.pipe(takeUntil(this._destroyed$))
27618
- .subscribe(confirm => {
27619
- if (confirm) {
27766
+ const cancelConfig = this.config.pageTypeConfig?.cancel;
27767
+ this.confirmationDialogService
27768
+ .showDiscardConfirmDialog(cancelConfig?.confirmationMessage, this.testId + '-cancel-confirmation')
27769
+ .pipe(takeUntil(this._destroyed$))
27770
+ .subscribe(confirmed => {
27771
+ if (confirmed) {
27772
+ cancelConfig?.handler?.();
27620
27773
  this.formGroupManagerService.restoreFormGroupsFromSnapshot();
27621
27774
  this.pageStoreService.toggleViewonly(true);
27622
27775
  }
@@ -28587,126 +28740,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
28587
28740
  type: Output
28588
28741
  }] } });
28589
28742
 
28590
- /**
28591
- * Intercepts router navigation when unsaved form changes exist on a QdPage.
28592
- *
28593
- * Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
28594
- * (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
28595
- *
28596
- * #### When navigation is intercepted
28597
- *
28598
- * - The user has unsaved form changes (tracked via `QdFormGroupManagerService`).
28599
- * - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
28600
- *
28601
- * The current navigation is cancelled, and a confirmation dialog is shown. The user can either
28602
- * discard changes and proceed or cancel and stay on the page.
28603
- *
28604
- * #### When navigation is allowed
28605
- *
28606
- * - No unsaved changes exist — navigation proceeds silently.
28607
- * - A framework action (Submit, SaveDraft) wraps its handler via `executeWithBypass()`.
28608
- * - A confirmed discard sets `allowNextNavigation()` before the cancel handler navigates.
28609
- * - The page switches to view mode via `deactivate()`.
28610
- *
28611
- * Custom actions defined by the application are not bypassed. If a custom action navigates away
28612
- * while unsaved changes exist, the confirmation dialog is shown.
28613
- */
28614
- class QdPageNavigationInterceptorService {
28615
- router = inject(Router);
28616
- formGroupManager = inject(QdFormGroupManagerService);
28617
- confirmationDialog = inject(QdConfirmationDialogOpenerService);
28618
- _destroy$ = new Subject();
28619
- _deactivate$ = new Subject();
28620
- _bypassInterception = false;
28621
- _allowedTargetUrls = new Set();
28622
- _confirmationMessage;
28623
- ngOnDestroy() {
28624
- this._allowedTargetUrls.clear();
28625
- this._destroy$.next();
28626
- this._destroy$.complete();
28627
- this._deactivate$.complete();
28628
- }
28629
- /**
28630
- * Starts intercepting navigation events. Replaces any previously active listener.
28631
- */
28632
- activate(confirmationMessage) {
28633
- this._confirmationMessage = confirmationMessage;
28634
- this._deactivate$.next();
28635
- this.listenForUnsavedNavigationAttempts();
28636
- }
28637
- /**
28638
- * Stops intercepting and clears all pending URL bypasses.
28639
- */
28640
- deactivate() {
28641
- this._deactivate$.next();
28642
- this._allowedTargetUrls.clear();
28643
- }
28644
- /**
28645
- * Whitelists the next navigation so it bypasses interception.
28646
- * Without a URL, any next navigation is bypassed (wildcard). With a URL, only that
28647
- * specific navigation is bypassed — non-matching navigations are still intercepted.
28648
- */
28649
- allowNextNavigation(targetUrl) {
28650
- this._allowedTargetUrls.add(targetUrl ?? '*');
28651
- }
28652
- /**
28653
- * Executes the callback with interception temporarily disabled.
28654
- * The callback must navigate synchronously — async navigation after the callback returns
28655
- * will not be bypassed. This works because Angular's router emits NavigationStart
28656
- * synchronously within the navigateByUrl() / navigate() call.
28657
- */
28658
- executeWithBypass(fn) {
28659
- this._bypassInterception = true;
28660
- try {
28661
- fn();
28662
- }
28663
- finally {
28664
- this._bypassInterception = false;
28665
- }
28666
- }
28667
- listenForUnsavedNavigationAttempts() {
28668
- this.router.events
28669
- .pipe(filter$1((event) => event instanceof NavigationStart), filter$1(() => !this._bypassInterception), filter$1(event => this.shouldIntercept(event)), concatMap$1(event => this.checkForPendingChanges(event)), filter$1(({ hasChanges }) => hasChanges), exhaustMap(({ event }) => this.cancelNavigationAndConfirm(event)), filter$1(({ confirmed }) => confirmed), takeUntil$1(this._deactivate$), takeUntil$1(this._destroy$))
28670
- .subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
28671
- }
28672
- shouldIntercept(event) {
28673
- if (this._allowedTargetUrls.has('*')) {
28674
- this._allowedTargetUrls.clear();
28675
- return false;
28676
- }
28677
- if (this._allowedTargetUrls.has(event.url)) {
28678
- this._allowedTargetUrls.delete(event.url);
28679
- return false;
28680
- }
28681
- return true;
28682
- }
28683
- checkForPendingChanges(event) {
28684
- return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
28685
- }
28686
- cancelNavigationAndConfirm(event) {
28687
- this._bypassInterception = true;
28688
- void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
28689
- return this.confirmationDialog
28690
- .showCancelConfirmation({ cancel: { confirmationMessage: this._confirmationMessage } })
28691
- .pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
28692
- }
28693
- navigateToConfirmedTarget(targetUrl) {
28694
- this._bypassInterception = false;
28695
- this._allowedTargetUrls.add(targetUrl);
28696
- void this.router.navigateByUrl(targetUrl);
28697
- }
28698
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
28699
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
28700
- }
28701
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
28702
- type: Injectable
28703
- }] });
28704
-
28705
28743
  class QdPageSubmitActionService {
28706
28744
  footerService = inject(QdPageFooterService);
28707
28745
  formGroupManagerService = inject(QdFormGroupManagerService);
28746
+ navigationInterceptor = inject(QdPageNavigationInterceptorService);
28708
28747
  _labelI18n = 'i18n.qd.page.footer.submit';
28709
- _submitHandler;
28748
+ _submitAction;
28710
28749
  _isVisible = true;
28711
28750
  _destroyed$ = new Subject();
28712
28751
  _cancelTrackFormValidity$ = new Subject();
@@ -28720,9 +28759,9 @@ class QdPageSubmitActionService {
28720
28759
  this._isVisible = isVisible;
28721
28760
  this.configureSubmitAction(pageTypeConfig.submit);
28722
28761
  }
28723
- configureSubmitAction(submitConfig) {
28724
- this._labelI18n = submitConfig.label?.i18n || 'i18n.qd.page.footer.submit';
28725
- this._submitHandler = this.buildSubmitHandler(submitConfig.handler);
28762
+ configureSubmitAction(action) {
28763
+ this._labelI18n = action.label?.i18n || 'i18n.qd.page.footer.submit';
28764
+ this._submitAction = action;
28726
28765
  this.registerSubmitAction();
28727
28766
  this.trackFormValidity();
28728
28767
  }
@@ -28732,7 +28771,7 @@ class QdPageSubmitActionService {
28732
28771
  key: 'submit',
28733
28772
  action: {
28734
28773
  titleI18n: this._labelI18n,
28735
- handler: this._submitHandler,
28774
+ handler: () => this.executeSubmit(),
28736
28775
  isDisabled: true,
28737
28776
  isVisible: this._isVisible,
28738
28777
  type: QdFooterActionType.Primary
@@ -28740,12 +28779,33 @@ class QdPageSubmitActionService {
28740
28779
  }
28741
28780
  ]);
28742
28781
  }
28743
- buildSubmitHandler(originalHandler) {
28744
- return (...args) => {
28745
- if (!this.formGroupManagerService.hasFormGroups())
28746
- return originalHandler(...args);
28747
- return originalHandler(this.formGroupManagerService.getAllValues());
28748
- };
28782
+ executeSubmit() {
28783
+ const action = this._submitAction;
28784
+ if (!action?.handler)
28785
+ return;
28786
+ const values = this.formGroupManagerService.hasFormGroups()
28787
+ ? this.formGroupManagerService.getAllValues()
28788
+ : undefined;
28789
+ const captured = this.formGroupManagerService.captureFormValues();
28790
+ let result;
28791
+ this.navigationInterceptor.executeWithBypass(() => {
28792
+ result = action.handler(values);
28793
+ });
28794
+ if (isObservable(result)) {
28795
+ result.pipe(take(1), takeUntil(this._destroyed$)).subscribe({
28796
+ next: ok => {
28797
+ if (!ok)
28798
+ return;
28799
+ this.formGroupManagerService.commitBaseline(captured);
28800
+ this.navigationInterceptor.executeWithBypass(() => action.onSuccess?.());
28801
+ },
28802
+ error: err => console.error('QdPage commit action observable errored — baseline not committed. Catch errors via catchError() in your handler.', err)
28803
+ });
28804
+ }
28805
+ else {
28806
+ this.formGroupManagerService.commitBaseline(captured);
28807
+ this.navigationInterceptor.executeWithBypass(() => action.onSuccess?.());
28808
+ }
28749
28809
  }
28750
28810
  trackFormValidity() {
28751
28811
  this._cancelTrackFormValidity$.next();
@@ -28760,7 +28820,7 @@ class QdPageSubmitActionService {
28760
28820
  key: 'submit',
28761
28821
  action: {
28762
28822
  titleI18n: this._labelI18n,
28763
- handler: this._submitHandler,
28823
+ handler: () => this.executeSubmit(),
28764
28824
  isDisabled: !isValid,
28765
28825
  isVisible: this._isVisible,
28766
28826
  type: QdFooterActionType.Primary
@@ -28884,7 +28944,15 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
28884
28944
  * handler: () => handleCancel()
28885
28945
  * },
28886
28946
  * save: {
28887
- * handler: (formValues) => handleSave(formValues)
28947
+ * handler: () => saveApi.save(form.value).pipe(
28948
+ * tap(result => notifications.success('Saved')),
28949
+ * map(() => true),
28950
+ * catchError(err => {
28951
+ * notifications.showError(err);
28952
+ * return of(false);
28953
+ * })
28954
+ * ),
28955
+ * onSuccess: () => router.navigate(['/overview'])
28888
28956
  * }
28889
28957
  * }
28890
28958
  * };
@@ -29002,7 +29070,8 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
29002
29070
  * pageType: 'create',
29003
29071
  * pageTypeConfig: {
29004
29072
  * submit: {
29005
- * handler: (formValues) => handleSubmit(formValues)
29073
+ * handler: (formValues) => createApi.create(formValues).pipe(map(() => true)),
29074
+ * onSuccess: () => router.navigate(['/items'])
29006
29075
  * }
29007
29076
  * }
29008
29077
  * };
@@ -29095,7 +29164,14 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
29095
29164
  * handler: () => handleCancel()
29096
29165
  * },
29097
29166
  * save: {
29098
- * handler: (formValues) => handleSave(formValues)
29167
+ * handler: (formValues) => saveApi.save(formValues).pipe(
29168
+ * map(() => true),
29169
+ * catchError(err => {
29170
+ * notifications.showError(err);
29171
+ * return of(false);
29172
+ * })
29173
+ * ),
29174
+ * onSuccess: () => router.navigate(['/overview'])
29099
29175
  * }
29100
29176
  * }
29101
29177
  * };
@@ -29164,6 +29240,7 @@ class QdPageComponent {
29164
29240
  bottomOffset$ = inject(QD_SAFE_BOTTOM_OFFSET, { optional: true });
29165
29241
  dialogRef = inject(DialogRef, { optional: true });
29166
29242
  navigationInterceptor = inject(QdPageNavigationInterceptorService);
29243
+ confirmationDialogService = inject(QdConfirmationDialogOpenerService);
29167
29244
  /**
29168
29245
  * This property defines the configuration for the QdPage component, including the page type,
29169
29246
  * title, and specific configurations for each type of page.
@@ -29264,11 +29341,14 @@ class QdPageComponent {
29264
29341
  const action = pageTypeConfig[actionKey];
29265
29342
  if (!action)
29266
29343
  continue;
29344
+ const handler = actionKey === 'cancel'
29345
+ ? () => action.handler()
29346
+ : this.generateCommitActionHandler(action);
29267
29347
  actions.push({
29268
29348
  actionKey,
29269
29349
  partialAction: {
29270
29350
  ...(action?.label?.i18n ? { titleI18n: action.label.i18n } : {}),
29271
- handler: this.generateFooterActionHandler(action?.handler)
29351
+ handler
29272
29352
  }
29273
29353
  });
29274
29354
  }
@@ -29285,11 +29365,7 @@ class QdPageComponent {
29285
29365
  partialAction: {
29286
29366
  handler: hasChanged
29287
29367
  ? () => this.setupCancelConfirmation()
29288
- : () => {
29289
- this.navigationInterceptor.executeWithBypass(() => {
29290
- this.config?.pageTypeConfig?.cancel?.handler();
29291
- });
29292
- }
29368
+ : () => this.config?.pageTypeConfig?.cancel?.handler()
29293
29369
  }
29294
29370
  }
29295
29371
  ]);
@@ -29301,9 +29377,9 @@ class QdPageComponent {
29301
29377
  {
29302
29378
  key: 'saveDraft',
29303
29379
  action: {
29304
- titleI18n: pageTypeConfig.saveDraft?.label?.i18n ?? 'i18n.qd.page.footer.saveDraft',
29380
+ titleI18n: pageTypeConfig.saveDraft.label?.i18n ?? 'i18n.qd.page.footer.saveDraft',
29305
29381
  type: QdFooterActionType.Secondary,
29306
- handler: this.generateFooterActionHandler(pageTypeConfig?.saveDraft?.handler),
29382
+ handler: this.generateCommitActionHandler(pageTypeConfig.saveDraft),
29307
29383
  isVisible: true,
29308
29384
  isDisabled: false
29309
29385
  }
@@ -29330,30 +29406,49 @@ class QdPageComponent {
29330
29406
  if (this._isInitialized)
29331
29407
  this.operationModeChanged.emit(mode);
29332
29408
  }
29333
- generateFooterActionHandler(handler) {
29409
+ generateCommitActionHandler(action) {
29334
29410
  return (...args) => {
29335
- if (!handler)
29411
+ if (!action?.handler)
29336
29412
  return;
29337
29413
  const values = this.formGroupManagerService.hasFormGroups() ? this.formGroupManagerService.getAllValues() : args;
29338
- this.navigationInterceptor.executeWithBypass(() => handler(values));
29414
+ const captured = this.formGroupManagerService.captureFormValues();
29415
+ let result;
29416
+ this.navigationInterceptor.executeWithBypass(() => {
29417
+ result = action.handler(values);
29418
+ });
29419
+ if (isObservable(result)) {
29420
+ result.pipe(take(1), takeUntil(this._destroyed$)).subscribe({
29421
+ next: ok => {
29422
+ if (!ok)
29423
+ return;
29424
+ this.formGroupManagerService.commitBaseline(captured);
29425
+ this.navigationInterceptor.executeWithBypass(() => action.onSuccess?.());
29426
+ },
29427
+ error: err => console.error('QdPage commit action observable errored — baseline not committed. Catch errors via catchError() in your handler.', err)
29428
+ });
29429
+ }
29430
+ else {
29431
+ this.formGroupManagerService.commitBaseline(captured);
29432
+ this.navigationInterceptor.executeWithBypass(() => action.onSuccess?.());
29433
+ }
29339
29434
  };
29340
29435
  }
29341
29436
  setupCancelConfirmation() {
29342
- this.dialog
29343
- .open(QdPageCancelConfirmationDialogComponent, {
29344
- title: { i18n: 'i18n.qd.page.cancel.confirmation.dialog.title' },
29345
- dialogSize: QdDialogSize.Small,
29346
- data: this.config.pageTypeConfig
29347
- })
29348
- .closed.pipe(filter$1(result => !!result), takeUntil(this._destroyed$))
29349
- .subscribe(() => this.navigationInterceptor.allowNextNavigation());
29437
+ const cancelConfig = this.config.pageTypeConfig?.cancel;
29438
+ this.confirmationDialogService
29439
+ .showDiscardConfirmDialog(cancelConfig?.confirmationMessage, this.testId + '-cancel-confirmation')
29440
+ .pipe(filter$1(result => result), takeUntil(this._destroyed$))
29441
+ .subscribe(() => {
29442
+ this.formGroupManagerService.restoreFormGroupsFromSnapshot();
29443
+ cancelConfig?.handler?.();
29444
+ });
29350
29445
  }
29351
29446
  initSubmitValidation() {
29352
29447
  this._cancelSubmitValidation$.next();
29353
29448
  this.formGroupManagerService
29354
29449
  .$areFormGroupsValid()
29355
29450
  .pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
29356
- const submitDisabledInfoText = this.config.pageType === 'inspect' ? this.config.pageTypeConfig?.submit?.disabledInfo : undefined;
29451
+ const submitDisabledInfoText = this.config.pageTypeConfig?.submit?.disabledInfo;
29357
29452
  this.footerService.updateActions([
29358
29453
  {
29359
29454
  actionKey: 'submit',
@@ -29545,7 +29640,6 @@ class QdPageModule {
29545
29640
  QdConnectFormStateToPageDirective,
29546
29641
  QdPageFooterCustomContentDirective,
29547
29642
  QdContextSelectDialogComponent,
29548
- QdPageCancelConfirmationDialogComponent,
29549
29643
  QdPageInfoBannerComponent], imports: [CommonModule,
29550
29644
  TranslateModule,
29551
29645
  QdButtonModule,
@@ -29630,7 +29724,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
29630
29724
  QdConnectFormStateToPageDirective,
29631
29725
  QdPageFooterCustomContentDirective,
29632
29726
  QdContextSelectDialogComponent,
29633
- QdPageCancelConfirmationDialogComponent,
29634
29727
  QdPageInfoBannerComponent
29635
29728
  ],
29636
29729
  exports: [
@@ -32486,6 +32579,7 @@ class QdQuickEditComponent {
32486
32579
  eventBrokerService = inject(QdEventBrokerService, { optional: true });
32487
32580
  changeDetectorRef = inject(ChangeDetectorRef);
32488
32581
  controlContainer = inject(ControlContainer, { optional: true, self: true });
32582
+ confirmationDialogService = inject(QdConfirmationDialogOpenerService);
32489
32583
  /**
32490
32584
  * Configuration of the QuickEdit. The generic type specifies the column definition. <br />
32491
32585
  *
@@ -32634,6 +32728,16 @@ class QdQuickEditComponent {
32634
32728
  window.setTimeout(() => this.focusFirstControl());
32635
32729
  }
32636
32730
  removeRow(index) {
32731
+ if (this.config.removeConfirmationMessage) {
32732
+ this.confirmationDialogService
32733
+ .showConfirmDialog({ message: this.config.removeConfirmationMessage }, (this.testId ?? 'quick-edit') + '-remove-confirmation')
32734
+ .pipe(filter$1(confirmed => confirmed))
32735
+ .subscribe(() => this.executeRemoveRow(index));
32736
+ return;
32737
+ }
32738
+ this.executeRemoveRow(index);
32739
+ }
32740
+ executeRemoveRow(index) {
32637
32741
  this.redrawOnNextChange();
32638
32742
  if (this.controlContainer) {
32639
32743
  this.controlContainer.control.removeAt(index);