@quadrel-enterprise-ui/framework 20.10.0 → 20.10.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.
package/index.d.ts CHANGED
@@ -9214,11 +9214,17 @@ interface QdPageCustomActionsLabel {
9214
9214
  */
9215
9215
  type QdInspectOperationMode = 'view' | 'edit';
9216
9216
  /**
9217
- * @description Interface for the save action, including optional validation configuration.
9217
+ * @description Common shape for all framework-managed commit actions (save, save draft, submit).
9218
+ *
9219
+ * Each commit action is built around a single `handler` whose return value tells the framework how
9220
+ * the action ended (success / planned failure / error), with optional `onSuccess` / `onError` hooks
9221
+ * for consumer-side reactions. Per-action interfaces (`QdPageSaveAction`, `QdPageSaveDraftAction`,
9222
+ * `QdPageCreateSubmitAction`, `QdPageInspectSubmitAction`) extend this base with their action-specific
9223
+ * options.
9218
9224
  */
9219
- interface QdPageSaveAction {
9225
+ interface QdPageCommitAction {
9220
9226
  /**
9221
- * @description Label for the save action, used for translation.
9227
+ * @description Label for the action, used for translation.
9222
9228
  *
9223
9229
  * * If no custom translation key is provided, a standard label will be used by default.
9224
9230
  */
@@ -9226,17 +9232,106 @@ interface QdPageSaveAction {
9226
9232
  i18n: string;
9227
9233
  };
9228
9234
  /**
9229
- * @description Triggered when the save action is executed.
9235
+ * @description Triggered when the action is executed.
9230
9236
  *
9231
9237
  * Success criteria:
9232
- * - If the handler returns an Observable<boolean>, only the first emission after the save click counts:
9233
- * - **true:** success - toggle to view and apply the saved values
9234
- * - **false:** failure/cancel - stay in edit mode
9235
- * - If the handler returns no Observable (void), the save is treated as instant success (toggle + apply values).
9238
+ * - If the handler returns an `Observable<boolean>`, only the first emission counts:
9239
+ * - **true:** success the framework marks the form as saved (the page is no longer dirty).
9240
+ * - **false:** failure/cancel the form stays dirty.
9241
+ * - If the handler returns `void`, the action is treated as instant success and the form is marked as saved immediately.
9242
+ *
9243
+ * Errors emitted by the Observable trigger the optional `onError` hook, or fall back to `console.error` if
9244
+ * `onError` is not configured. The form stays dirty either way.
9245
+ *
9246
+ * **When to emit `false` vs. let an error fly:**
9247
+ * - `false` = predictable cancel/reject (user aborted, business rule, structured validation response from the backend).
9248
+ * - Error = unexpected (network outage, 5xx, bug).
9249
+ *
9250
+ * Rule of thumb: if the UI can explain the cause to the user, emit `false` (plus a notification). Otherwise let the error propagate.
9251
+ *
9252
+ * ---
9253
+ *
9254
+ * **Anti-pattern: do not navigate from inside an async handler pipeline.**
9236
9255
  *
9237
- * On a successful save, the framework applies the values and resets its internal validation and change-tracking state.
9256
+ * The framework runs the synchronous handler invocation inside its navigation-bypass window. For
9257
+ * a sync `void` handler that calls `router.navigate(...)` directly, this is fine — the bypass is
9258
+ * active and the form is marked as saved immediately afterwards.
9259
+ *
9260
+ * For an `Observable<boolean>` handler, the bypass window closes as soon as the handler returns
9261
+ * the observable. Side effects wired into `tap` (or any later operator) run **after** the bypass
9262
+ * has already closed AND **before** the framework marks the form as saved on the `true` emission.
9263
+ * Navigating from there raises the unsaved-changes dialog — exactly what the framework is trying
9264
+ * to suppress. Use `onSuccess` for navigation after async commits.
9238
9265
  */
9239
9266
  handler: (formValues?: any) => void | Observable<boolean>;
9267
+ /**
9268
+ * @description Optional callback invoked after the framework marks the form as saved on a successful commit.
9269
+ *
9270
+ * * Runs in the framework's navigation-bypass window, so router navigation from this hook does **not**
9271
+ * raise the unsaved-changes dialog.
9272
+ *
9273
+ * * For async handlers (returning `Observable<boolean>`), this hook is the **only** race-free place to
9274
+ * navigate. Side effects in `tap` run *before* the framework marks the form as saved; navigation
9275
+ * from there would still see a dirty form and trigger the dialog.
9276
+ *
9277
+ * @example
9278
+ * ```ts
9279
+ * submit: {
9280
+ * handler: (values) => api.create(values).pipe(map(() => true)),
9281
+ * onSuccess: () => router.navigateByUrl('/')
9282
+ * }
9283
+ * ```
9284
+ */
9285
+ onSuccess?: () => void;
9286
+ /**
9287
+ * @description Optional callback invoked when the handler observable emits an error.
9288
+ *
9289
+ * * Receives the original error from the observable.
9290
+ *
9291
+ * * **Not** invoked when the handler emits `false` — `false` is a planned outcome, errors are unexpected
9292
+ * (see the `handler` doc for the distinction).
9293
+ *
9294
+ * * **Does not run in the navigation-bypass window.** Navigation from this hook raises the unsaved-changes
9295
+ * dialog, because the form is still dirty after a failed commit — and the user usually wants to keep
9296
+ * their edits. To navigate away unconditionally (e.g. on auth expiry), call
9297
+ * `QdPageComponent.discardUnsavedChanges()` first to reset the form to its last saved state.
9298
+ *
9299
+ * * If `onError` is not configured, the framework falls back to `console.error` so failures never go silent.
9300
+ *
9301
+ * @example
9302
+ * ```ts
9303
+ * submit: {
9304
+ * handler: (values) => api.create(values).pipe(map(() => true)),
9305
+ * onSuccess: () => router.navigateByUrl('/'),
9306
+ * onError: (err) => notifications.error('i18n.create.failed', err)
9307
+ * }
9308
+ * ```
9309
+ *
9310
+ * @example
9311
+ * Navigate away unconditionally from onError (e.g. auth-expiry redirect to login). Call `discardUnsavedChanges()` first to drop the dirty snapshot so the next navigation passes through without the dialog:
9312
+ *
9313
+ * ```ts
9314
+ * @ViewChild(QdPageComponent) pageComponent!: QdPageComponent<MyModel>;
9315
+ *
9316
+ * submit: {
9317
+ * handler: (values) => api.save(values).pipe(map(() => true)),
9318
+ * onError: (err) => {
9319
+ * if (err.status === 401) {
9320
+ * this.pageComponent.discardUnsavedChanges();
9321
+ * this.router.navigateByUrl('/login');
9322
+ * return;
9323
+ * }
9324
+ * this.notifications.error('i18n.save.failed', err);
9325
+ * }
9326
+ * }
9327
+ * ```
9328
+ */
9329
+ onError?: (err: unknown) => void;
9330
+ }
9331
+ /**
9332
+ * @description Interface for the save action, including optional validation configuration.
9333
+ */
9334
+ interface QdPageSaveAction extends QdPageCommitAction {
9240
9335
  /**
9241
9336
  * @description Optional validation flag, defaults to true.
9242
9337
  * Determines whether form validation is required before saving and ensures that changes must be present in the form before saving.
@@ -9250,21 +9345,10 @@ interface QdPageSaveAction {
9250
9345
  hasValidation?: boolean;
9251
9346
  }
9252
9347
  /**
9253
- * @description Interface for the safe draft action.
9348
+ * @description Interface for the save draft action. Inherits the standard commit-action shape
9349
+ * (`label`, `handler`, `onSuccess`, `onError`) without further options.
9254
9350
  */
9255
- interface QdPageSaveDraftAction {
9256
- /**
9257
- * @description Label for the save draft action, used for translation.
9258
- *
9259
- * * If no custom translation key is provided, a standard label will be used by default.
9260
- */
9261
- label?: {
9262
- i18n: string;
9263
- };
9264
- /**
9265
- * @description Handler function that is triggered when the save draft action is executed.
9266
- */
9267
- handler: () => void;
9351
+ interface QdPageSaveDraftAction extends QdPageCommitAction {
9268
9352
  }
9269
9353
  /**
9270
9354
  * @description Interface for the cancel action, including an optional confirmation message.
@@ -9295,45 +9379,10 @@ interface QdPageCancelAction {
9295
9379
  };
9296
9380
  }
9297
9381
  /**
9298
- * @description Interface for the submit action on create pages.
9382
+ * @description Interface for the submit action on create pages. Inherits the standard commit-action
9383
+ * shape (`label`, `handler`, `onSuccess`, `onError`) without further options.
9299
9384
  */
9300
- interface QdPageCreateSubmitAction {
9301
- /**
9302
- * @description Label for the submit action, used for translation.
9303
- *
9304
- * * If no custom translation key is provided, a standard label will be used by default.
9305
- */
9306
- label?: {
9307
- i18n: string;
9308
- };
9309
- /**
9310
- * @description Handler function that is triggered when the submit action is executed.
9311
- */
9312
- handler: (formValues?: any) => void;
9313
- }
9314
- /**
9315
- * @description Interface for the submit action.
9316
- */
9317
- interface QdPageSubmitAction {
9318
- /**
9319
- * @description Label for the submit action, used for translation.
9320
- *
9321
- * * If no custom translation key is provided, a standard label will be used by default.
9322
- */
9323
- label?: {
9324
- i18n: string;
9325
- };
9326
- /**
9327
- * @description Handler function that is triggered when the submit action is executed.
9328
- */
9329
- handler: (formValues?: any) => void;
9330
- /**
9331
- * @description By default, the visibility of the submit button depends on the page type and the operation mode.
9332
- * To completely hide the submit button, you can set this isHidden flag.
9333
- *
9334
- * @default false
9335
- */
9336
- isHidden?: boolean;
9385
+ interface QdPageCreateSubmitAction extends QdPageCommitAction {
9337
9386
  }
9338
9387
  /**
9339
9388
  * @description Interface for the edit action.
@@ -9375,9 +9424,18 @@ interface QdPageArchiveAction {
9375
9424
  handler: (formValues?: any) => void;
9376
9425
  }
9377
9426
  /**
9378
- * @description Inspect-specific submit action configuration.
9427
+ * @description Inspect-specific submit action configuration. Submit on inspect pages is a process
9428
+ * action (status change, approval, workflow transition) and is only visible in **view** mode.
9379
9429
  */
9380
- interface QdPageInspectSubmitAction extends QdPageSubmitAction {
9430
+ interface QdPageInspectSubmitAction extends QdPageCommitAction {
9431
+ /**
9432
+ * @description By default, the visibility of the submit button depends on the operation mode
9433
+ * (visible in view mode, hidden in edit mode). Set this flag to hide the button entirely,
9434
+ * regardless of mode.
9435
+ *
9436
+ * @default false
9437
+ */
9438
+ isHidden?: boolean;
9381
9439
  /**
9382
9440
  * @description Optional info message shown when submit is disabled on inspect pages for non-validation reasons.
9383
9441
  *
@@ -12806,6 +12864,22 @@ declare class QdSectionComponent implements OnInit, AfterViewInit, OnChanges, On
12806
12864
  *
12807
12865
  * Please check the relevant interfaces for each page type: `QdPageConfigOverview`, `QdPageConfigCreate`, `QdPageConfigInspect`, and `QdPageConfigCustom`.
12808
12866
  *
12867
+ * #### **Commit Actions**
12868
+ *
12869
+ * Commit actions (`submit`, `save`, `saveDraft`) can either run synchronously (handler returns `void`) or wait on an async result (handler returns `Observable<boolean>`). The framework waits for the first emission, advances the saved-state baseline on `true`, and exposes race-free hooks for side effects so navigation after a successful commit does not trigger the unsaved-changes dialog.
12870
+ *
12871
+ * `onSuccess` runs when the handler emits `true` (after the baseline has advanced), `onError` runs when the handler observable errors — e.g. a failed HTTP request that is not caught inside the pipeline.
12872
+ *
12873
+ * ```ts
12874
+ * submit: {
12875
+ * handler: (formValues) => this.api.create(formValues).pipe(map(() => true)),
12876
+ * onSuccess: () => this.router.navigateByUrl('/'),
12877
+ * onError: (err) => this.notifications.add('', { type: 'critical', i18n: 'i18n.myApp.create.failed' })
12878
+ * }
12879
+ * ```
12880
+ *
12881
+ * The same mechanism applies to `save` and `saveDraft`. For the full contract (success vs. planned `false` vs. error, anti-patterns, `discardUnsavedChanges()`), see `QdPageCommitAction` and its action-specific extensions `QdPageSaveAction`, `QdPageSaveDraftAction`, `QdPageCreateSubmitAction`, and `QdPageInspectSubmitAction`.
12882
+ *
12809
12883
  * #### **Validation/Parameterization**
12810
12884
  *
12811
12885
  * Validation and parameterization are covered in a dedicated chapter in the Storybook. Please check the "Validation" section for more information.
@@ -12875,7 +12949,15 @@ declare class QdSectionComponent implements OnInit, AfterViewInit, OnChanges, On
12875
12949
  * handler: () => handleCancel()
12876
12950
  * },
12877
12951
  * save: {
12878
- * handler: (formValues) => handleSave(formValues)
12952
+ * handler: () => saveApi.save(form.value).pipe(
12953
+ * tap(result => notifications.success('Saved')),
12954
+ * map(() => true),
12955
+ * catchError(err => {
12956
+ * notifications.showError(err);
12957
+ * return of(false);
12958
+ * })
12959
+ * ),
12960
+ * onSuccess: () => router.navigate(['/overview'])
12879
12961
  * }
12880
12962
  * }
12881
12963
  * };
@@ -12993,7 +13075,8 @@ declare class QdSectionComponent implements OnInit, AfterViewInit, OnChanges, On
12993
13075
  * pageType: 'create',
12994
13076
  * pageTypeConfig: {
12995
13077
  * submit: {
12996
- * handler: (formValues) => handleSubmit(formValues)
13078
+ * handler: (formValues) => createApi.create(formValues).pipe(map(() => true)),
13079
+ * onSuccess: () => router.navigate(['/items'])
12997
13080
  * }
12998
13081
  * }
12999
13082
  * };
@@ -13086,7 +13169,14 @@ declare class QdSectionComponent implements OnInit, AfterViewInit, OnChanges, On
13086
13169
  * handler: () => handleCancel()
13087
13170
  * },
13088
13171
  * save: {
13089
- * handler: (formValues) => handleSave(formValues)
13172
+ * handler: (formValues) => saveApi.save(formValues).pipe(
13173
+ * map(() => true),
13174
+ * catchError(err => {
13175
+ * notifications.showError(err);
13176
+ * return of(false);
13177
+ * })
13178
+ * ),
13179
+ * onSuccess: () => router.navigate(['/overview'])
13090
13180
  * }
13091
13181
  * }
13092
13182
  * };
@@ -13193,12 +13283,38 @@ declare class QdPageComponent<T extends object> implements OnInit, OnChanges, Af
13193
13283
  ngOnChanges(changes: SimpleChanges): void;
13194
13284
  ngAfterViewInit(): void;
13195
13285
  ngOnDestroy(): void;
13286
+ /**
13287
+ * @description Resets all registered form groups to their last saved snapshot, marking the page
13288
+ * as no longer dirty.
13289
+ *
13290
+ * Intended for explicit consumer-driven discard scenarios where you want subsequent navigation
13291
+ * to bypass the unsaved-changes dialog — for example inside a commit action's `onError` hook
13292
+ * after an auth-expiry response, where the user must be redirected to the login page regardless
13293
+ * of unsaved edits.
13294
+ *
13295
+ * Prefer this over wiring custom bypass logic; it keeps the discard explicit and auditable in
13296
+ * application code.
13297
+ *
13298
+ * @example
13299
+ * ```ts
13300
+ * save: {
13301
+ * handler: (values) => api.save(values).pipe(map(() => true)),
13302
+ * onError: (err) => {
13303
+ * if (err.status === 401) {
13304
+ * this.pageComponent.discardUnsavedChanges();
13305
+ * this.router.navigateByUrl('/login');
13306
+ * }
13307
+ * }
13308
+ * }
13309
+ * ```
13310
+ */
13311
+ discardUnsavedChanges(): void;
13196
13312
  private checkConfigValidity;
13197
13313
  private setupCreatePageFooterActions;
13198
13314
  private handleCancelActionWithFormChanges;
13199
13315
  private initSaveDraftFooterAction;
13200
13316
  private updateInspectPageOperationMode;
13201
- private generateFooterActionHandler;
13317
+ private generateCommitActionHandler;
13202
13318
  private setupCancelConfirmation;
13203
13319
  private initSubmitValidation;
13204
13320
  private setupPageDialogCloseContract;
@@ -13325,6 +13441,7 @@ declare class QdPageStoreService<T extends object> {
13325
13441
  declare class QdPageObjectHeaderComponent<T extends object> implements OnInit, OnChanges, OnDestroy {
13326
13442
  private pageObjectResolver;
13327
13443
  private formGroupManagerService;
13444
+ private navigationInterceptor;
13328
13445
  private dialogComponent;
13329
13446
  private dialog;
13330
13447
  private confirmationDialogService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quadrel-enterprise-ui/framework",
3
- "version": "20.10.0",
3
+ "version": "20.10.1",
4
4
  "exports": {
5
5
  "./jest-preset": "./jest-preset.js",
6
6
  "./package.json": {