@eherve/angular-material-entity-wizard 0.0.0-stage → 22.1.0

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/README.md CHANGED
@@ -1,3 +1,34 @@
1
- # Temporary Holding Version
1
+ # Angular Material entity wizard
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Independent creation wizard using the shared forms session and renderers. This package has no dependency on edit-entity-dialog.
4
+
5
+ ```ts
6
+ import { CreateEntityWizardService, createEntityWizardOptions, CreateEntityWizardComponentOptions } from '@eherve/angular-material-entity-wizard';
7
+
8
+ const options = createEntityWizardOptions({
9
+ title: 'Create',
10
+ steps: [{ id: 'main', label: 'Main', displayOptions: { section: { id: 'main', label: '', cards: [] } } }],
11
+ service: { create: async value => ({ _id: 'created', ...value }) },
12
+ });
13
+ wizard.open(options);
14
+ ```
15
+
16
+ `CreateEntityWizardComponentOptions<TRecord>` extends the canonical `EditEntityComponentOptions<TRecord>` from `@eherve/angular-material-forms/core` with `wizard`. The normalization helper retains the established wizard configuration shape and options identity when opened. Standard edit options contain no wizard property.
17
+
18
+ Each wizard owns one shared `EntityFormSession`; changing a step does not replace the form. Its scoped save policy projects only current-step and always-in-upsert fields, preserves other-step drafts during server hydration, blocks navigation during saves or pending validation and accumulates created/updated results. Component-managed steps use the supplied `markUpdated` callback. The independent modal wrapper retains top-dialog keyboard and focused-input key ownership behavior.
19
+
20
+ Include `@eherve/angular-material-forms/styles.css` once. Public `EntityWizardComponent` supports page or modal display, and `CreateEntityWizardService` opens its own `EntityWizardDialog`. Version `22.0.0` requires `@eherve/angular-material-forms@^22.0.0` and has no dependency on edit-dialog.
21
+
22
+ ### Restoring navigation
23
+
24
+ `CreateEntityWizardOptions` accepts the shared `navigation` options. Each step's stable
25
+ `id` is its `navigation.tab`; child section navigation uses the existing adapters.
26
+ Initial selection applies after record hydration and visible-step computation. Existing
27
+ persisted records can select an allowed visible step; a record without an id retains
28
+ the first-step restriction. Restoration never calls create, update or Continue.
29
+ `stateChanges` and lifecycle-handle navigation update the actual wizard selection,
30
+ respecting pending validation/updating restrictions. User step changes publish `onChange`.
31
+ The forms connection lifecycle (`ready`, `presentationChanged`, `failed`, `beforeClose`)
32
+ also applies to wizard sessions.
33
+
34
+ The optional lifecycle connection `opened(handle)` receives a close-only `requestClose(): Promise<boolean>` handle before asynchronous loading. It supports cancelling loading or failed content through the existing close guards; it does not signal readiness. `ready` remains the rendered-content barrier. Handles from previous openings return `false`.
@@ -0,0 +1,673 @@
1
+ import * as i2 from "@angular/forms";
2
+ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
3
+ import { EditEntityConditionalStateService, EditEntityFormBuilderService, EditEntityRenderService, EditEntitySubmitValueService, isNonEditable } from "@eherve/angular-material-forms/core";
4
+ import { cloneDeep, get, has, isEqual, set } from "lodash-es";
5
+ import * as i0 from "@angular/core";
6
+ import { ChangeDetectionStrategy, Component, Inject, Injectable, Input, Output, ViewChild, computed, inject, signal } from "@angular/core";
7
+ import { EntityDialogKeyboardHost, EntityDialogLauncher, EntityFormSession } from "@eherve/angular-material-forms/editor";
8
+ import * as i1 from "@angular/material/dialog";
9
+ import { MAT_DIALOG_DATA } from "@angular/material/dialog";
10
+ import * as i1$1 from "@angular/common";
11
+ import { CommonModule } from "@angular/common";
12
+ import * as i3 from "@angular/material/button";
13
+ import { MatButtonModule } from "@angular/material/button";
14
+ import * as i4 from "@angular/material/icon";
15
+ import { MatIconModule } from "@angular/material/icon";
16
+ import * as i5 from "@angular/material/progress-spinner";
17
+ import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
18
+ import * as i7 from "@angular/material/stepper";
19
+ import { MatStepper, MatStepperModule } from "@angular/material/stepper";
20
+ import * as i6 from "@angular/material/tooltip";
21
+ import { MatTooltipModule } from "@angular/material/tooltip";
22
+ import { FormContentComponent } from "@eherve/angular-material-forms/layout";
23
+ var CreateEntityWizardState = class {
24
+ valid(form, paths) {
25
+ return paths.every((path) => {
26
+ const control = form.getControl(path);
27
+ return !control || control.disabled || !control.pending && control.valid;
28
+ });
29
+ }
30
+ project(form, paths, changedOnly, always = []) {
31
+ const value = {};
32
+ for (const path of /* @__PURE__ */ new Set([...paths, ...always])) {
33
+ const control = form.getControl(path);
34
+ if (!control || isNonEditable(control)) continue;
35
+ const current = control.getRawValue();
36
+ const initial = this.read(form.initialValue, path);
37
+ if (!changedOnly || always.includes(path) || !isEqual(current, initial)) set(value, path, cloneDeep(current));
38
+ }
39
+ return value;
40
+ }
41
+ hydrate(form, record, submitted) {
42
+ const baseline = cloneDeep(form.initialValue);
43
+ const visit = (control, path) => {
44
+ if (control instanceof FormGroup) {
45
+ for (const [key, child] of Object.entries(control.controls)) visit(child, path ? `${path}.${key}` : key);
46
+ return;
47
+ }
48
+ const saved = submitted.some((name) => path === name || path.startsWith(`${name}.`));
49
+ const current = control.getRawValue();
50
+ const draft = !isEqual(current, this.read(baseline, path));
51
+ if (saved || !draft) {
52
+ if (has(record, path)) control.patchValue(cloneDeep(get(record, path)), { emitEvent: false });
53
+ if (Object.prototype.hasOwnProperty.call(form.controls, path)) baseline[path] = cloneDeep(control.getRawValue());
54
+ else set(baseline, path, cloneDeep(control.getRawValue()));
55
+ control.markAsPristine();
56
+ control.markAsUntouched();
57
+ }
58
+ };
59
+ visit(form, "");
60
+ form.setInitialValue(baseline);
61
+ }
62
+ read(value, path) {
63
+ return Object.prototype.hasOwnProperty.call(value, path) ? value[path] : get(value, path);
64
+ }
65
+ };
66
+ var EntityWizardComponent = class EntityWizardComponent {
67
+ options;
68
+ displayMode = "page";
69
+ session = inject(EntityFormSession);
70
+ result = this.session.result;
71
+ closeRequest = this.session.closeRequest;
72
+ ngOnInit() {
73
+ this.session.options = this.options;
74
+ this.session.displayMode = this.displayMode;
75
+ this.session.policy = {
76
+ valid: () => this.wizardValid,
77
+ value: (create) => this.wizardValue(create),
78
+ patch: (record, value) => this.patchWizardRecord(record, value),
79
+ saved: (record, value) => this.hydrateWizard(record, value),
80
+ hydrate: (record) => this.wizardState.hydrate(this.session.formGroup, record, []),
81
+ refresh: () => this.refreshWizardState(),
82
+ navigate: () => this.restoreWizardNavigation(),
83
+ reset: () => {
84
+ this.wizardIndex.set(0);
85
+ this.wizardCompleted.set(this.options.wizard.steps.map(() => false));
86
+ this.wizardVisibleIndices.set([]);
87
+ this.wizardNavigationControls = [];
88
+ },
89
+ componentUpdated: () => this.completeWizardStep(),
90
+ beforeClose: () => this.session.formGroup.setInitialValue(this.session.formGroup.initialValue),
91
+ stickyResult: true
92
+ };
93
+ this.session.initialize();
94
+ }
95
+ ngOnChanges(changes) {
96
+ if (changes["options"] && !changes["options"].firstChange) {
97
+ this.session.options = this.options;
98
+ this.session.initialize();
99
+ }
100
+ }
101
+ ngOnDestroy() {
102
+ this.session.destroy();
103
+ }
104
+ requestClose() {
105
+ return this.session.requestClose();
106
+ }
107
+ refreshPresentation() {
108
+ this.session.refreshPresentation();
109
+ }
110
+ submitFromKeyboard() {
111
+ if (!this.options.validateOnEnter || this.session.loading || this.session.updating) return false;
112
+ this.wizardNext();
113
+ return true;
114
+ }
115
+ wizardIndex = signal(0, ...ngDevMode ? [{ debugName: "wizardIndex" }] : /* istanbul ignore next */ []);
116
+ wizardDrafts = signal([], ...ngDevMode ? [{ debugName: "wizardDrafts" }] : /* istanbul ignore next */ []);
117
+ wizardCompleted = signal([], ...ngDevMode ? [{ debugName: "wizardCompleted" }] : /* istanbul ignore next */ []);
118
+ wizardVisibleIndices = signal([], ...ngDevMode ? [{ debugName: "wizardVisibleIndices" }] : /* istanbul ignore next */ []);
119
+ wizardVisualIndex = computed(() => Math.max(0, this.wizardVisibleIndices().indexOf(this.wizardIndex())), ...ngDevMode ? [{ debugName: "wizardVisualIndex" }] : /* istanbul ignore next */ []);
120
+ wizardNavigationBlocked = signal(false, ...ngDevMode ? [{ debugName: "wizardNavigationBlocked" }] : /* istanbul ignore next */ []);
121
+ wizardLinear = signal(true, ...ngDevMode ? [{ debugName: "wizardLinear" }] : /* istanbul ignore next */ []);
122
+ wizardNavigationControls = [];
123
+ wizardStepper;
124
+ wizardState = new CreateEntityWizardState();
125
+ get wizardStep() {
126
+ return this.options?.wizard?.steps[this.wizardIndex()];
127
+ }
128
+ get wizardLast() {
129
+ return this.wizardIndex() === this.wizardVisibleIndices().at(-1);
130
+ }
131
+ get wizardPrimaryLabel() {
132
+ const wizard = this.options.wizard;
133
+ if (!this.options.record?._id) return wizard?.createNextLabel ?? this.session.intl.messages.wizardCreateNext;
134
+ return this.wizardLast ? wizard?.finishLabel ?? this.session.intl.messages.wizardFinish : wizard?.nextLabel ?? this.session.intl.messages.wizardNext;
135
+ }
136
+ get wizardExitLabel() {
137
+ return this.options.record?._id ? this.options.wizard?.finishLaterLabel ?? this.session.intl.messages.wizardFinishLater : this.session.cancelLabel;
138
+ }
139
+ selectWizardVisualStep(position) {
140
+ const index = this.wizardVisibleIndices()[position];
141
+ if (index !== void 0) this.selectWizardStep(index);
142
+ }
143
+ advanceWizard() {
144
+ const next = this.wizardVisibleIndices()[this.wizardVisualIndex() + 1];
145
+ if (next !== void 0) {
146
+ this.wizardIndex.set(next);
147
+ this.publishWizardNavigation();
148
+ this.refreshWizardNavigation();
149
+ }
150
+ }
151
+ completeWizardStep() {
152
+ const completed = [...this.wizardCompleted()];
153
+ completed[this.wizardIndex()] = true;
154
+ this.wizardCompleted.set(completed);
155
+ this.refreshWizardNavigation();
156
+ }
157
+ refreshWizardNavigation() {
158
+ if (!this.options?.wizard) return;
159
+ const blocked = this.session.updating || this.wizardPending;
160
+ this.wizardNavigationBlocked.set(blocked);
161
+ const linear = !this.options.record?._id || blocked;
162
+ this.wizardLinear.set(linear);
163
+ if (this.wizardStepper) this.wizardStepper.linear = linear;
164
+ const count = this.options.wizard.steps.length;
165
+ if (this.wizardNavigationControls.length !== count) this.wizardNavigationControls = Array.from({ length: count }, () => new FormControl(false, {
166
+ nonNullable: true,
167
+ validators: Validators.requiredTrue
168
+ }));
169
+ for (const [index, control] of this.wizardNavigationControls.entries()) control.setValue(!blocked && (index !== 0 || !!this.options.record?._id), { emitEvent: false });
170
+ this.wizardStepper?.steps.forEach((step, position) => {
171
+ const index = this.wizardVisibleIndices()[position];
172
+ step.optional = false;
173
+ step.interacted = true;
174
+ step.completed = !blocked && !!this.wizardCompleted()[index];
175
+ step.editable = !blocked && (index === 0 || !!this.options.record?._id);
176
+ });
177
+ }
178
+ get wizardValid() {
179
+ return this.wizardState.valid(this.session.formGroup, [...this.wizardPaths(), ...this.wizardAlwaysPaths()]);
180
+ }
181
+ get wizardPending() {
182
+ return [...this.wizardPaths(), ...this.wizardAlwaysPaths()].some((path) => this.session.formGroup.getControl(path)?.pending);
183
+ }
184
+ get wizardUnsaved() {
185
+ return Object.keys(this.wizardState.project(this.session.formGroup, this.wizardPaths(), true)).length > 0;
186
+ }
187
+ selectWizardStep(index) {
188
+ if (this.session.updating || this.wizardPending || !this.options.wizard || index < 0 || index >= this.options.wizard.steps.length || !this.wizardVisibleIndices().includes(index)) return;
189
+ if (index > 0 && !this.options.record?._id) return;
190
+ this.wizardIndex.set(index);
191
+ this.publishWizardNavigation();
192
+ this.refreshWizardNavigation();
193
+ }
194
+ skipWizardStep() {
195
+ if (this.session.updating || this.wizardPending || !this.wizardStep?.optional || !this.options.record?._id) return;
196
+ if (this.wizardLast) this.requestClose();
197
+ else this.advanceWizard();
198
+ }
199
+ async wizardNext() {
200
+ if (this.session.updating || !this.options.wizard) return;
201
+ const isCurrent = this.session.captureCurrentOperation();
202
+ if (!this.wizardStep?.componentManaged) {
203
+ for (const path of this.wizardPaths()) this.session.formGroup.getControl(path)?.markAllAsTouched();
204
+ if (!this.wizardValid) return;
205
+ const previous = this.session.savedResult;
206
+ if (!this.options.record?._id) await this.session.create();
207
+ else if (this.wizardUnsaved) await this.session.update();
208
+ else {
209
+ this.completeWizardStep();
210
+ if (this.wizardLast) await this.requestClose();
211
+ else this.advanceWizard();
212
+ return;
213
+ }
214
+ if (!isCurrent() || previous === this.session.savedResult) return;
215
+ }
216
+ if (this.wizardLast) await this.requestClose();
217
+ else this.advanceWizard();
218
+ }
219
+ wizardPaths(index = this.wizardIndex()) {
220
+ const display = this.options?.displayOptions;
221
+ const tab = display && "tabs" in display ? display.tabs[index] : void 0;
222
+ const names = tab ? this.session.renderService.collectFields(tab.displayOptions).flatMap((field) => field.kind === "separator" || field.kind === "metadata" || field.kind === "display" ? [] : [field.name]) : [];
223
+ return [.../* @__PURE__ */ new Set([...names, ...this.options.wizard?.steps[index]?.fields ?? []])];
224
+ }
225
+ wizardAlwaysPaths() {
226
+ return this.session.fields.flatMap((field) => field.kind !== "separator" && field.kind !== "metadata" && field.kind !== "display" && field.alwaysInUpsert ? [field.name] : []);
227
+ }
228
+ wizardValue(create) {
229
+ const paths = this.wizardPaths();
230
+ const always = this.wizardAlwaysPaths();
231
+ const value = this.wizardState.project(this.session.formGroup, paths, !create, always);
232
+ if (create) {
233
+ for (const path of [...paths, ...always]) if (!this.session.formGroup.getControl(path) && has(this.options.defaultValues, path)) set(value, path, cloneDeep(get(this.options.defaultValues, path)));
234
+ }
235
+ return value;
236
+ }
237
+ patchWizardRecord(record, value) {
238
+ const updated = cloneDeep(record);
239
+ for (const path of [...this.wizardPaths(), ...this.wizardAlwaysPaths()]) if (has(value, path)) set(updated, path, cloneDeep(get(value, path)));
240
+ return updated;
241
+ }
242
+ async hydrateWizard(record, value) {
243
+ const paths = [...this.wizardPaths(), ...this.wizardAlwaysPaths()].filter((path) => has(value, path));
244
+ this.wizardState.hydrate(this.session.formGroup, record, paths);
245
+ this.completeWizardStep();
246
+ this.session.updated = this.session.formGroup.isUpdated;
247
+ this.session.refreshAllowedActions();
248
+ this.session.refreshRuntimeState();
249
+ }
250
+ publishWizardNavigation() {
251
+ this.session.navigationController.selectTab(this.wizardVisualIndex());
252
+ }
253
+ restoreWizardNavigation() {
254
+ const index = this.wizardVisibleIndices()[this.session.navigationController.selectedIndex];
255
+ if (index !== void 0 && !this.session.updating && !this.wizardPending && (index === 0 || !!this.options.record?._id)) this.wizardIndex.set(index);
256
+ this.publishWizardNavigation();
257
+ this.refreshWizardNavigation();
258
+ }
259
+ refreshWizardState() {
260
+ const visible = (this.session.renderState().tabs ?? []).flatMap((tab, index) => tab.isVisible ? [index] : []);
261
+ this.wizardVisibleIndices.set(visible);
262
+ this.restoreWizardNavigation();
263
+ if (!visible.includes(this.wizardIndex()) && visible.length) this.wizardIndex.set(visible[0]);
264
+ this.wizardDrafts.set(this.options.wizard.steps.map((_, index) => Object.keys(this.wizardState.project(this.session.formGroup, this.wizardPaths(index), true)).length > 0));
265
+ this.refreshWizardNavigation();
266
+ }
267
+ static ɵfac = i0.ɵɵngDeclareFactory({
268
+ minVersion: "12.0.0",
269
+ version: "22.2.1",
270
+ ngImport: i0,
271
+ type: EntityWizardComponent,
272
+ deps: [],
273
+ target: i0.ɵɵFactoryTarget.Component
274
+ });
275
+ static ɵcmp = i0.ɵɵngDeclareComponent({
276
+ minVersion: "17.0.0",
277
+ version: "22.2.1",
278
+ type: EntityWizardComponent,
279
+ isStandalone: true,
280
+ selector: "ame-entity-wizard",
281
+ inputs: {
282
+ options: "options",
283
+ displayMode: "displayMode"
284
+ },
285
+ outputs: {
286
+ result: "result",
287
+ closeRequest: "closeRequest"
288
+ },
289
+ providers: [
290
+ EntityFormSession,
291
+ EditEntityConditionalStateService,
292
+ EditEntityFormBuilderService,
293
+ EditEntityRenderService,
294
+ EditEntitySubmitValueService
295
+ ],
296
+ viewQueries: [{
297
+ propertyName: "wizardStepper",
298
+ first: true,
299
+ predicate: MatStepper,
300
+ descendants: true
301
+ }],
302
+ usesOnChanges: true,
303
+ ngImport: i0,
304
+ template: "<!-- @format -->\n\n@if (session.loading) {\n <div class=\"edit-entity-loading\">\n <mat-spinner [diameter]=\"72\" />\n <span>{{ session.loadingLabel }}</span>\n </div>\n} @else if (session.errorLabel) {\n <div class=\"edit-entity-empty-state\">\n <mat-icon>info</mat-icon>\n <span>{{ session.errorLabel }}</span>\n\n @if (displayMode === \"dialog\") {\n <button mat-stroked-button type=\"button\" (click)=\"requestClose()\">\n {{ session.closeLabel }}\n </button>\n }\n </div>\n} @else {\n @if (\n session.title ||\n session.subtitle ||\n displayMode === \"dialog\" ||\n session.getActions(\"top\").length\n ) {\n <header\n class=\"edit-entity-title-bar\"\n [class.edit-entity-wizard-header]=\"options.wizard\"\n >\n @if (options.wizard?.icon) {\n <mat-icon>{{ options.wizard?.icon }}</mat-icon>\n }\n <div class=\"edit-entity-title-content\">\n @if (session.title) {\n <h2 [innerHTML]=\"session.title\"></h2>\n }\n @if (session.subtitle) {\n <p [innerHTML]=\"session.subtitle\"></p>\n }\n </div>\n\n <div class=\"edit-entity-title-actions\">\n @for (\n action of session.getActions(\"top\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.showJsonDataButton === true) {\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"session.intl.messages.showJson\"\n [attr.aria-label]=\"session.intl.messages.showJson\"\n (click)=\"session.openJsonDataDialog()\"\n >\n <mat-icon>file_json</mat-icon>\n </button>\n }\n\n @if (displayMode === \"dialog\") {\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"session.closeLabel\"\n (click)=\"requestClose()\"\n >\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </header>\n }\n\n <form class=\"edit-entity-form\" [formGroup]=\"session.formGroup\">\n <main class=\"edit-entity-content\">\n @if (options.wizard; as wizard) {\n <mat-stepper\n class=\"edit-entity-wizard\"\n orientation=\"horizontal\"\n labelPosition=\"end\"\n [linear]=\"wizardLinear()\"\n [selectedIndex]=\"wizardVisualIndex()\"\n (selectionChange)=\"selectWizardVisualStep($event.selectedIndex)\"\n >\n <!-- Material icon context.active also includes completed steps; selection follows the wizard index. -->\n <ng-template matStepperIcon=\"number\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"edit\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"done\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template #wizardStateIcon let-index=\"index\" let-active=\"active\">\n <span\n class=\"edit-entity-wizard-state-icon\"\n [class.edit-entity-wizard-state-icon--selected]=\"active\"\n [class.edit-entity-wizard-state-icon--completed]=\"\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n \"\n >\n @if (\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n ) {\n <mat-icon>check</mat-icon>\n } @else {\n {{ index + 1 }}\n }\n </span>\n </ng-template>\n @for (index of wizardVisibleIndices(); track index) {\n @let step = wizard.steps[index];\n <mat-step\n [optional]=\"false\"\n [stepControl]=\"wizardNavigationControls[index]\"\n [completed]=\"\n !wizardNavigationBlocked() && wizardCompleted()[index]\n \"\n [editable]=\"\n !wizardNavigationBlocked() &&\n (index === 0 || !!options.record?._id)\n \"\n >\n <ng-template matStepLabel>\n <div\n class=\"edit-entity-wizard-step-label\"\n [class.edit-entity-wizard-step-label--selected]=\"\n index === wizardIndex()\n \"\n >\n <span class=\"edit-entity-wizard-step-name\">{{\n step.label\n }}</span>\n @if (wizardDrafts()[index]) {\n <span\n class=\"edit-entity-wizard-unsaved\"\n [title]=\"\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n \"\n >{{\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n }}</span\n >\n }\n </div>\n </ng-template>\n <ng-template matStepContent>\n @if (index === wizardIndex()) {\n <header class=\"edit-entity-wizard-content-heading\">\n <div>\n <h3>{{ step.label }}</h3>\n @if (step.description) {\n <p>{{ step.description }}</p>\n }\n </div>\n @if (step.optional && index > 0) {\n <span class=\"edit-entity-wizard-optional\">{{\n wizard.optionalLabel ||\n session.intl.messages.wizardOptional\n }}</span>\n }\n </header>\n <ame-form-content\n [state]=\"\n session.renderState().tabs?.[index]?.renderState || {\n kind: 'empty',\n }\n \"\n [formGroup]=\"session.formGroup\"\n [options]=\"options\"\n [context]=\"session\"\n />\n }\n </ng-template>\n </mat-step>\n }\n </mat-stepper>\n }\n </main>\n </form>\n\n <footer\n class=\"edit-entity-actions\"\n [class.edit-entity-wizard-actions]=\"options.wizard\"\n >\n <div class=\"edit-entity-actions-left\">\n @for (\n action of session.getActions(\"bottom\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n </div>\n\n <div class=\"edit-entity-actions-right\">\n @for (\n action of session.getActions(\"bottom-end\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.wizard; as wizard) {\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"session.updating\"\n (click)=\"requestClose()\"\n >\n {{ wizardExitLabel }}\n </button>\n @if (wizardStep?.optional && options.record?._id) {\n <button\n mat-raised-button\n class=\"edit-entity-wizard-skip\"\n type=\"button\"\n [disabled]=\"wizardNavigationBlocked()\"\n (click)=\"skipWizardStep()\"\n >\n {{ wizard.skipLabel || session.intl.messages.wizardSkip }}\n </button>\n }\n <button\n mat-raised-button\n color=\"secondary\"\n type=\"button\"\n [disabled]=\"\n session.updating || (!wizardStep?.componentManaged && !wizardValid)\n \"\n (click)=\"wizardNext()\"\n >\n {{ wizardPrimaryLabel }}\n </button>\n }\n </div>\n </footer>\n\n @if (session.updating) {\n <div class=\"edit-entity-updating\">\n <mat-spinner [diameter]=\"72\" />\n </div>\n }\n}\n\n<ng-template #actionTemplate let-action=\"action\">\n @if (session.isRuntimeButtonAction(action)) {\n @if (action.buttonType === \"raised\") {\n <button\n class=\"action-btn\"\n mat-raised-button\n type=\"button\"\n [style.--action-color]=\"action.contrastColor\"\n [style.--action-background-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n } @else {\n <button\n class=\"action-btn\"\n mat-button\n type=\"button\"\n [style.--action-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n }\n } @else if (session.isRuntimeComponentAction(action)) {\n <ng-container\n *ngComponentOutlet=\"action.component; inputs: action.componentInputs\"\n ></ng-container>\n }\n</ng-template>\n",
305
+ styles: [":host{--edit-entity-background: var( --ngx-mat-edit-entity-dialog-background, var(--ngx-mat-extended-surface, #f4f6f8) );--edit-entity-border: var( --ngx-mat-edit-entity-dialog-border, var(--ngx-mat-extended-border, rgba(14, 43, 67, .12)) );--edit-entity-text: var( --ngx-mat-edit-entity-dialog-text, var(--ngx-mat-extended-text, #102f49) );--edit-entity-muted: var( --ngx-mat-edit-entity-dialog-muted, var(--ngx-mat-extended-muted, #61718a) );--edit-entity-primary: var(--ngx-mat-edit-entity-dialog-primary, #0f334d);position:relative;display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;color:var(--edit-entity-text)}.edit-entity-loading,.edit-entity-empty-state{display:flex;flex:1 1 auto;min-height:240px;align-items:center;justify-content:center;flex-direction:column;gap:var(--ngx-mat-extended-space-3, 16px);color:var(--edit-entity-primary)}.edit-entity-empty-state{padding:var(--ngx-mat-extended-space-4, 24px);color:var(--edit-entity-muted);text-align:center}.edit-entity-empty-state mat-icon{width:42px;height:42px;color:var(--edit-entity-primary);font-size:42px}.edit-entity-empty-state span,.edit-entity-empty-state strong{color:var(--edit-entity-text);font-size:15px;font-weight:700}.edit-entity-title-bar{display:flex;flex:0 0 auto;align-items:center;justify-content:start;gap:var(--ngx-mat-extended-space-3, 16px);min-height:48px;padding:var(--ngx-mat-extended-space-2, 8px);margin:0;border-bottom:1px solid var(--edit-entity-border);box-sizing:border-box;background:var(--ngx-mat-edit-entity-dialog-header-background, var(--ngx-mat-extended-surface, #0f334d));color:var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff))}.edit-entity-title-bar h2{margin:0;font-size:var(--mat-sys-title-medium-size, 18px);font-weight:700;line-height:1.2}.edit-entity-title-bar p{margin:var(--ngx-mat-extended-space-1, 4px) 0 0;color:var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff));font-size:var(--mat-sys-body-small-size, 13px)}.edit-entity-title-content{min-width:0;overflow:hidden}.edit-entity-title-content h2,.edit-entity-title-content p{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.edit-entity-title-actions{display:inline-flex;flex:0 0 auto;align-items:center;gap:var(--ngx-mat-extended-space-2, 8px);margin-left:auto}.edit-entity-title-actions .mat-mdc-icon-button{--mat-icon-button-icon-color: var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff))}.edit-entity-form,.edit-entity-content{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;max-height:none;margin:0;overflow:hidden;padding:0;background:var(--edit-entity-background);box-sizing:border-box}.edit-entity-actions{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:var(--ngx-mat-extended-space-2, 8px);min-height:54px;padding:var(--ngx-mat-extended-space-2, 8px);border-top:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-dialog-background, var(--ngx-mat-extended-surface, #ffffff));box-sizing:border-box}.edit-entity-actions-left,.edit-entity-actions-right{display:inline-flex;align-items:center;gap:var(--ngx-mat-extended-space-2, 8px);min-width:0}.edit-entity-updating{position:absolute;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;background:#fff9}.action-btn{--mat-button-text-label-text-color: var(--action-color);--mat-button-filled-label-text-color: var(--action-color);--mat-button-filled-container-color: var(--action-background-color);--mat-button-protected-label-text-color: var(--action-color);--mat-button-protected-container-color: var(--action-background-color)}:host ::ng-deep mat-form-field{--mat-form-field-outlined-disabled-input-text-color: var(--mat-form-field-outlined-input-text-color)}:host ::ng-deep mat-form-field .mat-mdc-chip.mat-mdc-chip-row{--mat-chip-disabled-container-opacity: 1;--mat-chip-elevated-disabled-container-color: var(--mat-chip-elevated-container-color)}@media(max-width:900px){.edit-entity-actions{align-items:stretch;flex-direction:column}.edit-entity-actions-left,.edit-entity-actions-right{justify-content:flex-end;width:100%}}.edit-entity-wizard-header{background:var(--edit-entity-primary);color:var(--ngx-mat-edit-entity-dialog-header-text, var(--mat-sys-on-primary))}.edit-entity-wizard{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0;background:transparent;--mat-stepper-header-selected-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary));--mat-stepper-header-selected-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-alert, var(--mat-sys-on-primary));--mat-stepper-header-selected-state-label-text-color: var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text));--mat-stepper-header-done-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));--mat-stepper-header-done-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary));--mat-stepper-header-edit-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));--mat-stepper-header-edit-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary));--mat-stepper-header-label-text-color: var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text));--mat-stepper-line-color: transparent;--mat-stepper-container-color: transparent;--mat-stepper-header-height: 56px;--mat-stepper-header-focus-state-layer-shape: 0;--mat-stepper-header-hover-state-layer-shape: 0}.edit-entity-wizard-optional{display:block;align-self:center;padding:2px 5px;border-radius:4px;font-size:11px}.edit-entity-wizard-optional{background:var(--ngx-mat-edit-entity-dialog-secondary, var(--mat-sys-secondary));color:var(--mat-sys-on-secondary)}.edit-entity-wizard-unsaved{position:absolute;inset-inline:8px;bottom:1px;display:block;overflow:hidden;color:var(--edit-entity-muted);font-size:10px;line-height:10px;text-overflow:ellipsis;white-space:nowrap}.edit-entity-wizard-skip{--mat-button-protected-container-color: var(--ngx-mat-edit-entity-dialog-secondary, var(--mat-sys-secondary));--mat-button-protected-label-text-color: var(--mat-sys-on-secondary)}.edit-entity-wizard-step-label{display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:112px;max-width:100%;white-space:normal;line-height:16px;text-align:start;color:var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text))}.edit-entity-wizard-step-label--selected{color:var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary))}.edit-entity-wizard-step-name{overflow-wrap:anywhere}.edit-entity-wizard-actions{flex-wrap:wrap}.edit-entity-wizard-actions .edit-entity-actions-left:empty{display:none}.edit-entity-wizard-actions .edit-entity-actions-right{flex:1 1 auto;flex-wrap:wrap;justify-content:flex-end}.edit-entity-wizard-actions .edit-entity-actions-right button{max-width:100%;white-space:normal}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-wrapper,:host ::ng-deep .edit-entity-wizard .mat-horizontal-content-container{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-header-container{flex:0 0 auto;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;border-bottom:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-wizard-toolbar-background, var(--mat-sys-surface))}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-header{flex:1 0 160px;min-width:160px;padding-inline:8px;box-sizing:border-box}:host ::ng-deep .edit-entity-wizard .mat-stepper-horizontal-line{display:none}:host ::ng-deep .edit-entity-wizard .mat-horizontal-content-container{padding:0}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-content.mat-horizontal-stepper-content-current{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;overflow:hidden}.edit-entity-wizard-content-heading{display:flex;flex:0 0 auto;align-items:flex-start;justify-content:space-between;gap:8px;padding:8px}.edit-entity-wizard-content-heading>div{min-width:0}.edit-entity-wizard-content-heading h3{margin:0;color:var(--edit-entity-text);font-size:16px;line-height:24px}.edit-entity-wizard-content-heading p{margin:4px 0 0;color:var(--edit-entity-muted);font-size:13px;line-height:18px}.edit-entity-wizard-content-heading .edit-entity-wizard-optional{flex:0 0 auto;align-self:flex-start;margin-block-start:2px}.edit-entity-wizard-state-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;box-sizing:border-box;border:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-wizard-toolbar-background, var(--mat-sys-surface));color:var(--edit-entity-muted)}.edit-entity-wizard-state-icon mat-icon{font-size:18px;width:18px;height:18px}.edit-entity-wizard-state-icon--completed{border-color:transparent;background:var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));color:var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary))}.edit-entity-wizard-state-icon--selected{border-color:transparent;background:var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary));color:var(--ngx-mat-edit-entity-wizard-on-alert, var(--mat-sys-on-primary))}\n"],
306
+ dependencies: [
307
+ {
308
+ kind: "ngmodule",
309
+ type: CommonModule
310
+ },
311
+ {
312
+ kind: "directive",
313
+ type: i1$1.NgComponentOutlet,
314
+ selector: "[ngComponentOutlet]",
315
+ inputs: [
316
+ "ngComponentOutlet",
317
+ "ngComponentOutletInputs",
318
+ "ngComponentOutletInjector",
319
+ "ngComponentOutletEnvironmentInjector",
320
+ "ngComponentOutletContent",
321
+ "ngComponentOutletNgModule"
322
+ ],
323
+ exportAs: ["ngComponentOutlet"]
324
+ },
325
+ {
326
+ kind: "directive",
327
+ type: i1$1.NgTemplateOutlet,
328
+ selector: "[ngTemplateOutlet]",
329
+ inputs: [
330
+ "ngTemplateOutletContext",
331
+ "ngTemplateOutlet",
332
+ "ngTemplateOutletInjector"
333
+ ]
334
+ },
335
+ {
336
+ kind: "ngmodule",
337
+ type: ReactiveFormsModule
338
+ },
339
+ {
340
+ kind: "directive",
341
+ type: i2.ɵNgNoValidate,
342
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
343
+ },
344
+ {
345
+ kind: "directive",
346
+ type: i2.NgControlStatusGroup,
347
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
348
+ },
349
+ {
350
+ kind: "directive",
351
+ type: i2.FormGroupDirective,
352
+ selector: "[formGroup]",
353
+ inputs: ["formGroup"],
354
+ outputs: ["ngSubmit"],
355
+ exportAs: ["ngForm"]
356
+ },
357
+ {
358
+ kind: "ngmodule",
359
+ type: MatButtonModule
360
+ },
361
+ {
362
+ kind: "component",
363
+ type: i3.MatButton,
364
+ selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ",
365
+ inputs: ["matButton"],
366
+ exportAs: ["matButton", "matAnchor"]
367
+ },
368
+ {
369
+ kind: "component",
370
+ type: i3.MatIconButton,
371
+ selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]",
372
+ exportAs: ["matButton", "matAnchor"]
373
+ },
374
+ {
375
+ kind: "ngmodule",
376
+ type: MatIconModule
377
+ },
378
+ {
379
+ kind: "component",
380
+ type: i4.MatIcon,
381
+ selector: "mat-icon",
382
+ inputs: [
383
+ "color",
384
+ "inline",
385
+ "svgIcon",
386
+ "fontSet",
387
+ "fontIcon"
388
+ ],
389
+ exportAs: ["matIcon"]
390
+ },
391
+ {
392
+ kind: "ngmodule",
393
+ type: MatProgressSpinnerModule
394
+ },
395
+ {
396
+ kind: "component",
397
+ type: i5.MatProgressSpinner,
398
+ selector: "mat-progress-spinner, mat-spinner",
399
+ inputs: [
400
+ "color",
401
+ "mode",
402
+ "value",
403
+ "diameter",
404
+ "strokeWidth"
405
+ ],
406
+ exportAs: ["matProgressSpinner"]
407
+ },
408
+ {
409
+ kind: "ngmodule",
410
+ type: MatTooltipModule
411
+ },
412
+ {
413
+ kind: "directive",
414
+ type: i6.MatTooltip,
415
+ selector: "[matTooltip]",
416
+ inputs: [
417
+ "matTooltipPosition",
418
+ "matTooltipPositionAtOrigin",
419
+ "matTooltipDisabled",
420
+ "matTooltipShowDelay",
421
+ "matTooltipHideDelay",
422
+ "matTooltipTouchGestures",
423
+ "matTooltip",
424
+ "matTooltipClass"
425
+ ],
426
+ exportAs: ["matTooltip"]
427
+ },
428
+ {
429
+ kind: "component",
430
+ type: FormContentComponent,
431
+ selector: "ame-form-content",
432
+ inputs: [
433
+ "state",
434
+ "formGroup",
435
+ "options",
436
+ "context"
437
+ ]
438
+ },
439
+ {
440
+ kind: "ngmodule",
441
+ type: MatStepperModule
442
+ },
443
+ {
444
+ kind: "component",
445
+ type: i7.MatStep,
446
+ selector: "mat-step",
447
+ inputs: ["color"],
448
+ exportAs: ["matStep"]
449
+ },
450
+ {
451
+ kind: "directive",
452
+ type: i7.MatStepLabel,
453
+ selector: "[matStepLabel]"
454
+ },
455
+ {
456
+ kind: "component",
457
+ type: i7.MatStepper,
458
+ selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]",
459
+ inputs: [
460
+ "disableRipple",
461
+ "color",
462
+ "labelPosition",
463
+ "headerPosition",
464
+ "aria-label",
465
+ "headerPrefix",
466
+ "animationDuration"
467
+ ],
468
+ outputs: ["animationDone"],
469
+ exportAs: [
470
+ "matStepper",
471
+ "matVerticalStepper",
472
+ "matHorizontalStepper"
473
+ ]
474
+ },
475
+ {
476
+ kind: "directive",
477
+ type: i7.MatStepperIcon,
478
+ selector: "ng-template[matStepperIcon]",
479
+ inputs: ["matStepperIcon"]
480
+ },
481
+ {
482
+ kind: "directive",
483
+ type: i7.MatStepContent,
484
+ selector: "ng-template[matStepContent]"
485
+ }
486
+ ],
487
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
488
+ });
489
+ };
490
+ i0.ɵɵngDeclareClassMetadata({
491
+ minVersion: "12.0.0",
492
+ version: "22.2.1",
493
+ ngImport: i0,
494
+ type: EntityWizardComponent,
495
+ decorators: [{
496
+ type: Component,
497
+ args: [{
498
+ selector: "ame-entity-wizard",
499
+ imports: [
500
+ CommonModule,
501
+ ReactiveFormsModule,
502
+ MatButtonModule,
503
+ MatIconModule,
504
+ MatProgressSpinnerModule,
505
+ MatTooltipModule,
506
+ FormContentComponent,
507
+ MatStepperModule
508
+ ],
509
+ providers: [
510
+ EntityFormSession,
511
+ EditEntityConditionalStateService,
512
+ EditEntityFormBuilderService,
513
+ EditEntityRenderService,
514
+ EditEntitySubmitValueService
515
+ ],
516
+ changeDetection: ChangeDetectionStrategy.OnPush,
517
+ template: "<!-- @format -->\n\n@if (session.loading) {\n <div class=\"edit-entity-loading\">\n <mat-spinner [diameter]=\"72\" />\n <span>{{ session.loadingLabel }}</span>\n </div>\n} @else if (session.errorLabel) {\n <div class=\"edit-entity-empty-state\">\n <mat-icon>info</mat-icon>\n <span>{{ session.errorLabel }}</span>\n\n @if (displayMode === \"dialog\") {\n <button mat-stroked-button type=\"button\" (click)=\"requestClose()\">\n {{ session.closeLabel }}\n </button>\n }\n </div>\n} @else {\n @if (\n session.title ||\n session.subtitle ||\n displayMode === \"dialog\" ||\n session.getActions(\"top\").length\n ) {\n <header\n class=\"edit-entity-title-bar\"\n [class.edit-entity-wizard-header]=\"options.wizard\"\n >\n @if (options.wizard?.icon) {\n <mat-icon>{{ options.wizard?.icon }}</mat-icon>\n }\n <div class=\"edit-entity-title-content\">\n @if (session.title) {\n <h2 [innerHTML]=\"session.title\"></h2>\n }\n @if (session.subtitle) {\n <p [innerHTML]=\"session.subtitle\"></p>\n }\n </div>\n\n <div class=\"edit-entity-title-actions\">\n @for (\n action of session.getActions(\"top\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.showJsonDataButton === true) {\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"session.intl.messages.showJson\"\n [attr.aria-label]=\"session.intl.messages.showJson\"\n (click)=\"session.openJsonDataDialog()\"\n >\n <mat-icon>file_json</mat-icon>\n </button>\n }\n\n @if (displayMode === \"dialog\") {\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"session.closeLabel\"\n (click)=\"requestClose()\"\n >\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </header>\n }\n\n <form class=\"edit-entity-form\" [formGroup]=\"session.formGroup\">\n <main class=\"edit-entity-content\">\n @if (options.wizard; as wizard) {\n <mat-stepper\n class=\"edit-entity-wizard\"\n orientation=\"horizontal\"\n labelPosition=\"end\"\n [linear]=\"wizardLinear()\"\n [selectedIndex]=\"wizardVisualIndex()\"\n (selectionChange)=\"selectWizardVisualStep($event.selectedIndex)\"\n >\n <!-- Material icon context.active also includes completed steps; selection follows the wizard index. -->\n <ng-template matStepperIcon=\"number\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"edit\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"done\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template #wizardStateIcon let-index=\"index\" let-active=\"active\">\n <span\n class=\"edit-entity-wizard-state-icon\"\n [class.edit-entity-wizard-state-icon--selected]=\"active\"\n [class.edit-entity-wizard-state-icon--completed]=\"\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n \"\n >\n @if (\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n ) {\n <mat-icon>check</mat-icon>\n } @else {\n {{ index + 1 }}\n }\n </span>\n </ng-template>\n @for (index of wizardVisibleIndices(); track index) {\n @let step = wizard.steps[index];\n <mat-step\n [optional]=\"false\"\n [stepControl]=\"wizardNavigationControls[index]\"\n [completed]=\"\n !wizardNavigationBlocked() && wizardCompleted()[index]\n \"\n [editable]=\"\n !wizardNavigationBlocked() &&\n (index === 0 || !!options.record?._id)\n \"\n >\n <ng-template matStepLabel>\n <div\n class=\"edit-entity-wizard-step-label\"\n [class.edit-entity-wizard-step-label--selected]=\"\n index === wizardIndex()\n \"\n >\n <span class=\"edit-entity-wizard-step-name\">{{\n step.label\n }}</span>\n @if (wizardDrafts()[index]) {\n <span\n class=\"edit-entity-wizard-unsaved\"\n [title]=\"\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n \"\n >{{\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n }}</span\n >\n }\n </div>\n </ng-template>\n <ng-template matStepContent>\n @if (index === wizardIndex()) {\n <header class=\"edit-entity-wizard-content-heading\">\n <div>\n <h3>{{ step.label }}</h3>\n @if (step.description) {\n <p>{{ step.description }}</p>\n }\n </div>\n @if (step.optional && index > 0) {\n <span class=\"edit-entity-wizard-optional\">{{\n wizard.optionalLabel ||\n session.intl.messages.wizardOptional\n }}</span>\n }\n </header>\n <ame-form-content\n [state]=\"\n session.renderState().tabs?.[index]?.renderState || {\n kind: 'empty',\n }\n \"\n [formGroup]=\"session.formGroup\"\n [options]=\"options\"\n [context]=\"session\"\n />\n }\n </ng-template>\n </mat-step>\n }\n </mat-stepper>\n }\n </main>\n </form>\n\n <footer\n class=\"edit-entity-actions\"\n [class.edit-entity-wizard-actions]=\"options.wizard\"\n >\n <div class=\"edit-entity-actions-left\">\n @for (\n action of session.getActions(\"bottom\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n </div>\n\n <div class=\"edit-entity-actions-right\">\n @for (\n action of session.getActions(\"bottom-end\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.wizard; as wizard) {\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"session.updating\"\n (click)=\"requestClose()\"\n >\n {{ wizardExitLabel }}\n </button>\n @if (wizardStep?.optional && options.record?._id) {\n <button\n mat-raised-button\n class=\"edit-entity-wizard-skip\"\n type=\"button\"\n [disabled]=\"wizardNavigationBlocked()\"\n (click)=\"skipWizardStep()\"\n >\n {{ wizard.skipLabel || session.intl.messages.wizardSkip }}\n </button>\n }\n <button\n mat-raised-button\n color=\"secondary\"\n type=\"button\"\n [disabled]=\"\n session.updating || (!wizardStep?.componentManaged && !wizardValid)\n \"\n (click)=\"wizardNext()\"\n >\n {{ wizardPrimaryLabel }}\n </button>\n }\n </div>\n </footer>\n\n @if (session.updating) {\n <div class=\"edit-entity-updating\">\n <mat-spinner [diameter]=\"72\" />\n </div>\n }\n}\n\n<ng-template #actionTemplate let-action=\"action\">\n @if (session.isRuntimeButtonAction(action)) {\n @if (action.buttonType === \"raised\") {\n <button\n class=\"action-btn\"\n mat-raised-button\n type=\"button\"\n [style.--action-color]=\"action.contrastColor\"\n [style.--action-background-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n } @else {\n <button\n class=\"action-btn\"\n mat-button\n type=\"button\"\n [style.--action-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n }\n } @else if (session.isRuntimeComponentAction(action)) {\n <ng-container\n *ngComponentOutlet=\"action.component; inputs: action.componentInputs\"\n ></ng-container>\n }\n</ng-template>\n",
518
+ styles: [":host{--edit-entity-background: var( --ngx-mat-edit-entity-dialog-background, var(--ngx-mat-extended-surface, #f4f6f8) );--edit-entity-border: var( --ngx-mat-edit-entity-dialog-border, var(--ngx-mat-extended-border, rgba(14, 43, 67, .12)) );--edit-entity-text: var( --ngx-mat-edit-entity-dialog-text, var(--ngx-mat-extended-text, #102f49) );--edit-entity-muted: var( --ngx-mat-edit-entity-dialog-muted, var(--ngx-mat-extended-muted, #61718a) );--edit-entity-primary: var(--ngx-mat-edit-entity-dialog-primary, #0f334d);position:relative;display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;color:var(--edit-entity-text)}.edit-entity-loading,.edit-entity-empty-state{display:flex;flex:1 1 auto;min-height:240px;align-items:center;justify-content:center;flex-direction:column;gap:var(--ngx-mat-extended-space-3, 16px);color:var(--edit-entity-primary)}.edit-entity-empty-state{padding:var(--ngx-mat-extended-space-4, 24px);color:var(--edit-entity-muted);text-align:center}.edit-entity-empty-state mat-icon{width:42px;height:42px;color:var(--edit-entity-primary);font-size:42px}.edit-entity-empty-state span,.edit-entity-empty-state strong{color:var(--edit-entity-text);font-size:15px;font-weight:700}.edit-entity-title-bar{display:flex;flex:0 0 auto;align-items:center;justify-content:start;gap:var(--ngx-mat-extended-space-3, 16px);min-height:48px;padding:var(--ngx-mat-extended-space-2, 8px);margin:0;border-bottom:1px solid var(--edit-entity-border);box-sizing:border-box;background:var(--ngx-mat-edit-entity-dialog-header-background, var(--ngx-mat-extended-surface, #0f334d));color:var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff))}.edit-entity-title-bar h2{margin:0;font-size:var(--mat-sys-title-medium-size, 18px);font-weight:700;line-height:1.2}.edit-entity-title-bar p{margin:var(--ngx-mat-extended-space-1, 4px) 0 0;color:var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff));font-size:var(--mat-sys-body-small-size, 13px)}.edit-entity-title-content{min-width:0;overflow:hidden}.edit-entity-title-content h2,.edit-entity-title-content p{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.edit-entity-title-actions{display:inline-flex;flex:0 0 auto;align-items:center;gap:var(--ngx-mat-extended-space-2, 8px);margin-left:auto}.edit-entity-title-actions .mat-mdc-icon-button{--mat-icon-button-icon-color: var(--ngx-mat-edit-entity-dialog-header-text, var(--ngx-mat-extended-text, #fff))}.edit-entity-form,.edit-entity-content{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;max-height:none;margin:0;overflow:hidden;padding:0;background:var(--edit-entity-background);box-sizing:border-box}.edit-entity-actions{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:var(--ngx-mat-extended-space-2, 8px);min-height:54px;padding:var(--ngx-mat-extended-space-2, 8px);border-top:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-dialog-background, var(--ngx-mat-extended-surface, #ffffff));box-sizing:border-box}.edit-entity-actions-left,.edit-entity-actions-right{display:inline-flex;align-items:center;gap:var(--ngx-mat-extended-space-2, 8px);min-width:0}.edit-entity-updating{position:absolute;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;background:#fff9}.action-btn{--mat-button-text-label-text-color: var(--action-color);--mat-button-filled-label-text-color: var(--action-color);--mat-button-filled-container-color: var(--action-background-color);--mat-button-protected-label-text-color: var(--action-color);--mat-button-protected-container-color: var(--action-background-color)}:host ::ng-deep mat-form-field{--mat-form-field-outlined-disabled-input-text-color: var(--mat-form-field-outlined-input-text-color)}:host ::ng-deep mat-form-field .mat-mdc-chip.mat-mdc-chip-row{--mat-chip-disabled-container-opacity: 1;--mat-chip-elevated-disabled-container-color: var(--mat-chip-elevated-container-color)}@media(max-width:900px){.edit-entity-actions{align-items:stretch;flex-direction:column}.edit-entity-actions-left,.edit-entity-actions-right{justify-content:flex-end;width:100%}}.edit-entity-wizard-header{background:var(--edit-entity-primary);color:var(--ngx-mat-edit-entity-dialog-header-text, var(--mat-sys-on-primary))}.edit-entity-wizard{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0;background:transparent;--mat-stepper-header-selected-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary));--mat-stepper-header-selected-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-alert, var(--mat-sys-on-primary));--mat-stepper-header-selected-state-label-text-color: var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text));--mat-stepper-header-done-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));--mat-stepper-header-done-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary));--mat-stepper-header-edit-state-icon-background-color: var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));--mat-stepper-header-edit-state-icon-foreground-color: var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary));--mat-stepper-header-label-text-color: var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text));--mat-stepper-line-color: transparent;--mat-stepper-container-color: transparent;--mat-stepper-header-height: 56px;--mat-stepper-header-focus-state-layer-shape: 0;--mat-stepper-header-hover-state-layer-shape: 0}.edit-entity-wizard-optional{display:block;align-self:center;padding:2px 5px;border-radius:4px;font-size:11px}.edit-entity-wizard-optional{background:var(--ngx-mat-edit-entity-dialog-secondary, var(--mat-sys-secondary));color:var(--mat-sys-on-secondary)}.edit-entity-wizard-unsaved{position:absolute;inset-inline:8px;bottom:1px;display:block;overflow:hidden;color:var(--edit-entity-muted);font-size:10px;line-height:10px;text-overflow:ellipsis;white-space:nowrap}.edit-entity-wizard-skip{--mat-button-protected-container-color: var(--ngx-mat-edit-entity-dialog-secondary, var(--mat-sys-secondary));--mat-button-protected-label-text-color: var(--mat-sys-on-secondary)}.edit-entity-wizard-step-label{display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:112px;max-width:100%;white-space:normal;line-height:16px;text-align:start;color:var(--ngx-mat-edit-entity-wizard-toolbar-text, var(--edit-entity-text))}.edit-entity-wizard-step-label--selected{color:var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary))}.edit-entity-wizard-step-name{overflow-wrap:anywhere}.edit-entity-wizard-actions{flex-wrap:wrap}.edit-entity-wizard-actions .edit-entity-actions-left:empty{display:none}.edit-entity-wizard-actions .edit-entity-actions-right{flex:1 1 auto;flex-wrap:wrap;justify-content:flex-end}.edit-entity-wizard-actions .edit-entity-actions-right button{max-width:100%;white-space:normal}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-wrapper,:host ::ng-deep .edit-entity-wizard .mat-horizontal-content-container{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-header-container{flex:0 0 auto;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;border-bottom:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-wizard-toolbar-background, var(--mat-sys-surface))}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-header{flex:1 0 160px;min-width:160px;padding-inline:8px;box-sizing:border-box}:host ::ng-deep .edit-entity-wizard .mat-stepper-horizontal-line{display:none}:host ::ng-deep .edit-entity-wizard .mat-horizontal-content-container{padding:0}:host ::ng-deep .edit-entity-wizard .mat-horizontal-stepper-content.mat-horizontal-stepper-content-current{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;overflow:hidden}.edit-entity-wizard-content-heading{display:flex;flex:0 0 auto;align-items:flex-start;justify-content:space-between;gap:8px;padding:8px}.edit-entity-wizard-content-heading>div{min-width:0}.edit-entity-wizard-content-heading h3{margin:0;color:var(--edit-entity-text);font-size:16px;line-height:24px}.edit-entity-wizard-content-heading p{margin:4px 0 0;color:var(--edit-entity-muted);font-size:13px;line-height:18px}.edit-entity-wizard-content-heading .edit-entity-wizard-optional{flex:0 0 auto;align-self:flex-start;margin-block-start:2px}.edit-entity-wizard-state-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;box-sizing:border-box;border:1px solid var(--edit-entity-border);background:var(--ngx-mat-edit-entity-wizard-toolbar-background, var(--mat-sys-surface));color:var(--edit-entity-muted)}.edit-entity-wizard-state-icon mat-icon{font-size:18px;width:18px;height:18px}.edit-entity-wizard-state-icon--completed{border-color:transparent;background:var(--ngx-mat-edit-entity-wizard-ok, var(--mat-sys-tertiary));color:var(--ngx-mat-edit-entity-wizard-on-ok, var(--mat-sys-on-tertiary))}.edit-entity-wizard-state-icon--selected{border-color:transparent;background:var(--ngx-mat-edit-entity-wizard-alert, var(--mat-sys-primary));color:var(--ngx-mat-edit-entity-wizard-on-alert, var(--mat-sys-on-primary))}\n"]
519
+ }]
520
+ }],
521
+ propDecorators: {
522
+ options: [{
523
+ type: Input,
524
+ args: [{ required: true }]
525
+ }],
526
+ displayMode: [{ type: Input }],
527
+ result: [{ type: Output }],
528
+ closeRequest: [{ type: Output }],
529
+ wizardStepper: [{
530
+ type: ViewChild,
531
+ args: [MatStepper]
532
+ }]
533
+ }
534
+ });
535
+ var EntityWizardDialog = class EntityWizardDialog extends EntityDialogKeyboardHost {
536
+ data;
537
+ editEntityComponent;
538
+ constructor(data, dialogRef, matDialog) {
539
+ super(dialogRef, matDialog);
540
+ this.data = data;
541
+ }
542
+ static ɵfac = i0.ɵɵngDeclareFactory({
543
+ minVersion: "12.0.0",
544
+ version: "22.2.1",
545
+ ngImport: i0,
546
+ type: EntityWizardDialog,
547
+ deps: [
548
+ { token: MAT_DIALOG_DATA },
549
+ { token: i1.MatDialogRef },
550
+ { token: i1.MatDialog }
551
+ ],
552
+ target: i0.ɵɵFactoryTarget.Component
553
+ });
554
+ static ɵcmp = i0.ɵɵngDeclareComponent({
555
+ minVersion: "14.0.0",
556
+ version: "22.2.1",
557
+ type: EntityWizardDialog,
558
+ isStandalone: true,
559
+ selector: "ame-entity-wizard-dialog",
560
+ viewQueries: [{
561
+ propertyName: "editEntityComponent",
562
+ first: true,
563
+ predicate: EntityWizardComponent,
564
+ descendants: true
565
+ }],
566
+ usesInheritance: true,
567
+ ngImport: i0,
568
+ template: "<ame-entity-wizard\n [options]=\"data\"\n displayMode=\"dialog\"\n (closeRequest)=\"close($event)\"\n/>\n",
569
+ styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}app-edit-entity-component{display:flex;flex:1 1 auto;height:auto;min-height:0;overflow:hidden}\n"],
570
+ dependencies: [{
571
+ kind: "component",
572
+ type: EntityWizardComponent,
573
+ selector: "ame-entity-wizard",
574
+ inputs: ["options", "displayMode"],
575
+ outputs: ["result", "closeRequest"]
576
+ }],
577
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
578
+ });
579
+ };
580
+ i0.ɵɵngDeclareClassMetadata({
581
+ minVersion: "12.0.0",
582
+ version: "22.2.1",
583
+ ngImport: i0,
584
+ type: EntityWizardDialog,
585
+ decorators: [{
586
+ type: Component,
587
+ args: [{
588
+ selector: "ame-entity-wizard-dialog",
589
+ imports: [EntityWizardComponent],
590
+ changeDetection: ChangeDetectionStrategy.OnPush,
591
+ template: "<ame-entity-wizard\n [options]=\"data\"\n displayMode=\"dialog\"\n (closeRequest)=\"close($event)\"\n/>\n",
592
+ styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}app-edit-entity-component{display:flex;flex:1 1 auto;height:auto;min-height:0;overflow:hidden}\n"]
593
+ }]
594
+ }],
595
+ ctorParameters: () => [
596
+ {
597
+ type: void 0,
598
+ decorators: [{
599
+ type: Inject,
600
+ args: [MAT_DIALOG_DATA]
601
+ }]
602
+ },
603
+ { type: i1.MatDialogRef },
604
+ { type: i1.MatDialog }
605
+ ],
606
+ propDecorators: { editEntityComponent: [{
607
+ type: ViewChild,
608
+ args: [EntityWizardComponent]
609
+ }] }
610
+ });
611
+ var CreateEntityWizardService = class CreateEntityWizardService {
612
+ launcher = inject(EntityDialogLauncher);
613
+ open(options, config = {}) {
614
+ if (!options.wizard?.steps.length || !("tabs" in options.displayOptions)) throw new Error("Use createEntityWizardOptions before opening a creation wizard");
615
+ return this.launcher.open(EntityWizardDialog, options, config);
616
+ }
617
+ static ɵfac = i0.ɵɵngDeclareFactory({
618
+ minVersion: "12.0.0",
619
+ version: "22.2.1",
620
+ ngImport: i0,
621
+ type: CreateEntityWizardService,
622
+ deps: [],
623
+ target: i0.ɵɵFactoryTarget.Injectable
624
+ });
625
+ static ɵprov = i0.ɵɵngDeclareInjectable({
626
+ minVersion: "12.0.0",
627
+ version: "22.2.1",
628
+ ngImport: i0,
629
+ type: CreateEntityWizardService,
630
+ providedIn: "root"
631
+ });
632
+ };
633
+ i0.ɵɵngDeclareClassMetadata({
634
+ minVersion: "12.0.0",
635
+ version: "22.2.1",
636
+ ngImport: i0,
637
+ type: CreateEntityWizardService,
638
+ decorators: [{
639
+ type: Injectable,
640
+ args: [{ providedIn: "root" }]
641
+ }]
642
+ });
643
+ function createEntityWizardOptions(options) {
644
+ if (!options.steps.length) throw new Error("A creation wizard requires at least one step");
645
+ const { steps, icon, nextLabel, createNextLabel, skipLabel, finishLaterLabel, finishLabel, optionalLabel, unsavedLabel, ...base } = options;
646
+ return {
647
+ ...base,
648
+ service: base.service ? {
649
+ ...base.service,
650
+ closeOnUpsert: false
651
+ } : void 0,
652
+ wizard: {
653
+ steps,
654
+ icon,
655
+ nextLabel,
656
+ createNextLabel,
657
+ skipLabel,
658
+ finishLaterLabel,
659
+ finishLabel,
660
+ optionalLabel,
661
+ unsavedLabel
662
+ },
663
+ displayOptions: { tabs: steps.map((step) => ({
664
+ id: step.id,
665
+ label: step.label,
666
+ icon: step.icon,
667
+ displayOptions: step.displayOptions
668
+ })) }
669
+ };
670
+ }
671
+ export { CreateEntityWizardService, CreateEntityWizardState, EntityWizardComponent, EntityWizardDialog, createEntityWizardOptions };
672
+
673
+ //# sourceMappingURL=eherve-angular-material-entity-wizard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eherve-angular-material-entity-wizard.mjs","names":["i1"],"sources":["../../../projects/entity-wizard/src/lib/create-entity-wizard-state.ts","../../../projects/entity-wizard/src/lib/entity-wizard.component.ts","../../../projects/entity-wizard/src/lib/entity-wizard.component.html","../../../projects/entity-wizard/src/lib/entity-wizard-dialog.component.ts","../../../projects/entity-wizard/src/lib/entity-wizard-dialog.component.html","../../../projects/entity-wizard/src/lib/create-entity-wizard.service.ts","../../../projects/entity-wizard/src/lib/create-entity-wizard.types.ts"],"sourcesContent":["import { AbstractControl, FormGroup } from \"@angular/forms\";\nimport {\n EditEntityFormGroup,\n isNonEditable,\n} from \"@eherve/angular-material-forms/core\";\nimport { cloneDeep, get, has, isEqual, set } from \"lodash-es\";\n\n/** Scoped persistence shared by the wizard engine; no host-specific model assumptions. */\nexport class CreateEntityWizardState {\n valid(form: EditEntityFormGroup, paths: string[]): boolean {\n return paths.every((path) => {\n const control = form.getControl(path);\n return (\n !control || control.disabled || (!control.pending && control.valid)\n );\n });\n }\n\n project(\n form: EditEntityFormGroup,\n paths: string[],\n changedOnly: boolean,\n always: string[] = [],\n ): Record<string, unknown> {\n const value: Record<string, unknown> = {};\n for (const path of new Set([...paths, ...always])) {\n const control = form.getControl(path);\n if (!control || isNonEditable(control)) continue;\n const current = control.getRawValue();\n const initial = this.read(form.initialValue, path);\n if (!changedOnly || always.includes(path) || !isEqual(current, initial))\n set(value, path, cloneDeep(current));\n }\n return value;\n }\n\n hydrate(\n form: EditEntityFormGroup,\n record: object,\n submitted: string[],\n ): void {\n const baseline = cloneDeep(form.initialValue);\n const visit = (control: AbstractControl, path: string): void => {\n if (control instanceof FormGroup) {\n for (const [key, child] of Object.entries(control.controls))\n visit(child, path ? `${path}.${key}` : key);\n return;\n }\n const saved = submitted.some(\n (name) => path === name || path.startsWith(`${name}.`),\n );\n const current = control.getRawValue();\n const draft = !isEqual(current, this.read(baseline, path));\n if (saved || !draft) {\n if (has(record, path))\n control.patchValue(cloneDeep(get(record, path)), {\n emitEvent: false,\n });\n if (Object.prototype.hasOwnProperty.call(form.controls, path))\n baseline[path] = cloneDeep(control.getRawValue());\n else set(baseline, path, cloneDeep(control.getRawValue()));\n control.markAsPristine();\n control.markAsUntouched();\n }\n };\n visit(form, \"\");\n form.setInitialValue(baseline);\n }\n\n private read(value: Record<string, unknown>, path: string): unknown {\n return Object.prototype.hasOwnProperty.call(value, path)\n ? value[path]\n : get(value, path);\n }\n}\n","import { CdkStepper } from \"@angular/cdk/stepper\";\nimport { CommonModule } from \"@angular/common\";\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n signal,\n SimpleChanges,\n ViewChild,\n} from \"@angular/core\";\nimport { FormControl, ReactiveFormsModule, Validators } from \"@angular/forms\";\nimport { MatButtonModule } from \"@angular/material/button\";\nimport { MatIconModule } from \"@angular/material/icon\";\nimport { MatProgressSpinnerModule } from \"@angular/material/progress-spinner\";\nimport { MatStepper, MatStepperModule } from \"@angular/material/stepper\";\nimport { MatTooltipModule } from \"@angular/material/tooltip\";\nimport {\n EditEntityConditionalStateService,\n EditEntityFormBuilderService,\n EditEntityRecord,\n EditEntityRenderService,\n EditEntitySubmitValueService,\n} from \"@eherve/angular-material-forms/core\";\nimport { EntityFormSession } from \"@eherve/angular-material-forms/editor\";\nimport { FormContentComponent } from \"@eherve/angular-material-forms/layout\";\nimport { cloneDeep, get, has, set } from \"lodash-es\";\nimport { CreateEntityWizardState } from \"./create-entity-wizard-state\";\nimport {\n CreateEntityWizardComponentOptions,\n CreateEntityWizardStep,\n} from \"./create-entity-wizard.types\";\n@Component({\n selector: \"ame-entity-wizard\",\n imports: [\n CommonModule,\n ReactiveFormsModule,\n MatButtonModule,\n MatIconModule,\n MatProgressSpinnerModule,\n MatTooltipModule,\n FormContentComponent,\n MatStepperModule,\n ],\n providers: [\n EntityFormSession,\n EditEntityConditionalStateService,\n EditEntityFormBuilderService,\n EditEntityRenderService,\n EditEntitySubmitValueService,\n ],\n templateUrl: \"./entity-wizard.component.html\",\n styleUrl: \"./entity-wizard.component.scss\",\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EntityWizardComponent<\n TRecord extends EditEntityRecord = EditEntityRecord,\n >\n implements OnInit, OnChanges, OnDestroy\n{\n @Input({ required: true })\n options!: CreateEntityWizardComponentOptions<TRecord>;\n @Input() displayMode: \"page\" | \"dialog\" = \"page\";\n readonly session = inject<EntityFormSession<TRecord>>(EntityFormSession);\n @Output() readonly result = this.session.result;\n @Output() readonly closeRequest = this.session.closeRequest;\n ngOnInit(): void {\n this.session.options = this.options;\n this.session.displayMode = this.displayMode;\n this.session.policy = {\n valid: () => this.wizardValid,\n value: (create) => this.wizardValue(create),\n patch: (record, value) => this.patchWizardRecord(record, value),\n saved: (record, value) => this.hydrateWizard(record, value),\n hydrate: (record) =>\n this.wizardState.hydrate(this.session.formGroup, record, []),\n refresh: () => this.refreshWizardState(),\n navigate: () => this.restoreWizardNavigation(),\n reset: () => {\n this.wizardIndex.set(0);\n this.wizardCompleted.set(this.options.wizard.steps.map(() => false));\n this.wizardVisibleIndices.set([]);\n this.wizardNavigationControls = [];\n },\n componentUpdated: () => this.completeWizardStep(),\n beforeClose: () =>\n this.session.formGroup.setInitialValue(\n this.session.formGroup.initialValue,\n ),\n stickyResult: true,\n };\n void this.session.initialize();\n }\n ngOnChanges(changes: SimpleChanges): void {\n if (changes[\"options\"] && !changes[\"options\"].firstChange) {\n this.session.options = this.options;\n void this.session.initialize();\n }\n }\n ngOnDestroy(): void {\n this.session.destroy();\n }\n requestClose(): Promise<void> {\n return this.session.requestClose();\n }\n refreshPresentation(): void {\n this.session.refreshPresentation();\n }\n submitFromKeyboard(): boolean {\n if (\n !this.options.validateOnEnter ||\n this.session.loading ||\n this.session.updating\n )\n return false;\n void this.wizardNext();\n return true;\n }\n readonly wizardIndex = signal(0);\n readonly wizardDrafts = signal<boolean[]>([]);\n readonly wizardCompleted = signal<boolean[]>([]);\n readonly wizardVisibleIndices = signal<number[]>([]);\n readonly wizardVisualIndex = computed(() =>\n Math.max(0, this.wizardVisibleIndices().indexOf(this.wizardIndex())),\n );\n readonly wizardNavigationBlocked = signal(false);\n readonly wizardLinear = signal(true);\n wizardNavigationControls: FormControl<boolean>[] = [];\n @ViewChild(MatStepper) wizardStepper?: CdkStepper;\n private readonly wizardState = new CreateEntityWizardState();\n get wizardStep(): CreateEntityWizardStep<TRecord> | undefined {\n return this.options?.wizard?.steps[this.wizardIndex()];\n }\n\n get wizardLast(): boolean {\n return this.wizardIndex() === this.wizardVisibleIndices().at(-1);\n }\n\n get wizardPrimaryLabel(): string {\n const wizard = this.options.wizard;\n if (!this.options.record?._id)\n return (\n wizard?.createNextLabel ?? this.session.intl.messages.wizardCreateNext\n );\n return this.wizardLast\n ? (wizard?.finishLabel ?? this.session.intl.messages.wizardFinish)\n : (wizard?.nextLabel ?? this.session.intl.messages.wizardNext);\n }\n\n get wizardExitLabel(): string {\n return this.options.record?._id\n ? (this.options.wizard?.finishLaterLabel ??\n this.session.intl.messages.wizardFinishLater)\n : this.session.cancelLabel;\n }\n\n selectWizardVisualStep(position: number): void {\n const index = this.wizardVisibleIndices()[position];\n if (index !== undefined) this.selectWizardStep(index);\n }\n\n private advanceWizard(): void {\n const next = this.wizardVisibleIndices()[this.wizardVisualIndex() + 1];\n if (next !== undefined) {\n this.wizardIndex.set(next);\n this.publishWizardNavigation();\n this.refreshWizardNavigation();\n }\n }\n\n private completeWizardStep(): void {\n const completed = [...this.wizardCompleted()];\n completed[this.wizardIndex()] = true;\n this.wizardCompleted.set(completed);\n this.refreshWizardNavigation();\n }\n\n private refreshWizardNavigation(): void {\n if (!this.options?.wizard) return;\n const blocked = this.session.updating || this.wizardPending;\n this.wizardNavigationBlocked.set(blocked);\n const linear = !this.options.record?._id || blocked;\n this.wizardLinear.set(linear);\n if (this.wizardStepper) this.wizardStepper.linear = linear;\n const count = this.options.wizard.steps.length;\n if (this.wizardNavigationControls.length !== count) {\n this.wizardNavigationControls = Array.from(\n { length: count },\n () =>\n new FormControl(false, {\n nonNullable: true,\n validators: Validators.requiredTrue,\n }),\n );\n }\n for (const [index, control] of this.wizardNavigationControls.entries()) {\n control.setValue(\n !blocked && (index !== 0 || !!this.options.record?._id),\n { emitEvent: false },\n );\n }\n // CDK checks these public step properties synchronously when a header is selected.\n // Synchronize immediately, including before Angular's next rendering pass.\n this.wizardStepper?.steps.forEach((step, position) => {\n const index = this.wizardVisibleIndices()[position];\n step.optional = false;\n step.interacted = true;\n step.completed = !blocked && !!this.wizardCompleted()[index];\n step.editable = !blocked && (index === 0 || !!this.options.record?._id);\n });\n }\n\n get wizardValid(): boolean {\n return this.wizardState.valid(this.session.formGroup, [\n ...this.wizardPaths(),\n ...this.wizardAlwaysPaths(),\n ]);\n }\n\n get wizardPending(): boolean {\n return [...this.wizardPaths(), ...this.wizardAlwaysPaths()].some(\n (path) => this.session.formGroup.getControl(path)?.pending,\n );\n }\n\n get wizardUnsaved(): boolean {\n return (\n Object.keys(\n this.wizardState.project(\n this.session.formGroup,\n this.wizardPaths(),\n true,\n ),\n ).length > 0\n );\n }\n\n selectWizardStep(index: number): void {\n if (\n this.session.updating ||\n this.wizardPending ||\n !this.options.wizard ||\n index < 0 ||\n index >= this.options.wizard.steps.length ||\n !this.wizardVisibleIndices().includes(index)\n )\n return;\n if (index > 0 && !this.options.record?._id) return;\n this.wizardIndex.set(index);\n this.publishWizardNavigation();\n this.refreshWizardNavigation();\n }\n\n skipWizardStep(): void {\n if (\n this.session.updating ||\n this.wizardPending ||\n !this.wizardStep?.optional ||\n !this.options.record?._id\n )\n return;\n if (this.wizardLast) void this.requestClose();\n else this.advanceWizard();\n }\n\n async wizardNext(): Promise<void> {\n if (this.session.updating || !this.options.wizard) return;\n const isCurrent = this.session.captureCurrentOperation();\n if (!this.wizardStep?.componentManaged) {\n for (const path of this.wizardPaths())\n this.session.formGroup.getControl(path)?.markAllAsTouched();\n if (!this.wizardValid) return;\n const previous = this.session.savedResult;\n if (!this.options.record?._id) await this.session.create();\n else if (this.wizardUnsaved) await this.session.update();\n else {\n this.completeWizardStep();\n if (this.wizardLast) await this.requestClose();\n else this.advanceWizard();\n return;\n }\n if (!isCurrent() || previous === this.session.savedResult) return;\n }\n if (this.wizardLast) await this.requestClose();\n else this.advanceWizard();\n }\n\n private wizardPaths(index = this.wizardIndex()): string[] {\n const display = this.options?.displayOptions;\n const tab = display && \"tabs\" in display ? display.tabs[index] : undefined;\n const names = tab\n ? this.session.renderService\n .collectFields(tab.displayOptions)\n .flatMap((field) =>\n field.kind === \"separator\" ||\n field.kind === \"metadata\" ||\n field.kind === \"display\"\n ? []\n : [field.name],\n )\n : [];\n return [\n ...new Set([\n ...names,\n ...(this.options.wizard?.steps[index]?.fields ?? []),\n ]),\n ];\n }\n\n private wizardAlwaysPaths(): string[] {\n return this.session.fields.flatMap((field) =>\n field.kind !== \"separator\" &&\n field.kind !== \"metadata\" &&\n field.kind !== \"display\" &&\n field.alwaysInUpsert\n ? [field.name]\n : [],\n );\n }\n\n private wizardValue(create: boolean): Record<string, unknown> {\n const paths = this.wizardPaths();\n const always = this.wizardAlwaysPaths();\n const value = this.wizardState.project(\n this.session.formGroup,\n paths,\n !create,\n always,\n );\n if (create)\n for (const path of [...paths, ...always]) {\n if (\n !this.session.formGroup.getControl(path) &&\n has(this.options.defaultValues, path)\n )\n set(value, path, cloneDeep(get(this.options.defaultValues, path)));\n }\n return value;\n }\n\n private patchWizardRecord(\n record: TRecord,\n value: Record<string, unknown>,\n ): TRecord {\n const updated = cloneDeep(record);\n for (const path of [...this.wizardPaths(), ...this.wizardAlwaysPaths()]) {\n if (has(value, path)) set(updated, path, cloneDeep(get(value, path)));\n }\n return updated;\n }\n\n private async hydrateWizard(\n record: TRecord,\n value: Record<string, unknown>,\n ): Promise<void> {\n const paths = [...this.wizardPaths(), ...this.wizardAlwaysPaths()].filter(\n (path) => has(value, path),\n );\n this.wizardState.hydrate(this.session.formGroup, record, paths);\n this.completeWizardStep();\n this.session.updated = this.session.formGroup.isUpdated;\n this.session.refreshAllowedActions();\n this.session.refreshRuntimeState();\n }\n\n private publishWizardNavigation(): void {\n this.session.navigationController.selectTab(this.wizardVisualIndex());\n }\n\n private restoreWizardNavigation(): void {\n const index =\n this.wizardVisibleIndices()[\n this.session.navigationController.selectedIndex\n ];\n if (\n index !== undefined &&\n !this.session.updating &&\n !this.wizardPending &&\n (index === 0 || !!this.options.record?._id)\n )\n this.wizardIndex.set(index);\n this.publishWizardNavigation();\n this.refreshWizardNavigation();\n }\n\n private refreshWizardState(): void {\n const visible = (this.session.renderState().tabs ?? []).flatMap(\n (tab, index) => (tab.isVisible ? [index] : []),\n );\n this.wizardVisibleIndices.set(visible);\n this.restoreWizardNavigation();\n if (!visible.includes(this.wizardIndex()) && visible.length)\n this.wizardIndex.set(visible[0]);\n this.wizardDrafts.set(\n this.options.wizard.steps.map(\n (_, index) =>\n Object.keys(\n this.wizardState.project(\n this.session.formGroup,\n this.wizardPaths(index),\n true,\n ),\n ).length > 0,\n ),\n );\n this.refreshWizardNavigation();\n }\n}\n","<!-- @format -->\n\n@if (session.loading) {\n <div class=\"edit-entity-loading\">\n <mat-spinner [diameter]=\"72\" />\n <span>{{ session.loadingLabel }}</span>\n </div>\n} @else if (session.errorLabel) {\n <div class=\"edit-entity-empty-state\">\n <mat-icon>info</mat-icon>\n <span>{{ session.errorLabel }}</span>\n\n @if (displayMode === \"dialog\") {\n <button mat-stroked-button type=\"button\" (click)=\"requestClose()\">\n {{ session.closeLabel }}\n </button>\n }\n </div>\n} @else {\n @if (\n session.title ||\n session.subtitle ||\n displayMode === \"dialog\" ||\n session.getActions(\"top\").length\n ) {\n <header\n class=\"edit-entity-title-bar\"\n [class.edit-entity-wizard-header]=\"options.wizard\"\n >\n @if (options.wizard?.icon) {\n <mat-icon>{{ options.wizard?.icon }}</mat-icon>\n }\n <div class=\"edit-entity-title-content\">\n @if (session.title) {\n <h2 [innerHTML]=\"session.title\"></h2>\n }\n @if (session.subtitle) {\n <p [innerHTML]=\"session.subtitle\"></p>\n }\n </div>\n\n <div class=\"edit-entity-title-actions\">\n @for (\n action of session.getActions(\"top\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.showJsonDataButton === true) {\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"session.intl.messages.showJson\"\n [attr.aria-label]=\"session.intl.messages.showJson\"\n (click)=\"session.openJsonDataDialog()\"\n >\n <mat-icon>file_json</mat-icon>\n </button>\n }\n\n @if (displayMode === \"dialog\") {\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"session.closeLabel\"\n (click)=\"requestClose()\"\n >\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </header>\n }\n\n <form class=\"edit-entity-form\" [formGroup]=\"session.formGroup\">\n <main class=\"edit-entity-content\">\n @if (options.wizard; as wizard) {\n <mat-stepper\n class=\"edit-entity-wizard\"\n orientation=\"horizontal\"\n labelPosition=\"end\"\n [linear]=\"wizardLinear()\"\n [selectedIndex]=\"wizardVisualIndex()\"\n (selectionChange)=\"selectWizardVisualStep($event.selectedIndex)\"\n >\n <!-- Material icon context.active also includes completed steps; selection follows the wizard index. -->\n <ng-template matStepperIcon=\"number\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"edit\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template matStepperIcon=\"done\" let-index=\"index\">\n <ng-container\n *ngTemplateOutlet=\"\n wizardStateIcon;\n context: { index, active: index === wizardVisualIndex() }\n \"\n ></ng-container>\n </ng-template>\n <ng-template #wizardStateIcon let-index=\"index\" let-active=\"active\">\n <span\n class=\"edit-entity-wizard-state-icon\"\n [class.edit-entity-wizard-state-icon--selected]=\"active\"\n [class.edit-entity-wizard-state-icon--completed]=\"\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n \"\n >\n @if (\n !active && wizardCompleted()[wizardVisibleIndices()[index]]\n ) {\n <mat-icon>check</mat-icon>\n } @else {\n {{ index + 1 }}\n }\n </span>\n </ng-template>\n @for (index of wizardVisibleIndices(); track index) {\n @let step = wizard.steps[index];\n <mat-step\n [optional]=\"false\"\n [stepControl]=\"wizardNavigationControls[index]\"\n [completed]=\"\n !wizardNavigationBlocked() && wizardCompleted()[index]\n \"\n [editable]=\"\n !wizardNavigationBlocked() &&\n (index === 0 || !!options.record?._id)\n \"\n >\n <ng-template matStepLabel>\n <div\n class=\"edit-entity-wizard-step-label\"\n [class.edit-entity-wizard-step-label--selected]=\"\n index === wizardIndex()\n \"\n >\n <span class=\"edit-entity-wizard-step-name\">{{\n step.label\n }}</span>\n @if (wizardDrafts()[index]) {\n <span\n class=\"edit-entity-wizard-unsaved\"\n [title]=\"\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n \"\n >{{\n wizard.unsavedLabel ||\n session.intl.messages.wizardUnsaved\n }}</span\n >\n }\n </div>\n </ng-template>\n <ng-template matStepContent>\n @if (index === wizardIndex()) {\n <header class=\"edit-entity-wizard-content-heading\">\n <div>\n <h3>{{ step.label }}</h3>\n @if (step.description) {\n <p>{{ step.description }}</p>\n }\n </div>\n @if (step.optional && index > 0) {\n <span class=\"edit-entity-wizard-optional\">{{\n wizard.optionalLabel ||\n session.intl.messages.wizardOptional\n }}</span>\n }\n </header>\n <ame-form-content\n [state]=\"\n session.renderState().tabs?.[index]?.renderState || {\n kind: 'empty',\n }\n \"\n [formGroup]=\"session.formGroup\"\n [options]=\"options\"\n [context]=\"session\"\n />\n }\n </ng-template>\n </mat-step>\n }\n </mat-stepper>\n }\n </main>\n </form>\n\n <footer\n class=\"edit-entity-actions\"\n [class.edit-entity-wizard-actions]=\"options.wizard\"\n >\n <div class=\"edit-entity-actions-left\">\n @for (\n action of session.getActions(\"bottom\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n </div>\n\n <div class=\"edit-entity-actions-right\">\n @for (\n action of session.getActions(\"bottom-end\");\n track session.trackRuntimeAction($index, action)\n ) {\n <ng-container\n *ngTemplateOutlet=\"actionTemplate; context: { action }\"\n ></ng-container>\n }\n\n @if (options.wizard; as wizard) {\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"session.updating\"\n (click)=\"requestClose()\"\n >\n {{ wizardExitLabel }}\n </button>\n @if (wizardStep?.optional && options.record?._id) {\n <button\n mat-raised-button\n class=\"edit-entity-wizard-skip\"\n type=\"button\"\n [disabled]=\"wizardNavigationBlocked()\"\n (click)=\"skipWizardStep()\"\n >\n {{ wizard.skipLabel || session.intl.messages.wizardSkip }}\n </button>\n }\n <button\n mat-raised-button\n color=\"secondary\"\n type=\"button\"\n [disabled]=\"\n session.updating || (!wizardStep?.componentManaged && !wizardValid)\n \"\n (click)=\"wizardNext()\"\n >\n {{ wizardPrimaryLabel }}\n </button>\n }\n </div>\n </footer>\n\n @if (session.updating) {\n <div class=\"edit-entity-updating\">\n <mat-spinner [diameter]=\"72\" />\n </div>\n }\n}\n\n<ng-template #actionTemplate let-action=\"action\">\n @if (session.isRuntimeButtonAction(action)) {\n @if (action.buttonType === \"raised\") {\n <button\n class=\"action-btn\"\n mat-raised-button\n type=\"button\"\n [style.--action-color]=\"action.contrastColor\"\n [style.--action-background-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n } @else {\n <button\n class=\"action-btn\"\n mat-button\n type=\"button\"\n [style.--action-color]=\"action.color\"\n [disabled]=\"session.updating || action.isDisabled\"\n [matTooltip]=\"action.tooltip || ''\"\n (click)=\"session.handleAction(action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span [innerHTML]=\"action.label\"></span>\n </button>\n }\n } @else if (session.isRuntimeComponentAction(action)) {\n <ng-container\n *ngComponentOutlet=\"action.component; inputs: action.componentInputs\"\n ></ng-container>\n }\n</ng-template>\n","import {\n ChangeDetectionStrategy,\n Component,\n Inject,\n ViewChild,\n} from \"@angular/core\";\nimport {\n MAT_DIALOG_DATA,\n MatDialog,\n MatDialogRef,\n} from \"@angular/material/dialog\";\nimport {\n EditEntityRecord,\n EditEntityDialogResult,\n} from \"@eherve/angular-material-forms/core\";\nimport { EntityDialogKeyboardHost } from \"@eherve/angular-material-forms/editor\";\nimport { EntityWizardComponent } from \"./entity-wizard.component\";\nimport { CreateEntityWizardComponentOptions } from \"./create-entity-wizard.types\";\n@Component({\n selector: \"ame-entity-wizard-dialog\",\n imports: [EntityWizardComponent],\n templateUrl: \"./entity-wizard-dialog.component.html\",\n styleUrl: \"./entity-wizard-dialog.component.scss\",\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EntityWizardDialog<\n TRecord extends EditEntityRecord = EditEntityRecord,\n> extends EntityDialogKeyboardHost<TRecord, EntityWizardDialog<TRecord>> {\n @ViewChild(EntityWizardComponent)\n protected override editEntityComponent?: EntityWizardComponent<TRecord>;\n constructor(\n @Inject(MAT_DIALOG_DATA)\n public readonly data: CreateEntityWizardComponentOptions<TRecord>,\n dialogRef: MatDialogRef<\n EntityWizardDialog<TRecord>,\n EditEntityDialogResult<TRecord>\n >,\n matDialog: MatDialog,\n ) {\n super(dialogRef, matDialog);\n }\n}\n","<ame-entity-wizard\n [options]=\"data\"\n displayMode=\"dialog\"\n (closeRequest)=\"close($event)\"\n/>\n","import { inject, Injectable } from \"@angular/core\";\nimport { MatDialogRef } from \"@angular/material/dialog\";\nimport {\n EditEntityDialogResult,\n EditEntityRecord,\n} from \"@eherve/angular-material-forms/core\";\nimport {\n EntityDialogLauncher,\n EntityDialogOpenConfig,\n} from \"@eherve/angular-material-forms/editor\";\nimport { CreateEntityWizardComponentOptions } from \"./create-entity-wizard.types\";\nimport { EntityWizardDialog } from \"./entity-wizard-dialog.component\";\nexport type CreateEntityWizardOpenConfig<TRecord extends EditEntityRecord> =\n EntityDialogOpenConfig<TRecord>;\n@Injectable({ providedIn: \"root\" })\nexport class CreateEntityWizardService {\n private readonly launcher = inject(EntityDialogLauncher);\n open<TRecord extends EditEntityRecord>(\n options: CreateEntityWizardComponentOptions<TRecord>,\n config: CreateEntityWizardOpenConfig<TRecord> = {},\n ): MatDialogRef<\n EntityWizardDialog<TRecord>,\n EditEntityDialogResult<TRecord>\n > {\n if (!options.wizard?.steps.length || !(\"tabs\" in options.displayOptions))\n throw new Error(\n \"Use createEntityWizardOptions before opening a creation wizard\",\n );\n return this.launcher.open(EntityWizardDialog<TRecord>, options, config);\n }\n}\n","import {\n EditEntityComponentOptions,\n EditEntityRecord,\n EditEntityTabDisplayOptions,\n} from \"@eherve/angular-material-forms/core\";\n\nexport interface CreateEntityWizardStep<\n TRecord extends EditEntityRecord = EditEntityRecord,\n> {\n id: string;\n label: string;\n description?: string;\n icon?: string;\n optional?: boolean;\n displayOptions: EditEntityTabDisplayOptions<TRecord>;\n /** Explicit control paths, including controls prepared by the host. Defaults to rendered fields. */\n fields?: string[];\n /** This step's components persist their own mutations; Continue never submits the form. */\n componentManaged?: boolean;\n}\n\nexport interface CreateEntityWizardConfiguration<\n TRecord extends EditEntityRecord = EditEntityRecord,\n> {\n steps: CreateEntityWizardStep<TRecord>[];\n icon?: string;\n nextLabel?: string;\n createNextLabel?: string;\n skipLabel?: string;\n finishLaterLabel?: string;\n finishLabel?: string;\n optionalLabel?: string;\n unsavedLabel?: string;\n}\n\nexport interface CreateEntityWizardComponentOptions<\n TRecord extends EditEntityRecord = EditEntityRecord,\n> extends EditEntityComponentOptions<TRecord> {\n wizard: CreateEntityWizardConfiguration<TRecord>;\n}\n\nexport type CreateEntityWizardOptions<\n TRecord extends EditEntityRecord = EditEntityRecord,\n> = Omit<\n EditEntityComponentOptions<TRecord>,\n \"displayOptions\" | \"record\" | \"wizard\"\n> &\n CreateEntityWizardConfiguration<TRecord>;\n\n/** Normalize before decorating access/fields. The opening service uses this exact options object. */\nexport function createEntityWizardOptions<TRecord extends EditEntityRecord>(\n options: CreateEntityWizardOptions<TRecord>,\n): CreateEntityWizardComponentOptions<TRecord> {\n if (!options.steps.length)\n throw new Error(\"A creation wizard requires at least one step\");\n const {\n steps,\n icon,\n nextLabel,\n createNextLabel,\n skipLabel,\n finishLaterLabel,\n finishLabel,\n optionalLabel,\n unsavedLabel,\n ...base\n } = options;\n return {\n ...base,\n service: base.service\n ? { ...base.service, closeOnUpsert: false }\n : undefined,\n wizard: {\n steps,\n icon,\n nextLabel,\n createNextLabel,\n skipLabel,\n finishLaterLabel,\n finishLabel,\n optionalLabel,\n unsavedLabel,\n },\n displayOptions: {\n tabs: steps.map((step) => ({\n id: step.id,\n label: step.label,\n icon: step.icon,\n displayOptions: step.displayOptions,\n })),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAQA,IAAa,0BAAb,MAAoC;CAClC,MAAM,MAA2B,OAAe;EAC9C,OAAO,MAAM,OAAO,SAAQ;GAC1B,MAAM,UAAU,KAAK,WAAW,IAAI;GACpC,OACE,CAAC,WAAW,QAAQ,YAAa,CAAC,QAAQ,WAAW,QAAQ;EAEjE,CAAC;CACH;CAEA,QACE,MACA,OACA,aACA,SAAmB,CAAA,GAAE;EAErB,MAAM,QAAiC,CAAA;EACvC,KAAK,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG;GACjD,MAAM,UAAU,KAAK,WAAW,IAAI;GACpC,IAAI,CAAC,WAAW,cAAc,OAAO,GAAG;GACxC,MAAM,UAAU,QAAQ,YAAW;GACnC,MAAM,UAAU,KAAK,KAAK,KAAK,cAAc,IAAI;GACjD,IAAI,CAAC,eAAe,OAAO,SAAS,IAAI,KAAK,CAAC,QAAQ,SAAS,OAAO,GACpE,IAAI,OAAO,MAAM,UAAU,OAAO,CAAC;EACvC;EACA,OAAO;CACT;CAEA,QACE,MACA,QACA,WAAmB;EAEnB,MAAM,WAAW,UAAU,KAAK,YAAY;EAC5C,MAAM,SAAS,SAA0B,SAAsB;GAC7D,IAAI,mBAAmB,WAAW;IAChC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,QAAQ,GACxD,MAAM,OAAO,OAAO,GAAG,KAAI,GAAI,QAAQ,GAAG;IAC5C;GACF;GACA,MAAM,QAAQ,UAAU,MACrB,SAAS,SAAS,QAAQ,KAAK,WAAW,GAAG,KAAI,EAAG,CAAC;GAExD,MAAM,UAAU,QAAQ,YAAW;GACnC,MAAM,QAAQ,CAAC,QAAQ,SAAS,KAAK,KAAK,UAAU,IAAI,CAAC;GACzD,IAAI,SAAS,CAAC,OAAO;IACnB,IAAI,IAAI,QAAQ,IAAI,GAClB,QAAQ,WAAW,UAAU,IAAI,QAAQ,IAAI,CAAC,GAAG,EAC/C,WAAW,MACZ,CAAA;IACH,IAAI,OAAO,UAAU,eAAe,KAAK,KAAK,UAAU,IAAI,GAC1D,SAAS,QAAQ,UAAU,QAAQ,YAAW,CAAE;SAC7C,IAAI,UAAU,MAAM,UAAU,QAAQ,YAAW,CAAE,CAAC;IACzD,QAAQ,eAAc;IACtB,QAAQ,gBAAe;GACzB;EACF;EACA,MAAM,MAAM,EAAE;EACd,KAAK,gBAAgB,QAAQ;CAC/B;CAEQ,KAAK,OAAgC,MAAY;EACvD,OAAO,OAAO,UAAU,eAAe,KAAK,OAAO,IAAI,IACnD,MAAM,QACN,IAAI,OAAO,IAAI;CACrB;AACD;ACdD,IAAa,wBAAb,MAAa,sBAAqB;CAMhC;CACS,cAAiC;CACjC,UAAU,OAAmC,iBAAiB;CACpD,SAAS,KAAK,QAAQ;CACtB,eAAe,KAAK,QAAQ;CAC/C,WAAQ;EACN,KAAK,QAAQ,UAAU,KAAK;EAC5B,KAAK,QAAQ,cAAc,KAAK;EAChC,KAAK,QAAQ,SAAS;GACpB,aAAa,KAAK;GAClB,QAAQ,WAAW,KAAK,YAAY,MAAM;GAC1C,QAAQ,QAAQ,UAAU,KAAK,kBAAkB,QAAQ,KAAK;GAC9D,QAAQ,QAAQ,UAAU,KAAK,cAAc,QAAQ,KAAK;GAC1D,UAAU,WACR,KAAK,YAAY,QAAQ,KAAK,QAAQ,WAAW,QAAQ,CAAA,CAAE;GAC7D,eAAe,KAAK,mBAAkB;GACtC,gBAAgB,KAAK,wBAAuB;GAC5C,aAAY;IACV,KAAK,YAAY,IAAI,CAAC;IACtB,KAAK,gBAAgB,IAAI,KAAK,QAAQ,OAAO,MAAM,UAAU,KAAK,CAAC;IACnE,KAAK,qBAAqB,IAAI,CAAA,CAAE;IAChC,KAAK,2BAA2B,CAAA;GAClC;GACA,wBAAwB,KAAK,mBAAkB;GAC/C,mBACE,KAAK,QAAQ,UAAU,gBACrB,KAAK,QAAQ,UAAU,YAAY;GAEvC,cAAc;EACf;EACD,AAAK,KAAK,QAAQ,WAAU;CAC9B;CACA,YAAY,SAAsB;EAChC,IAAI,QAAQ,cAAc,CAAC,QAAQ,UAAU,CAAC,aAAa;GACzD,KAAK,QAAQ,UAAU,KAAK;GAC5B,AAAK,KAAK,QAAQ,WAAU;EAC9B;CACF;CACA,cAAW;EACT,KAAK,QAAQ,QAAO;CACtB;CACA,eAAY;EACV,OAAO,KAAK,QAAQ,aAAY;CAClC;CACA,sBAAmB;EACjB,KAAK,QAAQ,oBAAmB;CAClC;CACA,qBAAkB;EAChB,IACE,CAAC,KAAK,QAAQ,mBACd,KAAK,QAAQ,WACb,KAAK,QAAQ,UAEb,OAAO;EACT,AAAK,KAAK,WAAU;EACpB,OAAO;CACT;CACS,cAAc,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CACvB,eAAe,OAAkB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CACpC,kBAAkB,OAAkB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CACvC,uBAAuB,OAAiB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,uBAAA,CAAA,+BAAA,CAAA,CAAA;CAC3C,oBAAoB,eAC3B,KAAK,IAAI,GAAG,KAAK,qBAAoB,CAAE,CAAC,QAAQ,KAAK,YAAW,CAAE,CAAC,GACpE,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CACQ,0BAA0B,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,0BAAA,CAAA,+BAAA,CAAA,CAAA;CACvC,eAAe,OAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CACpC,2BAAmD,CAAA;CAC5B;CACN,cAAc,IAAI,wBAAuB;CAC1D,IAAI,aAAU;EACZ,OAAO,KAAK,SAAS,QAAQ,MAAM,KAAK,YAAW;CACrD;CAEA,IAAI,aAAU;EACZ,OAAO,KAAK,YAAW,MAAO,KAAK,qBAAoB,CAAE,CAAC,GAAG,EAAE;CACjE;CAEA,IAAI,qBAAkB;EACpB,MAAM,SAAS,KAAK,QAAQ;EAC5B,IAAI,CAAC,KAAK,QAAQ,QAAQ,KACxB,OACE,QAAQ,mBAAmB,KAAK,QAAQ,KAAK,SAAS;EAE1D,OAAO,KAAK,aACP,QAAQ,eAAe,KAAK,QAAQ,KAAK,SAAS,eAClD,QAAQ,aAAa,KAAK,QAAQ,KAAK,SAAS;CACvD;CAEA,IAAI,kBAAe;EACjB,OAAO,KAAK,QAAQ,QAAQ,MACvB,KAAK,QAAQ,QAAQ,oBACpB,KAAK,QAAQ,KAAK,SAAS,oBAC7B,KAAK,QAAQ;CACnB;CAEA,uBAAuB,UAAgB;EACrC,MAAM,QAAQ,KAAK,qBAAoB,CAAE,CAAC;EAC1C,IAAI,UAAU,QAAW,KAAK,iBAAiB,KAAK;CACtD;CAEQ,gBAAa;EACnB,MAAM,OAAO,KAAK,qBAAoB,CAAE,CAAC,KAAK,kBAAiB,IAAK;EACpE,IAAI,SAAS,QAAW;GACtB,KAAK,YAAY,IAAI,IAAI;GACzB,KAAK,wBAAuB;GAC5B,KAAK,wBAAuB;EAC9B;CACF;CAEQ,qBAAkB;EACxB,MAAM,YAAY,CAAC,GAAG,KAAK,gBAAe,CAAE;EAC5C,UAAU,KAAK,YAAW,KAAM;EAChC,KAAK,gBAAgB,IAAI,SAAS;EAClC,KAAK,wBAAuB;CAC9B;CAEQ,0BAAuB;EAC7B,IAAI,CAAC,KAAK,SAAS,QAAQ;EAC3B,MAAM,UAAU,KAAK,QAAQ,YAAY,KAAK;EAC9C,KAAK,wBAAwB,IAAI,OAAO;EACxC,MAAM,SAAS,CAAC,KAAK,QAAQ,QAAQ,OAAO;EAC5C,KAAK,aAAa,IAAI,MAAM;EAC5B,IAAI,KAAK,eAAe,KAAK,cAAc,SAAS;EACpD,MAAM,QAAQ,KAAK,QAAQ,OAAO,MAAM;EACxC,IAAI,KAAK,yBAAyB,WAAW,OAC3C,KAAK,2BAA2B,MAAM,KACpC,EAAE,QAAQ,MAAK,SAEb,IAAI,YAAY,OAAO;GACrB,aAAa;GACb,YAAY,WAAW;EACxB,CAAA,CAAC;EAGR,KAAK,MAAM,CAAC,OAAO,YAAY,KAAK,yBAAyB,QAAO,GAClE,QAAQ,SACN,CAAC,YAAY,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,QAAQ,MACnD,EAAE,WAAW,MAAK,CAAE;EAKxB,KAAK,eAAe,MAAM,SAAS,MAAM,aAAY;GACnD,MAAM,QAAQ,KAAK,qBAAoB,CAAE,CAAC;GAC1C,KAAK,WAAW;GAChB,KAAK,aAAa;GAClB,KAAK,YAAY,CAAC,WAAW,CAAC,CAAC,KAAK,gBAAe,CAAE,CAAC;GACtD,KAAK,WAAW,CAAC,YAAY,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,QAAQ;EACrE,CAAC;CACH;CAEA,IAAI,cAAW;EACb,OAAO,KAAK,YAAY,MAAM,KAAK,QAAQ,WAAW,CACpD,GAAG,KAAK,YAAW,GACnB,GAAG,KAAK,kBAAiB,CAC1B,CAAA;CACH;CAEA,IAAI,gBAAa;EACf,OAAO,CAAC,GAAG,KAAK,YAAW,GAAI,GAAG,KAAK,kBAAiB,CAAE,CAAC,CAAC,MACzD,SAAS,KAAK,QAAQ,UAAU,WAAW,IAAI,CAAC,EAAE,OAAO;CAE9D;CAEA,IAAI,gBAAa;EACf,OACE,OAAO,KACL,KAAK,YAAY,QACf,KAAK,QAAQ,WACb,KAAK,YAAW,GAChB,IAAI,CACL,CACF,CAAC,SAAS;CAEf;CAEA,iBAAiB,OAAa;EAC5B,IACE,KAAK,QAAQ,YACb,KAAK,iBACL,CAAC,KAAK,QAAQ,UACd,QAAQ,KACR,SAAS,KAAK,QAAQ,OAAO,MAAM,UACnC,CAAC,KAAK,qBAAoB,CAAE,CAAC,SAAS,KAAK,GAE3C;EACF,IAAI,QAAQ,KAAK,CAAC,KAAK,QAAQ,QAAQ,KAAK;EAC5C,KAAK,YAAY,IAAI,KAAK;EAC1B,KAAK,wBAAuB;EAC5B,KAAK,wBAAuB;CAC9B;CAEA,iBAAc;EACZ,IACE,KAAK,QAAQ,YACb,KAAK,iBACL,CAAC,KAAK,YAAY,YAClB,CAAC,KAAK,QAAQ,QAAQ,KAEtB;EACF,IAAI,KAAK,YAAY,AAAK,KAAK,aAAY;OACtC,KAAK,cAAa;CACzB;CAEA,MAAM,aAAU;EACd,IAAI,KAAK,QAAQ,YAAY,CAAC,KAAK,QAAQ,QAAQ;EACnD,MAAM,YAAY,KAAK,QAAQ,wBAAuB;EACtD,IAAI,CAAC,KAAK,YAAY,kBAAkB;GACtC,KAAK,MAAM,QAAQ,KAAK,YAAW,GACjC,KAAK,QAAQ,UAAU,WAAW,IAAI,CAAC,EAAE,iBAAgB;GAC3D,IAAI,CAAC,KAAK,aAAa;GACvB,MAAM,WAAW,KAAK,QAAQ;GAC9B,IAAI,CAAC,KAAK,QAAQ,QAAQ,KAAK,MAAM,KAAK,QAAQ,OAAM;QACnD,IAAI,KAAK,eAAe,MAAM,KAAK,QAAQ,OAAM;QACjD;IACH,KAAK,mBAAkB;IACvB,IAAI,KAAK,YAAY,MAAM,KAAK,aAAY;SACvC,KAAK,cAAa;IACvB;GACF;GACA,IAAI,CAAC,UAAS,KAAM,aAAa,KAAK,QAAQ,aAAa;EAC7D;EACA,IAAI,KAAK,YAAY,MAAM,KAAK,aAAY;OACvC,KAAK,cAAa;CACzB;CAEQ,YAAY,QAAQ,KAAK,YAAW,GAAE;EAC5C,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,MAAM,WAAW,UAAU,UAAU,QAAQ,KAAK,SAAS;EACjE,MAAM,QAAQ,MACV,KAAK,QAAQ,cACV,cAAc,IAAI,cAAc,CAAC,CACjC,SAAS,UACR,MAAM,SAAS,eACf,MAAM,SAAS,cACf,MAAM,SAAS,YACX,CAAA,IACA,CAAC,MAAM,IAAI,CAAC,IAEpB,CAAA;EACJ,OAAO,CACL,mBAAG,IAAI,IAAI,CACT,GAAG,OACH,GAAI,KAAK,QAAQ,QAAQ,MAAM,MAAM,EAAE,UAAU,CAAA,CAClD,CAAA,CACF;CACH;CAEQ,oBAAiB;EACvB,OAAO,KAAK,QAAQ,OAAO,SAAS,UAClC,MAAM,SAAS,eACf,MAAM,SAAS,cACf,MAAM,SAAS,aACf,MAAM,iBACF,CAAC,MAAM,IAAI,IACX,CAAA,CAAE;CAEV;CAEQ,YAAY,QAAe;EACjC,MAAM,QAAQ,KAAK,YAAW;EAC9B,MAAM,SAAS,KAAK,kBAAiB;EACrC,MAAM,QAAQ,KAAK,YAAY,QAC7B,KAAK,QAAQ,WACb,OACA,CAAC,QACD,MAAM;EAER,IAAI,QACF;QAAK,MAAM,QAAQ,CAAC,GAAG,OAAO,GAAG,MAAM,GACrC,IACE,CAAC,KAAK,QAAQ,UAAU,WAAW,IAAI,KACvC,IAAI,KAAK,QAAQ,eAAe,IAAI,GAEpC,IAAI,OAAO,MAAM,UAAU,IAAI,KAAK,QAAQ,eAAe,IAAI,CAAC,CAAC;EACrE;EACF,OAAO;CACT;CAEQ,kBACN,QACA,OAA8B;EAE9B,MAAM,UAAU,UAAU,MAAM;EAChC,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,YAAW,GAAI,GAAG,KAAK,kBAAiB,CAAE,GACpE,IAAI,IAAI,OAAO,IAAI,GAAG,IAAI,SAAS,MAAM,UAAU,IAAI,OAAO,IAAI,CAAC,CAAC;EAEtE,OAAO;CACT;CAEQ,MAAM,cACZ,QACA,OAA8B;EAE9B,MAAM,QAAQ,CAAC,GAAG,KAAK,YAAW,GAAI,GAAG,KAAK,kBAAiB,CAAE,CAAC,CAAC,QAChE,SAAS,IAAI,OAAO,IAAI,CAAC;EAE5B,KAAK,YAAY,QAAQ,KAAK,QAAQ,WAAW,QAAQ,KAAK;EAC9D,KAAK,mBAAkB;EACvB,KAAK,QAAQ,UAAU,KAAK,QAAQ,UAAU;EAC9C,KAAK,QAAQ,sBAAqB;EAClC,KAAK,QAAQ,oBAAmB;CAClC;CAEQ,0BAAuB;EAC7B,KAAK,QAAQ,qBAAqB,UAAU,KAAK,kBAAiB,CAAE;CACtE;CAEQ,0BAAuB;EAC7B,MAAM,QACJ,KAAK,qBAAoB,CAAE,CACzB,KAAK,QAAQ,qBAAqB;EAEtC,IACE,UAAU,UACV,CAAC,KAAK,QAAQ,YACd,CAAC,KAAK,kBACL,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,QAAQ,MAEvC,KAAK,YAAY,IAAI,KAAK;EAC5B,KAAK,wBAAuB;EAC5B,KAAK,wBAAuB;CAC9B;CAEQ,qBAAkB;EACxB,MAAM,WAAW,KAAK,QAAQ,YAAW,CAAE,CAAC,QAAQ,CAAA,EAAA,CAAI,SACrD,KAAK,UAAW,IAAI,YAAY,CAAC,KAAK,IAAI,CAAA,CAAG;EAEhD,KAAK,qBAAqB,IAAI,OAAO;EACrC,KAAK,wBAAuB;EAC5B,IAAI,CAAC,QAAQ,SAAS,KAAK,YAAW,CAAE,KAAK,QAAQ,QACnD,KAAK,YAAY,IAAI,QAAQ,EAAE;EACjC,KAAK,aAAa,IAChB,KAAK,QAAQ,OAAO,MAAM,KACvB,GAAG,UACF,OAAO,KACL,KAAK,YAAY,QACf,KAAK,QAAQ,WACb,KAAK,YAAY,KAAK,GACtB,IAAI,CACL,CACF,CAAC,SAAS,CAAC,CACf;EAEH,KAAK,wBAAuB;CAC9B;CA/VW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAqB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAArB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAqB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;GAAA,aAAA;EAAA;EAAA,SAAA;GAAA,QAAA;GAAA,cAAA;EAAA;EAAA,WAXrB;GACT;GACA;GACA;GACA;GACA;EACD;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WA8EU;GAAU,aAAA;EAAA,CAAA;EAAA,eAAA;EAAA,UAAA;EAAA,UCrIvB;EAsTA,QAAA,CAAA,0oSAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MD9QI;GAAY;GAAA;IAAA,MAAA;IAAA,MAAAA,KAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,mBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAAA,KAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACZ;GAAmB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,WAAA;IAAA,SAAA,CAAA,UAAA;IAAA,UAAA,CAAA,QAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACnB;GAAe;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,WAAA;IAAA,UAAA,CAAA,aAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,UAAA,CAAA,aAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACf;GAAa;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,SAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACb;GAAwB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,oBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACxB;GAAgB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,YAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAChB;IAAoB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MACpB;GAAgB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,OAAA;IAAA,UAAA,CAAA,SAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,eAAA;IAAA,UAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,gBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAaP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA;EAvBjC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAmB,SACpB;IACP;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACD;GAAA,WACU;IACT;IACA;IACA;IACA;IACA;GACD;GAAA,iBAGgB,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,0oSAAA;EAAA,CAAA;;;;GAO9C,MAAA;GAAM,MAAA,CAAA,EAAE,UAAU,KAAI,CAAE;;EAExB,aAAA,CAAA,EAAA,MAAA,MAAA,CAAA;EAEA,QAAA,CAAA,EAAA,MAAA,OAAA,CAAA;EACA,cAAA,CAAA,EAAA,MAAA,OAAA,CAAA;;GA+DA,MAAA;GAAU,MAAA,CAAA,UAAU;;;;AE5GvB,IAAa,qBAAb,MAAa,2BAEH,yBAA8D;CAKpD;CAHC;CACnB,YAEkB,MAChB,WAIA,WAAoB;EAEpB,MAAM,WAAW,SAAS;EAPV,KAAA,OAAA;CAQlB;CAfW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA;GAAA,EAAA,OAMnB,gBAAe;GAAA,EAAA,OAAA,GAAA,aAAA;GAAA,EAAA,OAAA,GAAA,UAAA;EAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CANd,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAkB,cAAA;EAAA,UAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAGlB;GAAqB,aAAA;EAAA,CAAA;EAAA,iBAAA;EAAA,UAAA;EAAA,UC5BlC;EAKA,QAAA,CAAA,oLAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDeY;GAAqB,UAAA;GAAA,QAAA,CAAA,WAAA,aAAA;GAAA,SAAA,CAAA,UAAA,cAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAKpB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA;EAP9B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAA0B,SAC3B,CAAC,qBAAqB;GAAC,iBAGf,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,oLAAA;EAAA,CAAA;;;;;;IAQ5C,MAAA;IAAO,MAAA,CAAA,eAAe;;;;;;;EAHxB,MAAA;EAAU,MAAA,CAAA,qBAAqB;;;AEblC,IAAa,4BAAb,MAAa,0BAAyB;CACnB,WAAW,OAAO,oBAAoB;CACvD,KACE,SACA,SAAgD,CAAA,GAAE;EAKlD,IAAI,CAAC,QAAQ,QAAQ,MAAM,UAAU,EAAE,UAAU,QAAQ,iBACvD,MAAM,IAAI,MACR,gEAAgE;EAEpE,OAAO,KAAK,SAAS,KAAK,oBAA6B,SAAS,MAAM;CACxE;CAdW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAyB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAzB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAyB,YADZ;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAyB,YAAA,CAAA;EADrC,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;ACoClC,SAAgB,0BACd,SAA2C;CAE3C,IAAI,CAAC,QAAQ,MAAM,QACjB,MAAM,IAAI,MAAM,8CAA8C;CAChE,MAAM,EACJ,OACA,MACA,WACA,iBACA,WACA,kBACA,aACA,eACA,cACA,GAAG,SACD;CACJ,OAAO;EACL,GAAG;EACH,SAAS,KAAK,UACV;GAAE,GAAG,KAAK;GAAS,eAAe;EAAK,IACvC;EACJ,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACD;EACD,gBAAgB,EACd,MAAM,MAAM,KAAK,UAAU;GACzB,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,gBAAgB,KAAK;EACtB,EAAC,EACH;CACF;AACH"}
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@eherve/angular-material-entity-wizard",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "22.1.0",
4
+ "author": "Eric Herve <eric.herve.95@gmail.com>",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/eherve/angular-material-extended.git"
8
+ },
9
+ "private": false,
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "peerDependencies": {
14
+ "@angular/animations": "^22.0.0",
15
+ "@angular/cdk": "^22.0.0",
16
+ "@angular/common": "^22.0.0",
17
+ "@angular/core": "^22.0.0",
18
+ "@angular/material": "^22.0.0",
19
+ "rxjs": "^7.8.0",
20
+ "@angular/forms": "^22.0.0",
21
+ "@eherve/angular-material-section-tabs": "^22.3.0",
22
+ "moment": "^2.30.1",
23
+ "dayjs": "^1.11.20",
24
+ "@angular/material-moment-adapter": "^22.0.0",
25
+ "@ng-matero/extensions": "^22.1.1",
26
+ "@ng-matero/extensions-moment-adapter": "^22.0.0",
27
+ "@eherve/angular-material-forms": "^22.2.0"
28
+ },
29
+ "dependencies": {
30
+ "tslib": "^2.3.0",
31
+ "lodash-es": "^4.17.21",
32
+ "subsink": "^1.0.2"
33
+ },
34
+ "module": "fesm2022/eherve-angular-material-entity-wizard.mjs",
35
+ "typings": "types/eherve-angular-material-entity-wizard.d.ts",
36
+ "exports": {
37
+ "./package.json": {
38
+ "default": "./package.json"
39
+ },
40
+ ".": {
41
+ "types": "./types/eherve-angular-material-entity-wizard.d.ts",
42
+ "default": "./fesm2022/eherve-angular-material-entity-wizard.mjs"
43
+ }
44
+ },
45
+ "sideEffects": false,
46
+ "type": "module"
6
47
  }
@@ -0,0 +1,116 @@
1
+ import { EditEntityComponentOptions, EditEntityDialogResult, EditEntityFormGroup, EditEntityRecord, EditEntityTabDisplayOptions } from "@eherve/angular-material-forms/core";
2
+ import { MatDialog, MatDialogRef } from "@angular/material/dialog";
3
+ import { EntityDialogKeyboardHost, EntityDialogOpenConfig, EntityFormSession } from "@eherve/angular-material-forms/editor";
4
+ import { CdkStepper } from "@angular/cdk/stepper";
5
+ import * as i0 from "@angular/core";
6
+ import { OnChanges, OnDestroy, OnInit, SimpleChanges } from "@angular/core";
7
+ import { FormControl } from "@angular/forms";
8
+ /** Scoped persistence shared by the wizard engine; no host-specific model assumptions. */
9
+ export declare class CreateEntityWizardState {
10
+ valid(form: EditEntityFormGroup, paths: string[]): boolean;
11
+ project(form: EditEntityFormGroup, paths: string[], changedOnly: boolean, always?: string[]): Record<string, unknown>;
12
+ hydrate(form: EditEntityFormGroup, record: object, submitted: string[]): void;
13
+ private read;
14
+ }
15
+ export interface CreateEntityWizardStep<TRecord extends EditEntityRecord = EditEntityRecord> {
16
+ id: string;
17
+ label: string;
18
+ description?: string;
19
+ icon?: string;
20
+ optional?: boolean;
21
+ displayOptions: EditEntityTabDisplayOptions<TRecord>;
22
+ /** Explicit control paths, including controls prepared by the host. Defaults to rendered fields. */
23
+ fields?: string[];
24
+ /** This step's components persist their own mutations; Continue never submits the form. */
25
+ componentManaged?: boolean;
26
+ }
27
+ export interface CreateEntityWizardConfiguration<TRecord extends EditEntityRecord = EditEntityRecord> {
28
+ steps: CreateEntityWizardStep<TRecord>[];
29
+ icon?: string;
30
+ nextLabel?: string;
31
+ createNextLabel?: string;
32
+ skipLabel?: string;
33
+ finishLaterLabel?: string;
34
+ finishLabel?: string;
35
+ optionalLabel?: string;
36
+ unsavedLabel?: string;
37
+ }
38
+ export interface CreateEntityWizardComponentOptions<TRecord extends EditEntityRecord = EditEntityRecord> extends EditEntityComponentOptions<TRecord> {
39
+ wizard: CreateEntityWizardConfiguration<TRecord>;
40
+ }
41
+ export type CreateEntityWizardOptions<TRecord extends EditEntityRecord = EditEntityRecord> = Omit<EditEntityComponentOptions<TRecord>, "displayOptions" | "record" | "wizard"> & CreateEntityWizardConfiguration<TRecord>;
42
+ /** Normalize before decorating access/fields. The opening service uses this exact options object. */
43
+ export declare function createEntityWizardOptions<TRecord extends EditEntityRecord>(options: CreateEntityWizardOptions<TRecord>): CreateEntityWizardComponentOptions<TRecord>;
44
+ export declare class EntityWizardComponent<TRecord extends EditEntityRecord = EditEntityRecord> implements OnInit, OnChanges, OnDestroy {
45
+ options: CreateEntityWizardComponentOptions<TRecord>;
46
+ displayMode: "page" | "dialog";
47
+ readonly session: EntityFormSession<TRecord>;
48
+ readonly result: import("@angular/core").EventEmitter<import("@eherve/angular-material-forms/core").EditEntityDialogResult<TRecord>>;
49
+ readonly closeRequest: import("@angular/core").EventEmitter<import("@eherve/angular-material-forms/core").EditEntityDialogResult<TRecord>>;
50
+ ngOnInit(): void;
51
+ ngOnChanges(changes: SimpleChanges): void;
52
+ ngOnDestroy(): void;
53
+ requestClose(): Promise<void>;
54
+ refreshPresentation(): void;
55
+ submitFromKeyboard(): boolean;
56
+ readonly wizardIndex: import("@angular/core").WritableSignal<number>;
57
+ readonly wizardDrafts: import("@angular/core").WritableSignal<boolean[]>;
58
+ readonly wizardCompleted: import("@angular/core").WritableSignal<boolean[]>;
59
+ readonly wizardVisibleIndices: import("@angular/core").WritableSignal<number[]>;
60
+ readonly wizardVisualIndex: import("@angular/core").Signal<number>;
61
+ readonly wizardNavigationBlocked: import("@angular/core").WritableSignal<boolean>;
62
+ readonly wizardLinear: import("@angular/core").WritableSignal<boolean>;
63
+ wizardNavigationControls: FormControl<boolean>[];
64
+ wizardStepper?: CdkStepper;
65
+ private readonly wizardState;
66
+ get wizardStep(): CreateEntityWizardStep<TRecord> | undefined;
67
+ get wizardLast(): boolean;
68
+ get wizardPrimaryLabel(): string;
69
+ get wizardExitLabel(): string;
70
+ selectWizardVisualStep(position: number): void;
71
+ private advanceWizard;
72
+ private completeWizardStep;
73
+ private refreshWizardNavigation;
74
+ get wizardValid(): boolean;
75
+ get wizardPending(): boolean;
76
+ get wizardUnsaved(): boolean;
77
+ selectWizardStep(index: number): void;
78
+ skipWizardStep(): void;
79
+ wizardNext(): Promise<void>;
80
+ private wizardPaths;
81
+ private wizardAlwaysPaths;
82
+ private wizardValue;
83
+ private patchWizardRecord;
84
+ private hydrateWizard;
85
+ private publishWizardNavigation;
86
+ private restoreWizardNavigation;
87
+ private refreshWizardState;
88
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityWizardComponent<any>, never>;
89
+ static ɵcmp: i0.ɵɵComponentDeclaration<EntityWizardComponent<any>, "ame-entity-wizard", never, {
90
+ "options": {
91
+ "alias": "options";
92
+ "required": true;
93
+ };
94
+ "displayMode": {
95
+ "alias": "displayMode";
96
+ "required": false;
97
+ };
98
+ }, {
99
+ "result": "result";
100
+ "closeRequest": "closeRequest";
101
+ }, never, never, true, never>;
102
+ }
103
+ export declare class EntityWizardDialog<TRecord extends EditEntityRecord = EditEntityRecord> extends EntityDialogKeyboardHost<TRecord, EntityWizardDialog<TRecord>> {
104
+ readonly data: CreateEntityWizardComponentOptions<TRecord>;
105
+ protected editEntityComponent?: EntityWizardComponent<TRecord>;
106
+ constructor(data: CreateEntityWizardComponentOptions<TRecord>, dialogRef: MatDialogRef<EntityWizardDialog<TRecord>, EditEntityDialogResult<TRecord>>, matDialog: MatDialog);
107
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityWizardDialog<any>, never>;
108
+ static ɵcmp: i0.ɵɵComponentDeclaration<EntityWizardDialog<any>, "ame-entity-wizard-dialog", never, {}, {}, never, never, true, never>;
109
+ }
110
+ export type CreateEntityWizardOpenConfig<TRecord extends EditEntityRecord> = EntityDialogOpenConfig<TRecord>;
111
+ export declare class CreateEntityWizardService {
112
+ private readonly launcher;
113
+ open<TRecord extends EditEntityRecord>(options: CreateEntityWizardComponentOptions<TRecord>, config?: CreateEntityWizardOpenConfig<TRecord>): MatDialogRef<EntityWizardDialog<TRecord>, EditEntityDialogResult<TRecord>>;
114
+ static ɵfac: i0.ɵɵFactoryDeclaration<CreateEntityWizardService, never>;
115
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
116
+ }