@daltonr/pathwrite-angular 0.9.0 → 0.10.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/dist/index.css +86 -0
- package/dist/index.d.ts +15 -7
- package/dist/index.js +16 -6
- package/dist/index.js.map +1 -1
- package/dist/shell.d.ts +10 -5
- package/dist/shell.js +92 -22
- package/dist/shell.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +26 -10
- package/src/shell.ts +55 -14
package/dist/index.css
CHANGED
|
@@ -285,6 +285,16 @@
|
|
|
285
285
|
content: ":";
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
/* ------------------------------------------------------------------ */
|
|
289
|
+
/* Blocking error (guard-level message, not field-attached) */
|
|
290
|
+
/* ------------------------------------------------------------------ */
|
|
291
|
+
.pw-shell__blocking-error {
|
|
292
|
+
margin: 0;
|
|
293
|
+
padding: 8px 16px;
|
|
294
|
+
font-size: 13px;
|
|
295
|
+
color: var(--pw-color-error);
|
|
296
|
+
}
|
|
297
|
+
|
|
288
298
|
/* ------------------------------------------------------------------ */
|
|
289
299
|
/* Warning messages */
|
|
290
300
|
/* ------------------------------------------------------------------ */
|
|
@@ -361,6 +371,7 @@
|
|
|
361
371
|
background: var(--pw-color-primary);
|
|
362
372
|
border-color: var(--pw-color-primary);
|
|
363
373
|
color: #fff;
|
|
374
|
+
position: relative;
|
|
364
375
|
}
|
|
365
376
|
|
|
366
377
|
.pw-shell__btn--next:hover:not(:disabled) {
|
|
@@ -368,6 +379,29 @@
|
|
|
368
379
|
border-color: #1d4ed8;
|
|
369
380
|
}
|
|
370
381
|
|
|
382
|
+
@keyframes pw-spin {
|
|
383
|
+
to { transform: rotate(360deg); }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.pw-shell__btn--next.pw-shell__btn--loading {
|
|
387
|
+
color: transparent;
|
|
388
|
+
pointer-events: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.pw-shell__btn--next.pw-shell__btn--loading::after {
|
|
392
|
+
content: "";
|
|
393
|
+
position: absolute;
|
|
394
|
+
top: 50%;
|
|
395
|
+
left: 50%;
|
|
396
|
+
width: 14px;
|
|
397
|
+
height: 14px;
|
|
398
|
+
margin: -7px 0 0 -7px;
|
|
399
|
+
border: 2px solid rgba(255, 255, 255, 0.35);
|
|
400
|
+
border-top-color: #fff;
|
|
401
|
+
border-radius: 50%;
|
|
402
|
+
animation: pw-spin 0.6s linear infinite;
|
|
403
|
+
}
|
|
404
|
+
|
|
371
405
|
.pw-shell__btn--back {
|
|
372
406
|
background: transparent;
|
|
373
407
|
border-color: var(--pw-color-primary);
|
|
@@ -388,3 +422,55 @@
|
|
|
388
422
|
background: var(--pw-color-primary-light);
|
|
389
423
|
}
|
|
390
424
|
|
|
425
|
+
/* ------------------------------------------------------------------ */
|
|
426
|
+
/* Error panel — replaces footer when status === "error" */
|
|
427
|
+
/* ------------------------------------------------------------------ */
|
|
428
|
+
.pw-shell__error {
|
|
429
|
+
background: var(--pw-color-error-bg);
|
|
430
|
+
border: 1px solid var(--pw-color-error-border);
|
|
431
|
+
border-radius: var(--pw-shell-radius);
|
|
432
|
+
padding: 16px 20px;
|
|
433
|
+
display: flex;
|
|
434
|
+
flex-direction: column;
|
|
435
|
+
gap: 10px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.pw-shell__error-title {
|
|
439
|
+
font-size: 14px;
|
|
440
|
+
font-weight: 600;
|
|
441
|
+
color: var(--pw-color-error);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.pw-shell__error-message {
|
|
445
|
+
font-size: 13px;
|
|
446
|
+
color: var(--pw-color-muted);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.pw-shell__error-actions {
|
|
450
|
+
display: flex;
|
|
451
|
+
gap: 8px;
|
|
452
|
+
align-items: center;
|
|
453
|
+
margin-top: 2px;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.pw-shell__btn--retry {
|
|
457
|
+
background: var(--pw-color-error);
|
|
458
|
+
border-color: var(--pw-color-error);
|
|
459
|
+
color: #fff;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.pw-shell__btn--retry:hover:not(:disabled) {
|
|
463
|
+
opacity: 0.9;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.pw-shell__btn--suspend {
|
|
467
|
+
color: var(--pw-color-muted);
|
|
468
|
+
border-color: transparent;
|
|
469
|
+
background: transparent;
|
|
470
|
+
font-size: 13px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.pw-shell__btn--suspend:hover:not(:disabled) {
|
|
474
|
+
background: var(--pw-color-primary-light);
|
|
475
|
+
}
|
|
476
|
+
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,10 @@ export declare class PathFacade<TData extends PathData = PathData> implements On
|
|
|
51
51
|
* component that provides this facade.
|
|
52
52
|
*/
|
|
53
53
|
restart(): Promise<void>;
|
|
54
|
+
/** Re-runs the operation that set `snapshot().error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. */
|
|
55
|
+
retry(): Promise<void>;
|
|
56
|
+
/** Pauses the path with intent to return. Emits `suspended`. All state is preserved. */
|
|
57
|
+
suspend(): Promise<void>;
|
|
54
58
|
startSubPath(path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>): Promise<void>;
|
|
55
59
|
next(): Promise<void>;
|
|
56
60
|
previous(): Promise<void>;
|
|
@@ -69,11 +73,11 @@ export declare class PathFacade<TData extends PathData = PathData> implements On
|
|
|
69
73
|
static ɵprov: i0.ɵɵInjectableDeclaration<PathFacade<any>>;
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
|
-
* Return type of `
|
|
76
|
+
* Return type of `usePathContext()`. Provides signal-based reactive access to the
|
|
73
77
|
* path state and strongly-typed navigation actions. Mirrors React's `usePathContext()`
|
|
74
78
|
* return type for consistency across adapters.
|
|
75
79
|
*/
|
|
76
|
-
export interface
|
|
80
|
+
export interface UsePathContextReturn<TData extends PathData = PathData> {
|
|
77
81
|
/** Current path snapshot as a signal. Returns `null` when no path is active. */
|
|
78
82
|
snapshot: Signal<PathSnapshot<TData> | null>;
|
|
79
83
|
/** Start (or restart) a path. */
|
|
@@ -99,15 +103,19 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
99
103
|
* Use for "Start over" / retry flows.
|
|
100
104
|
*/
|
|
101
105
|
restart: () => Promise<void>;
|
|
106
|
+
/** Re-run the operation that set `snapshot().error`. */
|
|
107
|
+
retry: () => Promise<void>;
|
|
108
|
+
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
109
|
+
suspend: () => Promise<void>;
|
|
102
110
|
}
|
|
103
111
|
/**
|
|
104
|
-
*
|
|
112
|
+
* Access the nearest `PathFacade`'s path instance for use in Angular step components.
|
|
105
113
|
* Requires `PathFacade` to be provided in the component's injector tree (either via
|
|
106
114
|
* `providers: [PathFacade]` in the component or a parent component).
|
|
107
115
|
*
|
|
108
116
|
* **This is the recommended way to consume Pathwrite in Angular components** — it
|
|
109
|
-
* provides the same ergonomic
|
|
110
|
-
*
|
|
117
|
+
* provides the same ergonomic API as React's `usePathContext()` and Vue's `usePathContext()`.
|
|
118
|
+
* No template references or manual facade injection needed.
|
|
111
119
|
*
|
|
112
120
|
* The optional generic `TData` narrows `snapshot().data` and `setData()` to your
|
|
113
121
|
* data shape. It is a **type-level assertion**, not a runtime guarantee.
|
|
@@ -126,7 +134,7 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
126
134
|
* `
|
|
127
135
|
* })
|
|
128
136
|
* export class ContactStepComponent {
|
|
129
|
-
* protected readonly path =
|
|
137
|
+
* protected readonly path = usePathContext<ContactData>();
|
|
130
138
|
*
|
|
131
139
|
* updateName(name: string) {
|
|
132
140
|
* this.path.setData('name', name);
|
|
@@ -136,7 +144,7 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
136
144
|
*
|
|
137
145
|
* @throws Error if PathFacade is not provided in the injector tree
|
|
138
146
|
*/
|
|
139
|
-
export declare function
|
|
147
|
+
export declare function usePathContext<TData extends PathData = PathData>(): UsePathContextReturn<TData>;
|
|
140
148
|
/**
|
|
141
149
|
* Minimal interface describing what syncFormGroup needs from an Angular
|
|
142
150
|
* FormGroup. Typed as a duck interface so that @angular/forms is not a
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,14 @@ export class PathFacade {
|
|
|
83
83
|
restart() {
|
|
84
84
|
return this._engine.restart();
|
|
85
85
|
}
|
|
86
|
+
/** Re-runs the operation that set `snapshot().error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. */
|
|
87
|
+
retry() {
|
|
88
|
+
return this._engine.retry();
|
|
89
|
+
}
|
|
90
|
+
/** Pauses the path with intent to return. Emits `suspended`. All state is preserved. */
|
|
91
|
+
suspend() {
|
|
92
|
+
return this._engine.suspend();
|
|
93
|
+
}
|
|
86
94
|
startSubPath(path, initialData = {}, meta) {
|
|
87
95
|
return this._engine.startSubPath(path, initialData, meta);
|
|
88
96
|
}
|
|
@@ -122,13 +130,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
122
130
|
type: Injectable
|
|
123
131
|
}], ctorParameters: () => [] });
|
|
124
132
|
/**
|
|
125
|
-
*
|
|
133
|
+
* Access the nearest `PathFacade`'s path instance for use in Angular step components.
|
|
126
134
|
* Requires `PathFacade` to be provided in the component's injector tree (either via
|
|
127
135
|
* `providers: [PathFacade]` in the component or a parent component).
|
|
128
136
|
*
|
|
129
137
|
* **This is the recommended way to consume Pathwrite in Angular components** — it
|
|
130
|
-
* provides the same ergonomic
|
|
131
|
-
*
|
|
138
|
+
* provides the same ergonomic API as React's `usePathContext()` and Vue's `usePathContext()`.
|
|
139
|
+
* No template references or manual facade injection needed.
|
|
132
140
|
*
|
|
133
141
|
* The optional generic `TData` narrows `snapshot().data` and `setData()` to your
|
|
134
142
|
* data shape. It is a **type-level assertion**, not a runtime guarantee.
|
|
@@ -147,7 +155,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
147
155
|
* `
|
|
148
156
|
* })
|
|
149
157
|
* export class ContactStepComponent {
|
|
150
|
-
* protected readonly path =
|
|
158
|
+
* protected readonly path = usePathContext<ContactData>();
|
|
151
159
|
*
|
|
152
160
|
* updateName(name: string) {
|
|
153
161
|
* this.path.setData('name', name);
|
|
@@ -157,10 +165,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
157
165
|
*
|
|
158
166
|
* @throws Error if PathFacade is not provided in the injector tree
|
|
159
167
|
*/
|
|
160
|
-
export function
|
|
168
|
+
export function usePathContext() {
|
|
161
169
|
const facade = inject(PathFacade, { optional: true });
|
|
162
170
|
if (!facade) {
|
|
163
|
-
throw new Error("
|
|
171
|
+
throw new Error("usePathContext() requires PathFacade to be provided. " +
|
|
164
172
|
"Add 'providers: [PathFacade]' to your component or a parent component.");
|
|
165
173
|
}
|
|
166
174
|
return {
|
|
@@ -175,6 +183,8 @@ export function injectPath() {
|
|
|
175
183
|
goToStep: (stepId) => facade.goToStep(stepId),
|
|
176
184
|
goToStepChecked: (stepId) => facade.goToStepChecked(stepId),
|
|
177
185
|
restart: () => facade.restart(),
|
|
186
|
+
retry: () => facade.retry(),
|
|
187
|
+
suspend: () => facade.suspend(),
|
|
178
188
|
};
|
|
179
189
|
}
|
|
180
190
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/shell.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TemplateRef, EventEmitter, QueryList, OnInit, OnChanges, OnDestroy, SimpleChanges, Injector } from "@angular/core";
|
|
2
|
-
import { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot, ProgressLayout } from "@daltonr/pathwrite-core";
|
|
2
|
+
import { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot, ProgressLayout, formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
|
|
3
3
|
import { PathFacade } from "./index";
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
/**
|
|
@@ -16,6 +16,10 @@ export interface PathShellActions {
|
|
|
16
16
|
setData: (key: string, value: unknown) => Promise<void>;
|
|
17
17
|
/** Restart the shell's current path with its current `initialData`. */
|
|
18
18
|
restart: () => Promise<void>;
|
|
19
|
+
/** Re-run the operation that set `snapshot.error`. */
|
|
20
|
+
retry: () => Promise<void>;
|
|
21
|
+
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
22
|
+
suspend: () => Promise<void>;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* Structural directive that associates a template with a step ID.
|
|
@@ -124,6 +128,8 @@ export declare class PathShellComponent implements OnInit, OnChanges, OnDestroy
|
|
|
124
128
|
nextLabel: string;
|
|
125
129
|
/** Label for the Next button when on the last step. */
|
|
126
130
|
completeLabel: string;
|
|
131
|
+
/** Label shown on the Next/Complete button while an async operation is in progress. When undefined, the button keeps its label and shows a CSS spinner only. */
|
|
132
|
+
loadingLabel?: string;
|
|
127
133
|
/** Label for the Cancel button. */
|
|
128
134
|
cancelLabel: string;
|
|
129
135
|
/** Hide the Cancel button entirely. */
|
|
@@ -186,9 +192,8 @@ export declare class PathShellComponent implements OnInit, OnChanges, OnDestroy
|
|
|
186
192
|
protected warningEntries(s: PathSnapshot): [string, string][];
|
|
187
193
|
/** Resolves "auto" footerLayout based on snapshot. Single-step top-level → "form", otherwise → "wizard". */
|
|
188
194
|
protected getResolvedFooterLayout(s: PathSnapshot): "wizard" | "form";
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
protected formatFieldKey(key: string): string;
|
|
195
|
+
protected errorPhaseMessage: typeof errorPhaseMessage;
|
|
196
|
+
protected formatFieldKey: typeof formatFieldKey;
|
|
192
197
|
static ɵfac: i0.ɵɵFactoryDeclaration<PathShellComponent, never>;
|
|
193
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<PathShellComponent, "pw-shell", never, { "path": { "alias": "path"; "required": false; }; "engine": { "alias": "engine"; "required": false; }; "initialData": { "alias": "initialData"; "required": false; }; "autoStart": { "alias": "autoStart"; "required": false; }; "backLabel": { "alias": "backLabel"; "required": false; }; "nextLabel": { "alias": "nextLabel"; "required": false; }; "completeLabel": { "alias": "completeLabel"; "required": false; }; "cancelLabel": { "alias": "cancelLabel"; "required": false; }; "hideCancel": { "alias": "hideCancel"; "required": false; }; "hideProgress": { "alias": "hideProgress"; "required": false; }; "footerLayout": { "alias": "footerLayout"; "required": false; }; "validationDisplay": { "alias": "validationDisplay"; "required": false; }; "progressLayout": { "alias": "progressLayout"; "required": false; }; }, { "complete": "complete"; "cancel": "cancel"; "event": "event"; }, ["customHeader", "customFooter", "stepDirectives"], never, true, never>;
|
|
198
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PathShellComponent, "pw-shell", never, { "path": { "alias": "path"; "required": false; }; "engine": { "alias": "engine"; "required": false; }; "initialData": { "alias": "initialData"; "required": false; }; "autoStart": { "alias": "autoStart"; "required": false; }; "backLabel": { "alias": "backLabel"; "required": false; }; "nextLabel": { "alias": "nextLabel"; "required": false; }; "completeLabel": { "alias": "completeLabel"; "required": false; }; "loadingLabel": { "alias": "loadingLabel"; "required": false; }; "cancelLabel": { "alias": "cancelLabel"; "required": false; }; "hideCancel": { "alias": "hideCancel"; "required": false; }; "hideProgress": { "alias": "hideProgress"; "required": false; }; "footerLayout": { "alias": "footerLayout"; "required": false; }; "validationDisplay": { "alias": "validationDisplay"; "required": false; }; "progressLayout": { "alias": "progressLayout"; "required": false; }; }, { "complete": "complete"; "cancel": "cancel"; "event": "event"; }, ["customHeader", "customFooter", "stepDirectives"], never, true, never>;
|
|
194
199
|
}
|
package/dist/shell.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Component, Directive, Input, Output, EventEmitter, ContentChild, Conten
|
|
|
2
2
|
import { CommonModule } from "@angular/common";
|
|
3
3
|
import { Subject } from "rxjs";
|
|
4
4
|
import { takeUntil } from "rxjs/operators";
|
|
5
|
+
import { formatFieldKey, errorPhaseMessage, } from "@daltonr/pathwrite-core";
|
|
5
6
|
import { PathFacade } from "./index";
|
|
6
7
|
import * as i0 from "@angular/core";
|
|
7
8
|
import * as i1 from "@angular/common";
|
|
@@ -160,8 +161,12 @@ export class PathShellComponent {
|
|
|
160
161
|
goToStepChecked: (id) => this.facade.goToStepChecked(id),
|
|
161
162
|
setData: (key, value) => this.facade.setData(key, value),
|
|
162
163
|
restart: () => this.facade.restart(),
|
|
164
|
+
retry: () => this.facade.retry(),
|
|
165
|
+
suspend: () => this.facade.suspend(),
|
|
163
166
|
};
|
|
164
167
|
this.destroy$ = new Subject();
|
|
168
|
+
this.errorPhaseMessage = errorPhaseMessage;
|
|
169
|
+
this.formatFieldKey = formatFieldKey;
|
|
165
170
|
}
|
|
166
171
|
ngOnChanges(changes) {
|
|
167
172
|
if (changes['engine'] && this.engine) {
|
|
@@ -216,13 +221,8 @@ export class PathShellComponent {
|
|
|
216
221
|
? (s.stepCount === 1 && s.nestingLevel === 0 ? "form" : "wizard")
|
|
217
222
|
: this.footerLayout;
|
|
218
223
|
}
|
|
219
|
-
/** Converts a camelCase or lowercase field key to a display label.
|
|
220
|
-
* e.g. "firstName" → "First Name", "email" → "Email" */
|
|
221
|
-
formatFieldKey(key) {
|
|
222
|
-
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
223
|
-
}
|
|
224
224
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PathShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
225
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: PathShellComponent, isStandalone: true, selector: "pw-shell", inputs: { path: "path", engine: "engine", initialData: "initialData", autoStart: "autoStart", backLabel: "backLabel", nextLabel: "nextLabel", completeLabel: "completeLabel", cancelLabel: "cancelLabel", hideCancel: "hideCancel", hideProgress: "hideProgress", footerLayout: "footerLayout", validationDisplay: "validationDisplay", progressLayout: "progressLayout" }, outputs: { complete: "complete", cancel: "cancel", event: "event" }, providers: [PathFacade], queries: [{ propertyName: "customHeader", first: true, predicate: PathShellHeaderDirective, descendants: true }, { propertyName: "customFooter", first: true, predicate: PathShellFooterDirective, descendants: true }, { propertyName: "stepDirectives", predicate: PathStepDirective }], usesOnChanges: true, ngImport: i0, template: `
|
|
225
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: PathShellComponent, isStandalone: true, selector: "pw-shell", inputs: { path: "path", engine: "engine", initialData: "initialData", autoStart: "autoStart", backLabel: "backLabel", nextLabel: "nextLabel", completeLabel: "completeLabel", loadingLabel: "loadingLabel", cancelLabel: "cancelLabel", hideCancel: "hideCancel", hideProgress: "hideProgress", footerLayout: "footerLayout", validationDisplay: "validationDisplay", progressLayout: "progressLayout" }, outputs: { complete: "complete", cancel: "cancel", event: "event" }, providers: [PathFacade], queries: [{ propertyName: "customHeader", first: true, predicate: PathShellHeaderDirective, descendants: true }, { propertyName: "customFooter", first: true, predicate: PathShellFooterDirective, descendants: true }, { propertyName: "stepDirectives", predicate: PathStepDirective }], usesOnChanges: true, ngImport: i0, template: `
|
|
226
226
|
<!-- Empty state -->
|
|
227
227
|
<div class="pw-shell" *ngIf="!(facade.state$ | async)">
|
|
228
228
|
<div class="pw-shell__empty" *ngIf="!started">
|
|
@@ -296,10 +296,43 @@ export class PathShellComponent {
|
|
|
296
296
|
</li>
|
|
297
297
|
</ul>
|
|
298
298
|
|
|
299
|
+
<!-- Blocking error — guard returned { allowed: false, reason } -->
|
|
300
|
+
<p class="pw-shell__blocking-error"
|
|
301
|
+
*ngIf="validationDisplay !== 'inline' && s.hasAttemptedNext && s.blockingError">
|
|
302
|
+
{{ s.blockingError }}
|
|
303
|
+
</p>
|
|
304
|
+
|
|
305
|
+
<!-- Error panel — replaces footer when an async operation has failed -->
|
|
306
|
+
<div class="pw-shell__error" *ngIf="s.status === 'error' && s.error; else footerOrCustom">
|
|
307
|
+
<div class="pw-shell__error-title">{{ s.error!.retryCount >= 2 ? 'Still having trouble.' : 'Something went wrong.' }}</div>
|
|
308
|
+
<div class="pw-shell__error-message">{{ errorPhaseMessage(s.error!.phase) }}{{ s.error!.message ? ' ' + s.error!.message : '' }}</div>
|
|
309
|
+
<div class="pw-shell__error-actions">
|
|
310
|
+
<button
|
|
311
|
+
*ngIf="s.error!.retryCount < 2"
|
|
312
|
+
type="button"
|
|
313
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
314
|
+
(click)="facade.retry()"
|
|
315
|
+
>Try again</button>
|
|
316
|
+
<button
|
|
317
|
+
*ngIf="s.hasPersistence"
|
|
318
|
+
type="button"
|
|
319
|
+
[class]="'pw-shell__btn ' + (s.error!.retryCount >= 2 ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend')"
|
|
320
|
+
(click)="facade.suspend()"
|
|
321
|
+
>Save and come back later</button>
|
|
322
|
+
<button
|
|
323
|
+
*ngIf="s.error!.retryCount >= 2 && !s.hasPersistence"
|
|
324
|
+
type="button"
|
|
325
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
326
|
+
(click)="facade.retry()"
|
|
327
|
+
>Try again</button>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
299
330
|
<!-- Footer — custom or default navigation buttons -->
|
|
300
|
-
<ng-
|
|
301
|
-
<ng-container *
|
|
302
|
-
|
|
331
|
+
<ng-template #footerOrCustom>
|
|
332
|
+
<ng-container *ngIf="customFooter; else defaultFooter">
|
|
333
|
+
<ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
|
|
334
|
+
</ng-container>
|
|
335
|
+
</ng-template>
|
|
303
336
|
<ng-template #defaultFooter>
|
|
304
337
|
<div class="pw-shell__footer">
|
|
305
338
|
<div class="pw-shell__footer-left">
|
|
@@ -308,7 +341,7 @@ export class PathShellComponent {
|
|
|
308
341
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
309
342
|
type="button"
|
|
310
343
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
311
|
-
[disabled]="s.
|
|
344
|
+
[disabled]="s.status !== 'idle'"
|
|
312
345
|
(click)="facade.cancel()"
|
|
313
346
|
>{{ cancelLabel }}</button>
|
|
314
347
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -316,7 +349,7 @@ export class PathShellComponent {
|
|
|
316
349
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
317
350
|
type="button"
|
|
318
351
|
class="pw-shell__btn pw-shell__btn--back"
|
|
319
|
-
[disabled]="s.
|
|
352
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
320
353
|
(click)="facade.previous()"
|
|
321
354
|
>{{ backLabel }}</button>
|
|
322
355
|
</div>
|
|
@@ -326,16 +359,17 @@ export class PathShellComponent {
|
|
|
326
359
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
327
360
|
type="button"
|
|
328
361
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
329
|
-
[disabled]="s.
|
|
362
|
+
[disabled]="s.status !== 'idle'"
|
|
330
363
|
(click)="facade.cancel()"
|
|
331
364
|
>{{ cancelLabel }}</button>
|
|
332
365
|
<!-- Both modes: Submit on the right -->
|
|
333
366
|
<button
|
|
334
367
|
type="button"
|
|
335
368
|
class="pw-shell__btn pw-shell__btn--next"
|
|
336
|
-
[
|
|
369
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
370
|
+
[disabled]="s.status !== 'idle'"
|
|
337
371
|
(click)="facade.next()"
|
|
338
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
372
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
339
373
|
</div>
|
|
340
374
|
</div>
|
|
341
375
|
</ng-template>
|
|
@@ -424,10 +458,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
424
458
|
</li>
|
|
425
459
|
</ul>
|
|
426
460
|
|
|
461
|
+
<!-- Blocking error — guard returned { allowed: false, reason } -->
|
|
462
|
+
<p class="pw-shell__blocking-error"
|
|
463
|
+
*ngIf="validationDisplay !== 'inline' && s.hasAttemptedNext && s.blockingError">
|
|
464
|
+
{{ s.blockingError }}
|
|
465
|
+
</p>
|
|
466
|
+
|
|
467
|
+
<!-- Error panel — replaces footer when an async operation has failed -->
|
|
468
|
+
<div class="pw-shell__error" *ngIf="s.status === 'error' && s.error; else footerOrCustom">
|
|
469
|
+
<div class="pw-shell__error-title">{{ s.error!.retryCount >= 2 ? 'Still having trouble.' : 'Something went wrong.' }}</div>
|
|
470
|
+
<div class="pw-shell__error-message">{{ errorPhaseMessage(s.error!.phase) }}{{ s.error!.message ? ' ' + s.error!.message : '' }}</div>
|
|
471
|
+
<div class="pw-shell__error-actions">
|
|
472
|
+
<button
|
|
473
|
+
*ngIf="s.error!.retryCount < 2"
|
|
474
|
+
type="button"
|
|
475
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
476
|
+
(click)="facade.retry()"
|
|
477
|
+
>Try again</button>
|
|
478
|
+
<button
|
|
479
|
+
*ngIf="s.hasPersistence"
|
|
480
|
+
type="button"
|
|
481
|
+
[class]="'pw-shell__btn ' + (s.error!.retryCount >= 2 ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend')"
|
|
482
|
+
(click)="facade.suspend()"
|
|
483
|
+
>Save and come back later</button>
|
|
484
|
+
<button
|
|
485
|
+
*ngIf="s.error!.retryCount >= 2 && !s.hasPersistence"
|
|
486
|
+
type="button"
|
|
487
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
488
|
+
(click)="facade.retry()"
|
|
489
|
+
>Try again</button>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
427
492
|
<!-- Footer — custom or default navigation buttons -->
|
|
428
|
-
<ng-
|
|
429
|
-
<ng-container *
|
|
430
|
-
|
|
493
|
+
<ng-template #footerOrCustom>
|
|
494
|
+
<ng-container *ngIf="customFooter; else defaultFooter">
|
|
495
|
+
<ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
|
|
496
|
+
</ng-container>
|
|
497
|
+
</ng-template>
|
|
431
498
|
<ng-template #defaultFooter>
|
|
432
499
|
<div class="pw-shell__footer">
|
|
433
500
|
<div class="pw-shell__footer-left">
|
|
@@ -436,7 +503,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
436
503
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
437
504
|
type="button"
|
|
438
505
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
439
|
-
[disabled]="s.
|
|
506
|
+
[disabled]="s.status !== 'idle'"
|
|
440
507
|
(click)="facade.cancel()"
|
|
441
508
|
>{{ cancelLabel }}</button>
|
|
442
509
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -444,7 +511,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
444
511
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
445
512
|
type="button"
|
|
446
513
|
class="pw-shell__btn pw-shell__btn--back"
|
|
447
|
-
[disabled]="s.
|
|
514
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
448
515
|
(click)="facade.previous()"
|
|
449
516
|
>{{ backLabel }}</button>
|
|
450
517
|
</div>
|
|
@@ -454,16 +521,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
454
521
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
455
522
|
type="button"
|
|
456
523
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
457
|
-
[disabled]="s.
|
|
524
|
+
[disabled]="s.status !== 'idle'"
|
|
458
525
|
(click)="facade.cancel()"
|
|
459
526
|
>{{ cancelLabel }}</button>
|
|
460
527
|
<!-- Both modes: Submit on the right -->
|
|
461
528
|
<button
|
|
462
529
|
type="button"
|
|
463
530
|
class="pw-shell__btn pw-shell__btn--next"
|
|
464
|
-
[
|
|
531
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
532
|
+
[disabled]="s.status !== 'idle'"
|
|
465
533
|
(click)="facade.next()"
|
|
466
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
534
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
467
535
|
</div>
|
|
468
536
|
</div>
|
|
469
537
|
</ng-template>
|
|
@@ -484,6 +552,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
484
552
|
type: Input
|
|
485
553
|
}], completeLabel: [{
|
|
486
554
|
type: Input
|
|
555
|
+
}], loadingLabel: [{
|
|
556
|
+
type: Input
|
|
487
557
|
}], cancelLabel: [{
|
|
488
558
|
type: Input
|
|
489
559
|
}], hideCancel: [{
|
package/dist/shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EAET,KAAK,EACL,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,eAAe,EAMf,MAAM,EACN,QAAQ,EACR,uBAAuB,EACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Angular adapter for @daltonr/pathwrite-core — RxJS observables, signal-friendly, with optional <pw-shell> default UI.",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@angular/compiler-cli": "^17.3.12"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@daltonr/pathwrite-core": "^0.
|
|
64
|
+
"@daltonr/pathwrite-core": "^0.10.0"
|
|
65
65
|
},
|
|
66
66
|
"publishConfig": {
|
|
67
67
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -98,6 +98,16 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
|
|
|
98
98
|
return this._engine.restart();
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/** Re-runs the operation that set `snapshot().error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. */
|
|
102
|
+
public retry(): Promise<void> {
|
|
103
|
+
return this._engine.retry();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Pauses the path with intent to return. Emits `suspended`. All state is preserved. */
|
|
107
|
+
public suspend(): Promise<void> {
|
|
108
|
+
return this._engine.suspend();
|
|
109
|
+
}
|
|
110
|
+
|
|
101
111
|
public startSubPath(path: PathDefinition<any>, initialData: PathData = {}, meta?: Record<string, unknown>): Promise<void> {
|
|
102
112
|
return this._engine.startSubPath(path, initialData, meta);
|
|
103
113
|
}
|
|
@@ -141,15 +151,15 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
|
|
|
141
151
|
}
|
|
142
152
|
|
|
143
153
|
// ---------------------------------------------------------------------------
|
|
144
|
-
//
|
|
154
|
+
// usePathContext() - Signal-based path access
|
|
145
155
|
// ---------------------------------------------------------------------------
|
|
146
156
|
|
|
147
157
|
/**
|
|
148
|
-
* Return type of `
|
|
158
|
+
* Return type of `usePathContext()`. Provides signal-based reactive access to the
|
|
149
159
|
* path state and strongly-typed navigation actions. Mirrors React's `usePathContext()`
|
|
150
160
|
* return type for consistency across adapters.
|
|
151
161
|
*/
|
|
152
|
-
export interface
|
|
162
|
+
export interface UsePathContextReturn<TData extends PathData = PathData> {
|
|
153
163
|
/** Current path snapshot as a signal. Returns `null` when no path is active. */
|
|
154
164
|
snapshot: Signal<PathSnapshot<TData> | null>;
|
|
155
165
|
/** Start (or restart) a path. */
|
|
@@ -175,16 +185,20 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
175
185
|
* Use for "Start over" / retry flows.
|
|
176
186
|
*/
|
|
177
187
|
restart: () => Promise<void>;
|
|
188
|
+
/** Re-run the operation that set `snapshot().error`. */
|
|
189
|
+
retry: () => Promise<void>;
|
|
190
|
+
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
191
|
+
suspend: () => Promise<void>;
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
/**
|
|
181
|
-
*
|
|
195
|
+
* Access the nearest `PathFacade`'s path instance for use in Angular step components.
|
|
182
196
|
* Requires `PathFacade` to be provided in the component's injector tree (either via
|
|
183
197
|
* `providers: [PathFacade]` in the component or a parent component).
|
|
184
198
|
*
|
|
185
199
|
* **This is the recommended way to consume Pathwrite in Angular components** — it
|
|
186
|
-
* provides the same ergonomic
|
|
187
|
-
*
|
|
200
|
+
* provides the same ergonomic API as React's `usePathContext()` and Vue's `usePathContext()`.
|
|
201
|
+
* No template references or manual facade injection needed.
|
|
188
202
|
*
|
|
189
203
|
* The optional generic `TData` narrows `snapshot().data` and `setData()` to your
|
|
190
204
|
* data shape. It is a **type-level assertion**, not a runtime guarantee.
|
|
@@ -203,7 +217,7 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
203
217
|
* `
|
|
204
218
|
* })
|
|
205
219
|
* export class ContactStepComponent {
|
|
206
|
-
* protected readonly path =
|
|
220
|
+
* protected readonly path = usePathContext<ContactData>();
|
|
207
221
|
*
|
|
208
222
|
* updateName(name: string) {
|
|
209
223
|
* this.path.setData('name', name);
|
|
@@ -213,12 +227,12 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
213
227
|
*
|
|
214
228
|
* @throws Error if PathFacade is not provided in the injector tree
|
|
215
229
|
*/
|
|
216
|
-
export function
|
|
230
|
+
export function usePathContext<TData extends PathData = PathData>(): UsePathContextReturn<TData> {
|
|
217
231
|
const facade = inject(PathFacade, { optional: true }) as PathFacade<TData> | null;
|
|
218
|
-
|
|
232
|
+
|
|
219
233
|
if (!facade) {
|
|
220
234
|
throw new Error(
|
|
221
|
-
"
|
|
235
|
+
"usePathContext() requires PathFacade to be provided. " +
|
|
222
236
|
"Add 'providers: [PathFacade]' to your component or a parent component."
|
|
223
237
|
);
|
|
224
238
|
}
|
|
@@ -235,6 +249,8 @@ export function injectPath<TData extends PathData = PathData>(): InjectPathRetur
|
|
|
235
249
|
goToStep: (stepId) => facade.goToStep(stepId),
|
|
236
250
|
goToStepChecked: (stepId) => facade.goToStepChecked(stepId),
|
|
237
251
|
restart: () => facade.restart(),
|
|
252
|
+
retry: () => facade.retry(),
|
|
253
|
+
suspend: () => facade.suspend(),
|
|
238
254
|
};
|
|
239
255
|
}
|
|
240
256
|
|
package/src/shell.ts
CHANGED
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
PathEvent,
|
|
27
27
|
PathSnapshot,
|
|
28
28
|
ProgressLayout,
|
|
29
|
-
RootProgress
|
|
29
|
+
RootProgress,
|
|
30
|
+
formatFieldKey,
|
|
31
|
+
errorPhaseMessage,
|
|
30
32
|
} from "@daltonr/pathwrite-core";
|
|
31
33
|
import { PathFacade } from "./index";
|
|
32
34
|
|
|
@@ -48,6 +50,10 @@ export interface PathShellActions {
|
|
|
48
50
|
setData: (key: string, value: unknown) => Promise<void>;
|
|
49
51
|
/** Restart the shell's current path with its current `initialData`. */
|
|
50
52
|
restart: () => Promise<void>;
|
|
53
|
+
/** Re-run the operation that set `snapshot.error`. */
|
|
54
|
+
retry: () => Promise<void>;
|
|
55
|
+
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
56
|
+
suspend: () => Promise<void>;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
// ---------------------------------------------------------------------------
|
|
@@ -216,10 +222,43 @@ export class PathShellFooterDirective {
|
|
|
216
222
|
</li>
|
|
217
223
|
</ul>
|
|
218
224
|
|
|
225
|
+
<!-- Blocking error — guard returned { allowed: false, reason } -->
|
|
226
|
+
<p class="pw-shell__blocking-error"
|
|
227
|
+
*ngIf="validationDisplay !== 'inline' && s.hasAttemptedNext && s.blockingError">
|
|
228
|
+
{{ s.blockingError }}
|
|
229
|
+
</p>
|
|
230
|
+
|
|
231
|
+
<!-- Error panel — replaces footer when an async operation has failed -->
|
|
232
|
+
<div class="pw-shell__error" *ngIf="s.status === 'error' && s.error; else footerOrCustom">
|
|
233
|
+
<div class="pw-shell__error-title">{{ s.error!.retryCount >= 2 ? 'Still having trouble.' : 'Something went wrong.' }}</div>
|
|
234
|
+
<div class="pw-shell__error-message">{{ errorPhaseMessage(s.error!.phase) }}{{ s.error!.message ? ' ' + s.error!.message : '' }}</div>
|
|
235
|
+
<div class="pw-shell__error-actions">
|
|
236
|
+
<button
|
|
237
|
+
*ngIf="s.error!.retryCount < 2"
|
|
238
|
+
type="button"
|
|
239
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
240
|
+
(click)="facade.retry()"
|
|
241
|
+
>Try again</button>
|
|
242
|
+
<button
|
|
243
|
+
*ngIf="s.hasPersistence"
|
|
244
|
+
type="button"
|
|
245
|
+
[class]="'pw-shell__btn ' + (s.error!.retryCount >= 2 ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend')"
|
|
246
|
+
(click)="facade.suspend()"
|
|
247
|
+
>Save and come back later</button>
|
|
248
|
+
<button
|
|
249
|
+
*ngIf="s.error!.retryCount >= 2 && !s.hasPersistence"
|
|
250
|
+
type="button"
|
|
251
|
+
class="pw-shell__btn pw-shell__btn--retry"
|
|
252
|
+
(click)="facade.retry()"
|
|
253
|
+
>Try again</button>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
219
256
|
<!-- Footer — custom or default navigation buttons -->
|
|
220
|
-
<ng-
|
|
221
|
-
<ng-container *
|
|
222
|
-
|
|
257
|
+
<ng-template #footerOrCustom>
|
|
258
|
+
<ng-container *ngIf="customFooter; else defaultFooter">
|
|
259
|
+
<ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
|
|
260
|
+
</ng-container>
|
|
261
|
+
</ng-template>
|
|
223
262
|
<ng-template #defaultFooter>
|
|
224
263
|
<div class="pw-shell__footer">
|
|
225
264
|
<div class="pw-shell__footer-left">
|
|
@@ -228,7 +267,7 @@ export class PathShellFooterDirective {
|
|
|
228
267
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
229
268
|
type="button"
|
|
230
269
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
231
|
-
[disabled]="s.
|
|
270
|
+
[disabled]="s.status !== 'idle'"
|
|
232
271
|
(click)="facade.cancel()"
|
|
233
272
|
>{{ cancelLabel }}</button>
|
|
234
273
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -236,7 +275,7 @@ export class PathShellFooterDirective {
|
|
|
236
275
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
237
276
|
type="button"
|
|
238
277
|
class="pw-shell__btn pw-shell__btn--back"
|
|
239
|
-
[disabled]="s.
|
|
278
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
240
279
|
(click)="facade.previous()"
|
|
241
280
|
>{{ backLabel }}</button>
|
|
242
281
|
</div>
|
|
@@ -246,16 +285,17 @@ export class PathShellFooterDirective {
|
|
|
246
285
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
247
286
|
type="button"
|
|
248
287
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
249
|
-
[disabled]="s.
|
|
288
|
+
[disabled]="s.status !== 'idle'"
|
|
250
289
|
(click)="facade.cancel()"
|
|
251
290
|
>{{ cancelLabel }}</button>
|
|
252
291
|
<!-- Both modes: Submit on the right -->
|
|
253
292
|
<button
|
|
254
293
|
type="button"
|
|
255
294
|
class="pw-shell__btn pw-shell__btn--next"
|
|
256
|
-
[
|
|
295
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
296
|
+
[disabled]="s.status !== 'idle'"
|
|
257
297
|
(click)="facade.next()"
|
|
258
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
298
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
259
299
|
</div>
|
|
260
300
|
</div>
|
|
261
301
|
</ng-template>
|
|
@@ -290,6 +330,8 @@ export class PathShellComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
290
330
|
@Input() nextLabel = "Next";
|
|
291
331
|
/** Label for the Next button when on the last step. */
|
|
292
332
|
@Input() completeLabel = "Complete";
|
|
333
|
+
/** Label shown on the Next/Complete button while an async operation is in progress. When undefined, the button keeps its label and shows a CSS spinner only. */
|
|
334
|
+
@Input() loadingLabel?: string;
|
|
293
335
|
/** Label for the Cancel button. */
|
|
294
336
|
@Input() cancelLabel = "Cancel";
|
|
295
337
|
/** Hide the Cancel button entirely. */
|
|
@@ -342,6 +384,8 @@ export class PathShellComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
342
384
|
goToStepChecked: (id) => this.facade.goToStepChecked(id),
|
|
343
385
|
setData: (key, value) => this.facade.setData(key, value as never),
|
|
344
386
|
restart: () => this.facade.restart(),
|
|
387
|
+
retry: () => this.facade.retry(),
|
|
388
|
+
suspend: () => this.facade.suspend(),
|
|
345
389
|
};
|
|
346
390
|
|
|
347
391
|
private readonly destroy$ = new Subject<void>();
|
|
@@ -405,9 +449,6 @@ export class PathShellComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
405
449
|
: this.footerLayout;
|
|
406
450
|
}
|
|
407
451
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
protected formatFieldKey(key: string): string {
|
|
411
|
-
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
412
|
-
}
|
|
452
|
+
protected errorPhaseMessage = errorPhaseMessage;
|
|
453
|
+
protected formatFieldKey = formatFieldKey;
|
|
413
454
|
}
|