@quadrel-enterprise-ui/framework 20.9.1 → 20.10.0-beta.139.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.
@@ -9211,7 +9211,7 @@ class QdDateAdapter {
9211
9211
  static parseToDate(formatted) {
9212
9212
  if (!formatted)
9213
9213
  return new Date();
9214
- return moment(formatted, 'l', 'de').toDate();
9214
+ return moment(formatted, 'DD.MM.YYYY').toDate();
9215
9215
  }
9216
9216
  /**
9217
9217
  * Converts a localized Swiss date and time string into a JavaScript Date object.
@@ -9231,9 +9231,9 @@ class QdDateAdapter {
9231
9231
  return new Date(new Date().setHours(hours, minutes, 0, 0));
9232
9232
  }
9233
9233
  else if (date && !time) {
9234
- return moment(date, 'l', 'de').toDate();
9234
+ return moment(date, 'DD.MM.YYYY').toDate();
9235
9235
  }
9236
- let format = 'l ';
9236
+ let format = 'DD.MM.YYYY ';
9237
9237
  switch (time.length) {
9238
9238
  case 8:
9239
9239
  format = format + 'HH:mm:ss';
@@ -9242,14 +9242,14 @@ class QdDateAdapter {
9242
9242
  default:
9243
9243
  format = format + 'HH:mm';
9244
9244
  }
9245
- return moment(`${date} ${time}`, format, 'de').toDate();
9245
+ return moment(`${date} ${time}`, format).toDate();
9246
9246
  }
9247
9247
  /**
9248
9248
  * Converts a localized date string into a Moment.js object.
9249
9249
  * If the input is null or undefined, it returns undefined.
9250
9250
  */
9251
9251
  static parseToMoment(formatted) {
9252
- return formatted ? moment(formatted, 'l', 'de').locale('de') : undefined;
9252
+ return formatted ? moment(formatted, 'DD.MM.YYYY').locale('de') : undefined;
9253
9253
  }
9254
9254
  /**
9255
9255
  * Formats a Moment.js object into a localized date string.
@@ -9258,7 +9258,7 @@ class QdDateAdapter {
9258
9258
  static formatToLocalizedDateString(momentObj) {
9259
9259
  if (!momentObj)
9260
9260
  return '';
9261
- return momentObj.locale('de').format('L');
9261
+ return momentObj.format('DD.MM.YYYY');
9262
9262
  }
9263
9263
  /**
9264
9264
  * Formats a JavaScript Date object into a localized date string.
@@ -9283,7 +9283,7 @@ class QdDateAdapter {
9283
9283
  * Returns true if the date is valid, otherwise false.
9284
9284
  */
9285
9285
  static isLocalizedDateStringValid(formatted) {
9286
- return moment(formatted, 'L', 'de', true).isValid();
9286
+ return moment(formatted, 'DD.MM.YYYY', true).isValid();
9287
9287
  }
9288
9288
  /**
9289
9289
  * Validates if a date-time string matches the Swiss `DD.MM.YYYY HH:mm:ss` format.
@@ -9339,9 +9339,8 @@ class QdCalendarComponent {
9339
9339
  this.setWeekdays();
9340
9340
  this.todayDate = moment();
9341
9341
  this.months = moment.months();
9342
- this.years = this.getYears((QdDateAdapter.parseToMoment(this.selectedDate)?.isValid()
9343
- ? moment(this.selectedDate, 'L', this.language)
9344
- : this.todayDate).year());
9342
+ const parsedSelected = QdDateAdapter.parseToMoment(this.selectedDate);
9343
+ this.years = this.getYears((parsedSelected?.isValid() ? parsedSelected : this.todayDate).year());
9345
9344
  this.setCurrentDate();
9346
9345
  this.generateCalendar(this.currentDate);
9347
9346
  }
@@ -9431,9 +9430,8 @@ class QdCalendarComponent {
9431
9430
  return this.todayDate.isSame(momentObj, 'day');
9432
9431
  }
9433
9432
  setCurrentDate() {
9434
- const currentDate = (this.currentDate = QdDateAdapter.parseToMoment(this.selectedDate)?.isValid()
9435
- ? moment(this.selectedDate, 'L', 'de')
9436
- : this.todayDate);
9433
+ const parsedDate = QdDateAdapter.parseToMoment(this.selectedDate);
9434
+ const currentDate = (this.currentDate = parsedDate?.isValid() ? parsedDate : this.todayDate);
9437
9435
  this.currentDate = currentDate;
9438
9436
  this.selectedDate = QdDateAdapter.formatToLocalizedDateString(currentDate);
9439
9437
  }
@@ -10995,6 +10993,7 @@ class QdInputComponent {
10995
10993
  hasAutofocus;
10996
10994
  hasOptions = false;
10997
10995
  _value = { value: '' };
10996
+ _displayValue = '';
10998
10997
  control;
10999
10998
  _optionsResolver;
11000
10999
  _subs = new Subscription();
@@ -11064,6 +11063,7 @@ class QdInputComponent {
11064
11063
  }
11065
11064
  writeValue(value) {
11066
11065
  this._value = getValueWithUnit(value, this.config);
11066
+ this._displayValue = String(this._value.value ?? '');
11067
11067
  }
11068
11068
  setDisabledState(disabled) {
11069
11069
  this.disabled = disabled;
@@ -11194,7 +11194,7 @@ class QdInputComponent {
11194
11194
  provide: QD_FOCUSABLE_TOKEN,
11195
11195
  useExisting: QdInputComponent
11196
11196
  }
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) : _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" }] });
11198
11198
  }
11199
11199
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdInputComponent, decorators: [{
11200
11200
  type: Component,
@@ -11212,7 +11212,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
11212
11212
  provide: QD_FOCUSABLE_TOKEN,
11213
11213
  useExisting: QdInputComponent
11214
11214
  }
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) : _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"] }]
11216
11216
  }], propDecorators: { formControlName: [{
11217
11217
  type: Input
11218
11218
  }], value: [{
@@ -11434,8 +11434,6 @@ class QdDatepickerComponent {
11434
11434
  this.displayedDateTime = date ? QdDateAdapter.formatToDateTimeLocaleString(date, this.config?.enableSeconds) : '';
11435
11435
  this.displayedTime = date ? QdDateAdapter.formatToTimeLocaleString(date, this.config?.enableSeconds) : '';
11436
11436
  this.changeDetectorRef.detectChanges();
11437
- this.valueChange.emit(date);
11438
- this._onChange(date);
11439
11437
  }
11440
11438
  setDisabledState(disabled) {
11441
11439
  this.disabled = disabled;
@@ -12873,7 +12871,7 @@ class QdValidators {
12873
12871
  return ({ value }) => {
12874
12872
  if (!value)
12875
12873
  return null;
12876
- const localizedFormat = moment().locale('de').format('L');
12874
+ const localizedFormat = moment().format('DD.MM.YYYY');
12877
12875
  const formattedErrorKey = { date: { key: errorKey, params: { format: localizedFormat } } };
12878
12876
  if (value === 'Invalid Date')
12879
12877
  return formattedErrorKey;
@@ -27064,6 +27062,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
27064
27062
  type: Input
27065
27063
  }] } });
27066
27064
 
27065
+ class QdPageCommitActionExecutor {
27066
+ static execute(action, values, context) {
27067
+ const { formGroupManager, navigationInterceptor, destroyed$, onAfterSnapshot } = context;
27068
+ const captured = formGroupManager.captureFormValues();
27069
+ let result$;
27070
+ navigationInterceptor.executeWithBypass(() => {
27071
+ const handlerResult = action.handler(values);
27072
+ if (isObservable(handlerResult))
27073
+ result$ = handlerResult;
27074
+ });
27075
+ const applySuccess = () => {
27076
+ formGroupManager.setFormGroupsSnapshot(captured);
27077
+ navigationInterceptor.executeWithBypass(() => {
27078
+ onAfterSnapshot?.();
27079
+ try {
27080
+ action.onSuccess?.();
27081
+ }
27082
+ catch (err) {
27083
+ console.error('QD-UI | QdPage - onSuccess callback threw after form was marked as saved.', err);
27084
+ }
27085
+ });
27086
+ };
27087
+ if (result$) {
27088
+ result$.pipe(take(1), takeUntil(destroyed$)).subscribe({
27089
+ next: ok => {
27090
+ if (ok)
27091
+ applySuccess();
27092
+ },
27093
+ error: err => console.error('QD-UI | QdPage - Commit action observable errored — form was not marked as saved. Catch errors via catchError() in your handler.', err)
27094
+ });
27095
+ }
27096
+ else {
27097
+ applySuccess();
27098
+ }
27099
+ }
27100
+ }
27101
+
27067
27102
  /**
27068
27103
  * The QdFormGroupManagerService provides centralized registration, snapshotting, and value tracking
27069
27104
  * for multiple Angular `FormGroup` instances. It supports efficient state restoration, including dynamic
@@ -27178,6 +27213,39 @@ class QdFormGroupManagerService {
27178
27213
  takeFormGroupsSnapshot() {
27179
27214
  this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
27180
27215
  }
27216
+ /**
27217
+ * Captures a deep-cloned snapshot of all current form values without mutating internal state.
27218
+ *
27219
+ * Used by framework orchestration code that needs to remember the "at click time" state of a form
27220
+ * so the snapshot can later be set to exactly those values — even if the user edits the form
27221
+ * while an async commit action is pending.
27222
+ *
27223
+ * Throws if any form value is not structured-cloneable (functions, symbols, DOM nodes).
27224
+ */
27225
+ captureFormValues() {
27226
+ const captured = new Map();
27227
+ this._formGroups.forEach((fg, key) => {
27228
+ try {
27229
+ captured.set(key, structuredClone(fg.getRawValue()));
27230
+ }
27231
+ catch (err) {
27232
+ throw new Error(`QD-UI | QdFormGroupManager - captureFormValues() failed for group "${key}". ` +
27233
+ `Form values must be structured-cloneable. Non-cloneable values like functions, ` +
27234
+ `symbols, or DOM nodes are not supported. Original error: ${String(err)}`);
27235
+ }
27236
+ });
27237
+ return captured;
27238
+ }
27239
+ /**
27240
+ * Replaces the internal saved snapshot with the provided values.
27241
+ *
27242
+ * Intended to be paired with `captureFormValues()` so that the snapshot can be set to the
27243
+ * values present when a commit action was initiated — independent of whether the user edited
27244
+ * the form in the meantime.
27245
+ */
27246
+ setFormGroupsSnapshot(values) {
27247
+ values.forEach((snapshot, key) => this._formGroupsSnapshot.set(key, snapshot));
27248
+ }
27181
27249
  restoreFormGroupsFromSnapshot() {
27182
27250
  this._formGroups.forEach((fg, key) => {
27183
27251
  const snapshot = this._formGroupsSnapshot.get(key);
@@ -27187,20 +27255,6 @@ class QdFormGroupManagerService {
27187
27255
  });
27188
27256
  this.cancelPendingAsyncValidation();
27189
27257
  }
27190
- restoreFormGroup(fg, snapshot) {
27191
- Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => {
27192
- const newValue = snapshot[ctrlKey];
27193
- if (ctrl instanceof FormArray && Array.isArray(newValue)) {
27194
- this.resetFormArrayToValues(ctrl, newValue);
27195
- }
27196
- else if (ctrl instanceof FormGroup && newValue && typeof newValue === 'object') {
27197
- this.restoreFormGroup(ctrl, newValue);
27198
- }
27199
- else {
27200
- ctrl.reset(newValue);
27201
- }
27202
- });
27203
- }
27204
27258
  /**
27205
27259
  * Cancels any in-flight async validators on all registered form groups.
27206
27260
  *
@@ -27224,6 +27278,20 @@ class QdFormGroupManagerService {
27224
27278
  }
27225
27279
  });
27226
27280
  }
27281
+ restoreFormGroup(fg, snapshot) {
27282
+ Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => {
27283
+ const newValue = snapshot[ctrlKey];
27284
+ if (ctrl instanceof FormArray && Array.isArray(newValue)) {
27285
+ this.resetFormArrayToValues(ctrl, newValue);
27286
+ }
27287
+ else if (ctrl instanceof FormGroup && newValue && typeof newValue === 'object') {
27288
+ this.restoreFormGroup(ctrl, newValue);
27289
+ }
27290
+ else {
27291
+ ctrl.reset(newValue);
27292
+ }
27293
+ });
27294
+ }
27227
27295
  collectPendingControls(control) {
27228
27296
  const result = [];
27229
27297
  if (control instanceof FormGroup) {
@@ -27289,6 +27357,122 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
27289
27357
  type: Injectable
27290
27358
  }] });
27291
27359
 
27360
+ /**
27361
+ * Intercepts router navigation when unsaved form changes exist on a QdPage.
27362
+ *
27363
+ * Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
27364
+ * (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
27365
+ *
27366
+ * #### When navigation is intercepted
27367
+ *
27368
+ * - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
27369
+ * - The registered form groups report unsaved changes via `QdFormGroupManagerService.$hasValuesChanged()`.
27370
+ *
27371
+ * The navigation is cancelled and a confirmation dialog is shown. The user can either discard
27372
+ * changes and proceed or stay on the page.
27373
+ *
27374
+ * #### When navigation is allowed
27375
+ *
27376
+ * - No unsaved changes exist — the form is either pristine or the framework has updated the
27377
+ * saved snapshot after a successful commit action (Submit, Save, SaveDraft).
27378
+ * - The page switches to view mode via `deactivate()`.
27379
+ *
27380
+ * The service has no concept of "pending actions" or bypass windows. Framework actions update the
27381
+ * saved snapshot directly via `QdFormGroupManagerService.setFormGroupsSnapshot(...)`; cancel-discard
27382
+ * flows reset the form to the saved snapshot via `restoreFormGroupsFromSnapshot()`. Either way, the
27383
+ * interceptor only observes the resulting form state — there is no time window where a bypass is active.
27384
+ */
27385
+ class QdPageNavigationInterceptorService {
27386
+ router = inject(Router);
27387
+ formGroupManager = inject(QdFormGroupManagerService);
27388
+ confirmationDialog = inject(QdConfirmationDialogOpenerService);
27389
+ _destroy$ = new Subject();
27390
+ _deactivate$ = new Subject();
27391
+ /**
27392
+ * Guards the re-dispatched navigation issued after the user confirms discard. While the dialog
27393
+ * is open and while the confirmed target is being re-dispatched, the router events must not
27394
+ * re-enter the interception pipeline — otherwise the dialog would open recursively.
27395
+ */
27396
+ _bypassInterception = false;
27397
+ /**
27398
+ * URL of the navigation target accepted by the user in the most recent discard-confirm dialog.
27399
+ * Set by `navigateToConfirmedTarget()` and consumed once by `shouldIntercept()` when the
27400
+ * corresponding `NavigationStart` arrives.
27401
+ */
27402
+ _confirmedTargetUrl;
27403
+ _confirmationMessage;
27404
+ ngOnDestroy() {
27405
+ this._confirmedTargetUrl = undefined;
27406
+ this._destroy$.next();
27407
+ this._destroy$.complete();
27408
+ this._deactivate$.complete();
27409
+ }
27410
+ /**
27411
+ * Starts intercepting navigation events. Replaces any previously active listener.
27412
+ */
27413
+ activate(confirmationMessage) {
27414
+ this._confirmationMessage = confirmationMessage;
27415
+ this._deactivate$.next();
27416
+ this.listenForUnsavedNavigationAttempts();
27417
+ }
27418
+ /**
27419
+ * Stops intercepting and clears the pending confirmed-target bypass.
27420
+ */
27421
+ deactivate() {
27422
+ this._deactivate$.next();
27423
+ this._confirmedTargetUrl = undefined;
27424
+ }
27425
+ /**
27426
+ * Runs the given action while temporarily suppressing navigation interception.
27427
+ *
27428
+ * Used by framework commit-action orchestration to wrap synchronous handler invocations and
27429
+ * post-commit `onSuccess` callbacks: any router navigation that occurs during `action()` is
27430
+ * passed through without raising the unsaved-changes dialog. Restoration happens in a `finally`
27431
+ * block, so the flag is released even if `action()` throws.
27432
+ */
27433
+ executeWithBypass(action) {
27434
+ this._bypassInterception = true;
27435
+ try {
27436
+ action();
27437
+ }
27438
+ finally {
27439
+ this._bypassInterception = false;
27440
+ }
27441
+ }
27442
+ listenForUnsavedNavigationAttempts() {
27443
+ this.router.events
27444
+ .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$))
27445
+ .subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
27446
+ }
27447
+ shouldIntercept(event) {
27448
+ if (this._confirmedTargetUrl !== undefined && this._confirmedTargetUrl === event.url) {
27449
+ this._confirmedTargetUrl = undefined;
27450
+ return false;
27451
+ }
27452
+ return true;
27453
+ }
27454
+ checkForPendingChanges(event) {
27455
+ return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
27456
+ }
27457
+ cancelNavigationAndConfirm(event) {
27458
+ this._bypassInterception = true;
27459
+ void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
27460
+ return this.confirmationDialog
27461
+ .showDiscardConfirmDialog(this._confirmationMessage, 'page-navigation-confirmation')
27462
+ .pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
27463
+ }
27464
+ navigateToConfirmedTarget(targetUrl) {
27465
+ this._bypassInterception = false;
27466
+ this._confirmedTargetUrl = targetUrl;
27467
+ void this.router.navigateByUrl(targetUrl);
27468
+ }
27469
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
27470
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
27471
+ }
27472
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
27473
+ type: Injectable
27474
+ }] });
27475
+
27292
27476
  class QdResolverTriggerService {
27293
27477
  route = inject(ActivatedRoute, { optional: true });
27294
27478
  shouldTriggerResolver(triggerOn) {
@@ -27338,6 +27522,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
27338
27522
  class QdPageObjectHeaderComponent {
27339
27523
  pageObjectResolver = inject(QD_PAGE_OBJECT_RESOLVER_TOKEN, { optional: true });
27340
27524
  formGroupManagerService = inject(QdFormGroupManagerService, { optional: true });
27525
+ navigationInterceptor = inject(QdPageNavigationInterceptorService);
27341
27526
  dialogComponent = inject(QdDialogComponent, { optional: true });
27342
27527
  dialog = inject(QdDialogService);
27343
27528
  confirmationDialogService = inject(QdConfirmationDialogOpenerService);
@@ -27502,13 +27687,18 @@ class QdPageObjectHeaderComponent {
27502
27687
  });
27503
27688
  }
27504
27689
  save() {
27505
- const handleSuccess = () => {
27506
- this.formGroupManagerService.cancelPendingAsyncValidation();
27507
- this.pageStoreService.toggleViewonly(true);
27508
- this.formGroupManagerService.takeFormGroupsSnapshot();
27509
- };
27510
- const result = this.saveButton?.handler?.(this.formGroupManagerService.getAllValues());
27511
- isObservable(result) ? result.pipe(take(1)).subscribe((ok) => ok && handleSuccess()) : handleSuccess();
27690
+ const saveAction = this.saveButton;
27691
+ if (!saveAction)
27692
+ return;
27693
+ QdPageCommitActionExecutor.execute(saveAction, this.formGroupManagerService.getAllValues(), {
27694
+ formGroupManager: this.formGroupManagerService,
27695
+ navigationInterceptor: this.navigationInterceptor,
27696
+ destroyed$: this._destroyed$,
27697
+ onAfterSnapshot: () => {
27698
+ this.formGroupManagerService.cancelPendingAsyncValidation();
27699
+ this.pageStoreService.toggleViewonly(true);
27700
+ }
27701
+ });
27512
27702
  }
27513
27703
  changeContext(context, selection, event) {
27514
27704
  event.stopPropagation();
@@ -27536,7 +27726,7 @@ class QdPageObjectHeaderComponent {
27536
27726
  .subscribe();
27537
27727
  }
27538
27728
  initContexts() {
27539
- this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts?.map(({ selection, context }) => ({
27729
+ this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts.map(({ selection, context }) => ({
27540
27730
  label: context.label.i18n,
27541
27731
  value: Array.isArray(selection?.value)
27542
27732
  ? selection?.value.filter(item => item !== null && item !== undefined)
@@ -27547,7 +27737,7 @@ class QdPageObjectHeaderComponent {
27547
27737
  selection: selection?.value ?? []
27548
27738
  }))));
27549
27739
  this.contexts$
27550
- .pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts?.length ?? 0)))
27740
+ .pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts.length)))
27551
27741
  .subscribe();
27552
27742
  }
27553
27743
  updateCustomActions() {
@@ -28566,126 +28756,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
28566
28756
  type: Output
28567
28757
  }] } });
28568
28758
 
28569
- /**
28570
- * Intercepts router navigation when unsaved form changes exist on a QdPage.
28571
- *
28572
- * Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
28573
- * (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
28574
- *
28575
- * #### When navigation is intercepted
28576
- *
28577
- * - The user has unsaved form changes (tracked via `QdFormGroupManagerService`).
28578
- * - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
28579
- *
28580
- * The current navigation is cancelled, and a confirmation dialog is shown. The user can either
28581
- * discard changes and proceed or cancel and stay on the page.
28582
- *
28583
- * #### When navigation is allowed
28584
- *
28585
- * - No unsaved changes exist — navigation proceeds silently.
28586
- * - A framework action (Submit, SaveDraft) wraps its handler via `executeWithBypass()`.
28587
- * - A confirmed discard sets `allowNextNavigation()` before the cancel handler navigates.
28588
- * - The page switches to view mode via `deactivate()`.
28589
- *
28590
- * Custom actions defined by the application are not bypassed. If a custom action navigates away
28591
- * while unsaved changes exist, the confirmation dialog is shown.
28592
- */
28593
- class QdPageNavigationInterceptorService {
28594
- router = inject(Router);
28595
- formGroupManager = inject(QdFormGroupManagerService);
28596
- confirmationDialog = inject(QdConfirmationDialogOpenerService);
28597
- _destroy$ = new Subject();
28598
- _deactivate$ = new Subject();
28599
- _bypassInterception = false;
28600
- _allowedTargetUrls = new Set();
28601
- _confirmationMessage;
28602
- ngOnDestroy() {
28603
- this._allowedTargetUrls.clear();
28604
- this._destroy$.next();
28605
- this._destroy$.complete();
28606
- this._deactivate$.complete();
28607
- }
28608
- /**
28609
- * Starts intercepting navigation events. Replaces any previously active listener.
28610
- */
28611
- activate(confirmationMessage) {
28612
- this._confirmationMessage = confirmationMessage;
28613
- this._deactivate$.next();
28614
- this.listenForUnsavedNavigationAttempts();
28615
- }
28616
- /**
28617
- * Stops intercepting and clears all pending URL bypasses.
28618
- */
28619
- deactivate() {
28620
- this._deactivate$.next();
28621
- this._allowedTargetUrls.clear();
28622
- }
28623
- /**
28624
- * Whitelists the next navigation so it bypasses interception.
28625
- * Without a URL, any next navigation is bypassed (wildcard). With a URL, only that
28626
- * specific navigation is bypassed — non-matching navigations are still intercepted.
28627
- */
28628
- allowNextNavigation(targetUrl) {
28629
- this._allowedTargetUrls.add(targetUrl ?? '*');
28630
- }
28631
- /**
28632
- * Executes the callback with interception temporarily disabled.
28633
- * The callback must navigate synchronously — async navigation after the callback returns
28634
- * will not be bypassed. This works because Angular's router emits NavigationStart
28635
- * synchronously within the navigateByUrl() / navigate() call.
28636
- */
28637
- executeWithBypass(fn) {
28638
- this._bypassInterception = true;
28639
- try {
28640
- fn();
28641
- }
28642
- finally {
28643
- this._bypassInterception = false;
28644
- }
28645
- }
28646
- listenForUnsavedNavigationAttempts() {
28647
- this.router.events
28648
- .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$))
28649
- .subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
28650
- }
28651
- shouldIntercept(event) {
28652
- if (this._allowedTargetUrls.has('*')) {
28653
- this._allowedTargetUrls.clear();
28654
- return false;
28655
- }
28656
- if (this._allowedTargetUrls.has(event.url)) {
28657
- this._allowedTargetUrls.delete(event.url);
28658
- return false;
28659
- }
28660
- return true;
28661
- }
28662
- checkForPendingChanges(event) {
28663
- return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
28664
- }
28665
- cancelNavigationAndConfirm(event) {
28666
- this._bypassInterception = true;
28667
- void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
28668
- return this.confirmationDialog
28669
- .showDiscardConfirmDialog(this._confirmationMessage, 'page-navigation-confirmation')
28670
- .pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
28671
- }
28672
- navigateToConfirmedTarget(targetUrl) {
28673
- this._bypassInterception = false;
28674
- this._allowedTargetUrls.add(targetUrl);
28675
- void this.router.navigateByUrl(targetUrl);
28676
- }
28677
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
28678
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
28679
- }
28680
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
28681
- type: Injectable
28682
- }] });
28683
-
28684
28759
  class QdPageSubmitActionService {
28685
28760
  footerService = inject(QdPageFooterService);
28686
28761
  formGroupManagerService = inject(QdFormGroupManagerService);
28762
+ navigationInterceptor = inject(QdPageNavigationInterceptorService);
28687
28763
  _labelI18n = 'i18n.qd.page.footer.submit';
28688
- _submitHandler;
28764
+ _submitAction;
28689
28765
  _isVisible = true;
28690
28766
  _destroyed$ = new Subject();
28691
28767
  _cancelTrackFormValidity$ = new Subject();
@@ -28699,9 +28775,9 @@ class QdPageSubmitActionService {
28699
28775
  this._isVisible = isVisible;
28700
28776
  this.configureSubmitAction(pageTypeConfig.submit);
28701
28777
  }
28702
- configureSubmitAction(submitConfig) {
28703
- this._labelI18n = submitConfig.label?.i18n || 'i18n.qd.page.footer.submit';
28704
- this._submitHandler = this.buildSubmitHandler(submitConfig.handler);
28778
+ configureSubmitAction(action) {
28779
+ this._labelI18n = action.label?.i18n || 'i18n.qd.page.footer.submit';
28780
+ this._submitAction = action;
28705
28781
  this.registerSubmitAction();
28706
28782
  this.trackFormValidity();
28707
28783
  }
@@ -28711,7 +28787,7 @@ class QdPageSubmitActionService {
28711
28787
  key: 'submit',
28712
28788
  action: {
28713
28789
  titleI18n: this._labelI18n,
28714
- handler: this._submitHandler,
28790
+ handler: (...args) => this.executeSubmit(...args),
28715
28791
  isDisabled: true,
28716
28792
  isVisible: this._isVisible,
28717
28793
  type: QdFooterActionType.Primary
@@ -28719,12 +28795,16 @@ class QdPageSubmitActionService {
28719
28795
  }
28720
28796
  ]);
28721
28797
  }
28722
- buildSubmitHandler(originalHandler) {
28723
- return (...args) => {
28724
- if (!this.formGroupManagerService.hasFormGroups())
28725
- return originalHandler(...args);
28726
- return originalHandler(this.formGroupManagerService.getAllValues());
28727
- };
28798
+ executeSubmit(...args) {
28799
+ const action = this._submitAction;
28800
+ if (!action)
28801
+ return;
28802
+ const values = this.formGroupManagerService.hasFormGroups() ? this.formGroupManagerService.getAllValues() : args;
28803
+ QdPageCommitActionExecutor.execute(action, values, {
28804
+ formGroupManager: this.formGroupManagerService,
28805
+ navigationInterceptor: this.navigationInterceptor,
28806
+ destroyed$: this._destroyed$
28807
+ });
28728
28808
  }
28729
28809
  trackFormValidity() {
28730
28810
  this._cancelTrackFormValidity$.next();
@@ -28739,7 +28819,7 @@ class QdPageSubmitActionService {
28739
28819
  key: 'submit',
28740
28820
  action: {
28741
28821
  titleI18n: this._labelI18n,
28742
- handler: this._submitHandler,
28822
+ handler: (...args) => this.executeSubmit(...args),
28743
28823
  isDisabled: !isValid,
28744
28824
  isVisible: this._isVisible,
28745
28825
  type: QdFooterActionType.Primary
@@ -28863,7 +28943,15 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
28863
28943
  * handler: () => handleCancel()
28864
28944
  * },
28865
28945
  * save: {
28866
- * handler: (formValues) => handleSave(formValues)
28946
+ * handler: () => saveApi.save(form.value).pipe(
28947
+ * tap(result => notifications.success('Saved')),
28948
+ * map(() => true),
28949
+ * catchError(err => {
28950
+ * notifications.showError(err);
28951
+ * return of(false);
28952
+ * })
28953
+ * ),
28954
+ * onSuccess: () => router.navigate(['/overview'])
28867
28955
  * }
28868
28956
  * }
28869
28957
  * };
@@ -28981,7 +29069,8 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
28981
29069
  * pageType: 'create',
28982
29070
  * pageTypeConfig: {
28983
29071
  * submit: {
28984
- * handler: (formValues) => handleSubmit(formValues)
29072
+ * handler: (formValues) => createApi.create(formValues).pipe(map(() => true)),
29073
+ * onSuccess: () => router.navigate(['/items'])
28985
29074
  * }
28986
29075
  * }
28987
29076
  * };
@@ -29074,7 +29163,14 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
29074
29163
  * handler: () => handleCancel()
29075
29164
  * },
29076
29165
  * save: {
29077
- * handler: (formValues) => handleSave(formValues)
29166
+ * handler: (formValues) => saveApi.save(formValues).pipe(
29167
+ * map(() => true),
29168
+ * catchError(err => {
29169
+ * notifications.showError(err);
29170
+ * return of(false);
29171
+ * })
29172
+ * ),
29173
+ * onSuccess: () => router.navigate(['/overview'])
29078
29174
  * }
29079
29175
  * }
29080
29176
  * };
@@ -29218,7 +29314,7 @@ class QdPageComponent {
29218
29314
  if (this.config.pageType === 'create' && this.config?.pageTypeConfig?.cancel !== undefined)
29219
29315
  this.handleCancelActionWithFormChanges();
29220
29316
  if (this.config.pageType === 'create' && this.config?.pageTypeConfig?.saveDraft !== undefined)
29221
- this.initSaveDraftFooterAction();
29317
+ this.initSaveDraftFooterAction(this.config.pageTypeConfig.saveDraft);
29222
29318
  if (this.config.pageType === 'inspect')
29223
29319
  this.pageStoreService.isViewonly$
29224
29320
  .pipe(takeUntil(this._destroyed$))
@@ -29244,11 +29340,14 @@ class QdPageComponent {
29244
29340
  const action = pageTypeConfig[actionKey];
29245
29341
  if (!action)
29246
29342
  continue;
29343
+ const handler = actionKey === 'cancel'
29344
+ ? () => action.handler()
29345
+ : this.generateCommitActionHandler(action);
29247
29346
  actions.push({
29248
29347
  actionKey,
29249
29348
  partialAction: {
29250
29349
  ...(action?.label?.i18n ? { titleI18n: action.label.i18n } : {}),
29251
- handler: this.generateFooterActionHandler(action?.handler)
29350
+ handler
29252
29351
  }
29253
29352
  });
29254
29353
  }
@@ -29265,25 +29364,20 @@ class QdPageComponent {
29265
29364
  partialAction: {
29266
29365
  handler: hasChanged
29267
29366
  ? () => this.setupCancelConfirmation()
29268
- : () => {
29269
- this.navigationInterceptor.executeWithBypass(() => {
29270
- this.config?.pageTypeConfig?.cancel?.handler();
29271
- });
29272
- }
29367
+ : () => this.config?.pageTypeConfig?.cancel?.handler()
29273
29368
  }
29274
29369
  }
29275
29370
  ]);
29276
29371
  });
29277
29372
  }
29278
- initSaveDraftFooterAction() {
29279
- const pageTypeConfig = this.config.pageTypeConfig;
29373
+ initSaveDraftFooterAction(saveDraft) {
29280
29374
  this.footerService.setActions([
29281
29375
  {
29282
29376
  key: 'saveDraft',
29283
29377
  action: {
29284
- titleI18n: pageTypeConfig.saveDraft?.label?.i18n ?? 'i18n.qd.page.footer.saveDraft',
29378
+ titleI18n: saveDraft.label?.i18n ?? 'i18n.qd.page.footer.saveDraft',
29285
29379
  type: QdFooterActionType.Secondary,
29286
- handler: this.generateFooterActionHandler(pageTypeConfig?.saveDraft?.handler),
29380
+ handler: this.generateCommitActionHandler(saveDraft),
29287
29381
  isVisible: true,
29288
29382
  isDisabled: false
29289
29383
  }
@@ -29310,12 +29404,14 @@ class QdPageComponent {
29310
29404
  if (this._isInitialized)
29311
29405
  this.operationModeChanged.emit(mode);
29312
29406
  }
29313
- generateFooterActionHandler(handler) {
29407
+ generateCommitActionHandler(action) {
29314
29408
  return (...args) => {
29315
- if (!handler)
29316
- return;
29317
29409
  const values = this.formGroupManagerService.hasFormGroups() ? this.formGroupManagerService.getAllValues() : args;
29318
- this.navigationInterceptor.executeWithBypass(() => handler(values));
29410
+ QdPageCommitActionExecutor.execute(action, values, {
29411
+ formGroupManager: this.formGroupManagerService,
29412
+ navigationInterceptor: this.navigationInterceptor,
29413
+ destroyed$: this._destroyed$
29414
+ });
29319
29415
  };
29320
29416
  }
29321
29417
  setupCancelConfirmation() {
@@ -29324,8 +29420,8 @@ class QdPageComponent {
29324
29420
  .showDiscardConfirmDialog(cancelConfig?.confirmationMessage, this.testId + '-cancel-confirmation')
29325
29421
  .pipe(filter$1(result => result), takeUntil(this._destroyed$))
29326
29422
  .subscribe(() => {
29423
+ this.formGroupManagerService.restoreFormGroupsFromSnapshot();
29327
29424
  cancelConfig?.handler?.();
29328
- this.navigationInterceptor.allowNextNavigation();
29329
29425
  });
29330
29426
  }
29331
29427
  initSubmitValidation() {
@@ -29333,7 +29429,7 @@ class QdPageComponent {
29333
29429
  this.formGroupManagerService
29334
29430
  .$areFormGroupsValid()
29335
29431
  .pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
29336
- const submitDisabledInfoText = this.config.pageType === 'inspect' ? this.config.pageTypeConfig?.submit?.disabledInfo : undefined;
29432
+ const submitDisabledInfoText = this.config.pageTypeConfig?.submit?.disabledInfo;
29337
29433
  this.footerService.updateActions([
29338
29434
  {
29339
29435
  actionKey: 'submit',
@@ -32613,9 +32709,11 @@ class QdQuickEditComponent {
32613
32709
  window.setTimeout(() => this.focusFirstControl());
32614
32710
  }
32615
32711
  removeRow(index) {
32616
- if (this.config.removeConfirmationMessage) {
32712
+ const { hasRemoveConfirmation, removeConfirmationMessage } = this.config;
32713
+ if (hasRemoveConfirmation || removeConfirmationMessage) {
32714
+ const message = removeConfirmationMessage ?? { i18n: 'i18n.qd.quick.edit.removeConfirmation.message' };
32617
32715
  this.confirmationDialogService
32618
- .showConfirmDialog({ message: this.config.removeConfirmationMessage }, (this.testId ?? 'quick-edit') + '-remove-confirmation')
32716
+ .showConfirmDialog({ message }, (this.testId ?? 'quick-edit') + '-remove-confirmation')
32619
32717
  .pipe(filter$1(confirmed => confirmed))
32620
32718
  .subscribe(() => this.executeRemoveRow(index));
32621
32719
  return;