@daltonr/pathwrite-angular 0.8.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/README.md +22 -27
- package/dist/index.css +86 -0
- package/dist/index.d.ts +18 -10
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/shell.d.ts +34 -13
- package/dist/shell.js +116 -38
- package/dist/shell.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +31 -15
- package/src/shell.ts +94 -28
package/README.md
CHANGED
|
@@ -410,7 +410,7 @@ import {
|
|
|
410
410
|
@Component({
|
|
411
411
|
imports: [PathShellComponent, PathStepDirective],
|
|
412
412
|
template: `
|
|
413
|
-
<pw-shell [path]="myPath" [initialData]="{ name: '' }" (
|
|
413
|
+
<pw-shell [path]="myPath" [initialData]="{ name: '' }" (complete)="onDone($event)">
|
|
414
414
|
<ng-template pwStep="details"><app-details-form /></ng-template>
|
|
415
415
|
<ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
416
416
|
</pw-shell>
|
|
@@ -484,7 +484,7 @@ The parent component hosting `<pw-shell>` does **not** need its own
|
|
|
484
484
|
@Component({
|
|
485
485
|
imports: [PathShellComponent, PathStepDirective],
|
|
486
486
|
template: `
|
|
487
|
-
<pw-shell #shell [path]="myPath" (
|
|
487
|
+
<pw-shell #shell [path]="myPath" (complete)="onDone($event)">
|
|
488
488
|
<ng-template pwStep="details"><app-details-form /></ng-template>
|
|
489
489
|
</pw-shell>
|
|
490
490
|
`
|
|
@@ -499,9 +499,10 @@ export class MyComponent {
|
|
|
499
499
|
|
|
500
500
|
| Input | Type | Default | Description |
|
|
501
501
|
|-------|------|---------|-------------|
|
|
502
|
-
| `path` | `PathDefinition` |
|
|
502
|
+
| `path` | `PathDefinition` | — | The path definition to drive. Required unless `[engine]` is provided. |
|
|
503
|
+
| `engine` | `PathEngine` | — | An externally-managed engine (e.g. from `restoreOrStart()`). When provided, `autoStart` is suppressed and the shell immediately reflects the engine's current state. Use `@if (engine)` in the parent template to gate mounting until the engine is ready. |
|
|
503
504
|
| `initialData` | `PathData` | `{}` | Initial data passed to `facade.start()`. |
|
|
504
|
-
| `autoStart` | `boolean` | `true` | Start the path automatically on `ngOnInit`. |
|
|
505
|
+
| `autoStart` | `boolean` | `true` | Start the path automatically on `ngOnInit`. Ignored when `[engine]` is provided. |
|
|
505
506
|
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
506
507
|
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
507
508
|
| `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
|
|
@@ -514,9 +515,9 @@ export class MyComponent {
|
|
|
514
515
|
|
|
515
516
|
| Output | Payload | Description |
|
|
516
517
|
|--------|---------|-------------|
|
|
517
|
-
| `(
|
|
518
|
-
| `(
|
|
519
|
-
| `(
|
|
518
|
+
| `(complete)` | `PathData` | Emitted when the path finishes naturally. |
|
|
519
|
+
| `(cancel)` | `PathData` | Emitted when the path is cancelled. |
|
|
520
|
+
| `(event)` | `PathEvent` | Emitted for every engine event. |
|
|
520
521
|
|
|
521
522
|
### Customising the header and footer
|
|
522
523
|
|
|
@@ -576,7 +577,7 @@ Toggle an `@if` flag to destroy and recreate the shell. Every child component re
|
|
|
576
577
|
|
|
577
578
|
```html
|
|
578
579
|
@if (isActive) {
|
|
579
|
-
<pw-shell [path]="myPath" (
|
|
580
|
+
<pw-shell [path]="myPath" (complete)="isActive = false" (cancel)="isActive = false">
|
|
580
581
|
<ng-template pwStep="details"><app-details-form /></ng-template>
|
|
581
582
|
</pw-shell>
|
|
582
583
|
} @else {
|
|
@@ -589,7 +590,7 @@ Toggle an `@if` flag to destroy and recreate the shell. Every child component re
|
|
|
589
590
|
Use the existing `#shell` template reference — `restart()` is a public method:
|
|
590
591
|
|
|
591
592
|
```html
|
|
592
|
-
<pw-shell #shell [path]="myPath" (
|
|
593
|
+
<pw-shell #shell [path]="myPath" (complete)="onDone($event)">
|
|
593
594
|
<ng-template pwStep="details"><app-details-form /></ng-template>
|
|
594
595
|
</pw-shell>
|
|
595
596
|
|
|
@@ -872,24 +873,24 @@ Use it to distinguish initialization from re-entry:
|
|
|
872
873
|
|
|
873
874
|
## Persistence
|
|
874
875
|
|
|
875
|
-
Use with [@daltonr/pathwrite-store
|
|
876
|
+
Use with [@daltonr/pathwrite-store](../store) for automatic state persistence. The engine is created externally via `restoreOrStart()` and passed directly to `<pw-shell>` via the `[engine]` input.
|
|
876
877
|
|
|
877
878
|
### Simple persistence example
|
|
878
879
|
|
|
879
880
|
```typescript
|
|
880
|
-
import { Component,
|
|
881
|
-
import {
|
|
882
|
-
import {
|
|
881
|
+
import { Component, OnInit } from '@angular/core';
|
|
882
|
+
import { PathEngine } from '@daltonr/pathwrite-angular';
|
|
883
|
+
import { PathShellComponent, PathStepDirective } from '@daltonr/pathwrite-angular/shell';
|
|
884
|
+
import { HttpStore, restoreOrStart, persistence } from '@daltonr/pathwrite-store';
|
|
883
885
|
import { signupPath } from './signup-path';
|
|
884
886
|
|
|
885
887
|
@Component({
|
|
886
888
|
selector: 'app-signup-wizard',
|
|
887
889
|
standalone: true,
|
|
888
890
|
imports: [PathShellComponent, PathStepDirective],
|
|
889
|
-
providers: [PathFacade],
|
|
890
891
|
template: `
|
|
891
|
-
@if (
|
|
892
|
-
<pw-shell [path]="path" [
|
|
892
|
+
@if (engine) {
|
|
893
|
+
<pw-shell [path]="path" [engine]="engine" (complete)="onComplete($event)">
|
|
893
894
|
<ng-template pwStep="details"><app-details-form /></ng-template>
|
|
894
895
|
<ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
895
896
|
</pw-shell>
|
|
@@ -899,10 +900,8 @@ import { signupPath } from './signup-path';
|
|
|
899
900
|
`
|
|
900
901
|
})
|
|
901
902
|
export class SignupWizardComponent implements OnInit {
|
|
902
|
-
private readonly facade = inject(PathFacade);
|
|
903
|
-
|
|
904
903
|
path = signupPath;
|
|
905
|
-
|
|
904
|
+
engine: PathEngine | null = null;
|
|
906
905
|
|
|
907
906
|
async ngOnInit() {
|
|
908
907
|
const store = new HttpStore({ baseUrl: '/api/wizard' });
|
|
@@ -914,14 +913,11 @@ export class SignupWizardComponent implements OnInit {
|
|
|
914
913
|
path: this.path,
|
|
915
914
|
initialData: { name: '', email: '' },
|
|
916
915
|
observers: [
|
|
917
|
-
|
|
916
|
+
persistence({ store, key, strategy: 'onNext' })
|
|
918
917
|
]
|
|
919
918
|
});
|
|
920
919
|
|
|
921
|
-
|
|
922
|
-
// reflects the engine's current state and forwards all events.
|
|
923
|
-
this.facade.adoptEngine(engine);
|
|
924
|
-
this.ready = true;
|
|
920
|
+
this.engine = engine;
|
|
925
921
|
|
|
926
922
|
if (restored) {
|
|
927
923
|
console.log('Progress restored — resuming from', engine.snapshot()?.stepId);
|
|
@@ -936,9 +932,8 @@ export class SignupWizardComponent implements OnInit {
|
|
|
936
932
|
|
|
937
933
|
### Key points
|
|
938
934
|
|
|
939
|
-
- **`
|
|
940
|
-
-
|
|
941
|
-
- **The facade is the same one the shell uses** (provided in the component's `providers`), so the shell's template bindings, progress indicator, and navigation buttons all work automatically.
|
|
935
|
+
- **`[engine]`** passes an externally-managed engine directly to the shell. The shell adopts it immediately and skips `autoStart`.
|
|
936
|
+
- **`@if (engine)`** acts as the async gate — the shell only mounts once the engine is ready, so there is no timing window where `autoStart` could fire before the engine arrives.
|
|
942
937
|
- **`restoreOrStart()`** handles the load-or-create logic: if saved state exists on the server, it restores the engine mid-flow; otherwise it starts fresh.
|
|
943
938
|
|
|
944
939
|
---
|
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
|
@@ -29,7 +29,7 @@ export declare class PathFacade<TData extends PathData = PathData> implements On
|
|
|
29
29
|
constructor();
|
|
30
30
|
/**
|
|
31
31
|
* Adopt an externally-managed `PathEngine` — for example, the engine returned
|
|
32
|
-
* by `restoreOrStart()` from `@daltonr/pathwrite-store
|
|
32
|
+
* by `restoreOrStart()` from `@daltonr/pathwrite-store`.
|
|
33
33
|
*
|
|
34
34
|
* The facade immediately reflects the engine's current state and forwards all
|
|
35
35
|
* subsequent events. The **caller** is responsible for the engine's lifecycle
|
|
@@ -50,7 +50,11 @@ export declare class PathFacade<TData extends PathData = PathData> implements On
|
|
|
50
50
|
* Use for "Start over" / retry flows without destroying and re-creating the
|
|
51
51
|
* component that provides this facade.
|
|
52
52
|
*/
|
|
53
|
-
restart(
|
|
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. */
|
|
@@ -98,16 +102,20 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
98
102
|
* Tears down any active path and immediately starts the given path fresh.
|
|
99
103
|
* Use for "Start over" / retry flows.
|
|
100
104
|
*/
|
|
101
|
-
restart: (
|
|
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
|
@@ -31,7 +31,7 @@ export class PathFacade {
|
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Adopt an externally-managed `PathEngine` — for example, the engine returned
|
|
34
|
-
* by `restoreOrStart()` from `@daltonr/pathwrite-store
|
|
34
|
+
* by `restoreOrStart()` from `@daltonr/pathwrite-store`.
|
|
35
35
|
*
|
|
36
36
|
* The facade immediately reflects the engine's current state and forwards all
|
|
37
37
|
* subsequent events. The **caller** is responsible for the engine's lifecycle
|
|
@@ -80,8 +80,16 @@ export class PathFacade {
|
|
|
80
80
|
* Use for "Start over" / retry flows without destroying and re-creating the
|
|
81
81
|
* component that provides this facade.
|
|
82
82
|
*/
|
|
83
|
-
restart(
|
|
84
|
-
return this._engine.restart(
|
|
83
|
+
restart() {
|
|
84
|
+
return this._engine.restart();
|
|
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();
|
|
85
93
|
}
|
|
86
94
|
startSubPath(path, initialData = {}, meta) {
|
|
87
95
|
return this._engine.startSubPath(path, initialData, meta);
|
|
@@ -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 {
|
|
@@ -174,7 +182,9 @@ export function injectPath() {
|
|
|
174
182
|
resetStep: () => facade.resetStep(),
|
|
175
183
|
goToStep: (stepId) => facade.goToStep(stepId),
|
|
176
184
|
goToStepChecked: (stepId) => facade.goToStepChecked(stepId),
|
|
177
|
-
restart: (
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAyB,MAAM,EAAU,MAAM,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAc,OAAO,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAGL,UAAU,EAGX,MAAM,yBAAyB,CAAC;;AAEjC;;;;;;;;;;;;;GAaG;AAEH,MAAM,OAAO,UAAU;IAYrB;QAXQ,YAAO,GAAG,IAAI,UAAU,EAAE,CAAC;QAClB,YAAO,GAAG,IAAI,eAAe,CAA6B,IAAI,CAAC,CAAC;QAChE,aAAQ,GAAG,IAAI,OAAO,EAAa,CAAC;QAC7C,2BAAsB,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;QACrC,iBAAY,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;QAEzD,WAAM,GAA2C,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QAC7E,YAAO,GAA0B,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAC9E,0FAA0F;QAC1E,gBAAW,GAAuC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;QAG/F,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,WAAW,CAAC,MAAkB;QACnC,+DAA+D;QAC/D,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAEO,aAAa,CAAC,MAAkB;QACtC,0EAA0E;QAC1E,kEAAkE;QAClE,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,EAAgC,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE/B,IAAI,CAAC,sBAAsB,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;YACvD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAA+B,CAAC,CAAC;gBACzD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,QAA+B,CAAC,CAAC;YAC/D,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACpE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,WAAW;QAChB,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAEM,KAAK,CAAC,IAAyB,EAAE,cAAwB,EAAE;QAChE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;OAKG;IACI,OAAO,CAAC,
|
|
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
|
-
import { TemplateRef, EventEmitter, QueryList, OnInit, OnDestroy, Injector } from "@angular/core";
|
|
2
|
-
import { PathData, PathDefinition, PathEvent, PathSnapshot, ProgressLayout } from "@daltonr/pathwrite-core";
|
|
1
|
+
import { TemplateRef, EventEmitter, QueryList, OnInit, OnChanges, OnDestroy, SimpleChanges, Injector } from "@angular/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.
|
|
@@ -90,15 +94,30 @@ export declare class PathShellFooterDirective {
|
|
|
90
94
|
* and navigation buttons.
|
|
91
95
|
*
|
|
92
96
|
* ```html
|
|
93
|
-
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (
|
|
97
|
+
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (complete)="onDone($event)">
|
|
94
98
|
* <ng-template pwStep="details"><app-details-form /></ng-template>
|
|
95
99
|
* <ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
96
100
|
* </pw-shell>
|
|
97
101
|
* ```
|
|
98
102
|
*/
|
|
99
|
-
export declare class PathShellComponent implements OnInit, OnDestroy {
|
|
100
|
-
/** The path definition to run. Required. */
|
|
101
|
-
path
|
|
103
|
+
export declare class PathShellComponent implements OnInit, OnChanges, OnDestroy {
|
|
104
|
+
/** The path definition to run. Required unless [engine] is provided. */
|
|
105
|
+
path?: PathDefinition<any>;
|
|
106
|
+
/**
|
|
107
|
+
* An externally-managed `PathEngine` to adopt — for example, the engine
|
|
108
|
+
* returned by `restoreOrStart()` from `@daltonr/pathwrite-store`.
|
|
109
|
+
*
|
|
110
|
+
* When provided the shell skips `autoStart` and immediately reflects the
|
|
111
|
+
* engine's current state. Gate the shell's existence on the engine being
|
|
112
|
+
* ready using `@if (engine)` so the input is always non-null on mount:
|
|
113
|
+
*
|
|
114
|
+
* ```html
|
|
115
|
+
* @if (engine) {
|
|
116
|
+
* <pw-shell [engine]="engine" [path]="myPath" ...></pw-shell>
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
engine?: PathEngine;
|
|
102
121
|
/** Initial data merged into the path engine on start. */
|
|
103
122
|
initialData: PathData;
|
|
104
123
|
/** Start the path automatically on ngOnInit. Set to false to call doStart() manually. */
|
|
@@ -109,6 +128,8 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
|
|
|
109
128
|
nextLabel: string;
|
|
110
129
|
/** Label for the Next button when on the last step. */
|
|
111
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;
|
|
112
133
|
/** Label for the Cancel button. */
|
|
113
134
|
cancelLabel: string;
|
|
114
135
|
/** Hide the Cancel button entirely. */
|
|
@@ -137,9 +158,9 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
|
|
|
137
158
|
* - "activeOnly": Only the active (sub-path) bar — root bar hidden.
|
|
138
159
|
*/
|
|
139
160
|
progressLayout: ProgressLayout;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
complete: EventEmitter<PathData>;
|
|
162
|
+
cancel: EventEmitter<PathData>;
|
|
163
|
+
event: EventEmitter<PathEvent>;
|
|
143
164
|
stepDirectives: QueryList<PathStepDirective>;
|
|
144
165
|
customHeader?: PathShellHeaderDirective;
|
|
145
166
|
customFooter?: PathShellFooterDirective;
|
|
@@ -151,6 +172,7 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
|
|
|
151
172
|
/** Navigation actions passed to custom `pwShellFooter` templates. */
|
|
152
173
|
protected readonly shellActions: PathShellActions;
|
|
153
174
|
private readonly destroy$;
|
|
175
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
154
176
|
ngOnInit(): void;
|
|
155
177
|
ngOnDestroy(): void;
|
|
156
178
|
doStart(): void;
|
|
@@ -170,9 +192,8 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
|
|
|
170
192
|
protected warningEntries(s: PathSnapshot): [string, string][];
|
|
171
193
|
/** Resolves "auto" footerLayout based on snapshot. Single-step top-level → "form", otherwise → "wizard". */
|
|
172
194
|
protected getResolvedFooterLayout(s: PathSnapshot): "wizard" | "form";
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
protected formatFieldKey(key: string): string;
|
|
195
|
+
protected errorPhaseMessage: typeof errorPhaseMessage;
|
|
196
|
+
protected formatFieldKey: typeof formatFieldKey;
|
|
176
197
|
static ɵfac: i0.ɵɵFactoryDeclaration<PathShellComponent, never>;
|
|
177
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<PathShellComponent, "pw-shell", never, { "path": { "alias": "path"; "required":
|
|
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>;
|
|
178
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";
|
|
@@ -97,7 +98,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
97
98
|
* and navigation buttons.
|
|
98
99
|
*
|
|
99
100
|
* ```html
|
|
100
|
-
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (
|
|
101
|
+
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (complete)="onDone($event)">
|
|
101
102
|
* <ng-template pwStep="details"><app-details-form /></ng-template>
|
|
102
103
|
* <ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
103
104
|
* </pw-shell>
|
|
@@ -134,7 +135,7 @@ export class PathShellComponent {
|
|
|
134
135
|
* - `"inline"`: Suppress the summary — handle errors inside the step template instead.
|
|
135
136
|
* - `"both"`: Render the shell summary AND whatever the step template renders.
|
|
136
137
|
*/
|
|
137
|
-
this.validationDisplay = "
|
|
138
|
+
this.validationDisplay = "summary";
|
|
138
139
|
/**
|
|
139
140
|
* Controls how progress bars are arranged when a sub-path is active.
|
|
140
141
|
* - "merged" (default): Root and sub-path bars in one card.
|
|
@@ -143,9 +144,9 @@ export class PathShellComponent {
|
|
|
143
144
|
* - "activeOnly": Only the active (sub-path) bar — root bar hidden.
|
|
144
145
|
*/
|
|
145
146
|
this.progressLayout = "merged";
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
148
|
-
this.
|
|
147
|
+
this.complete = new EventEmitter();
|
|
148
|
+
this.cancel = new EventEmitter();
|
|
149
|
+
this.event = new EventEmitter();
|
|
149
150
|
this.facade = inject(PathFacade);
|
|
150
151
|
/** The shell's own component-level injector. Passed to ngTemplateOutlet so that
|
|
151
152
|
* step components can resolve PathFacade (provided by this shell) via inject(). */
|
|
@@ -159,19 +160,28 @@ export class PathShellComponent {
|
|
|
159
160
|
goToStep: (id) => this.facade.goToStep(id),
|
|
160
161
|
goToStepChecked: (id) => this.facade.goToStepChecked(id),
|
|
161
162
|
setData: (key, value) => this.facade.setData(key, value),
|
|
162
|
-
restart: () => this.facade.restart(
|
|
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;
|
|
170
|
+
}
|
|
171
|
+
ngOnChanges(changes) {
|
|
172
|
+
if (changes['engine'] && this.engine) {
|
|
173
|
+
this.facade.adoptEngine(this.engine);
|
|
174
|
+
}
|
|
165
175
|
}
|
|
166
176
|
ngOnInit() {
|
|
167
177
|
this.facade.events$.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
|
168
|
-
this.
|
|
178
|
+
this.event.emit(event);
|
|
169
179
|
if (event.type === "completed")
|
|
170
|
-
this.
|
|
180
|
+
this.complete.emit(event.data);
|
|
171
181
|
if (event.type === "cancelled")
|
|
172
|
-
this.
|
|
182
|
+
this.cancel.emit(event.data);
|
|
173
183
|
});
|
|
174
|
-
if (this.autoStart) {
|
|
184
|
+
if (this.autoStart && !this.engine) {
|
|
175
185
|
this.doStart();
|
|
176
186
|
}
|
|
177
187
|
}
|
|
@@ -180,6 +190,8 @@ export class PathShellComponent {
|
|
|
180
190
|
this.destroy$.complete();
|
|
181
191
|
}
|
|
182
192
|
doStart() {
|
|
193
|
+
if (!this.path)
|
|
194
|
+
throw new Error('[pw-shell] [path] is required when no [engine] is provided');
|
|
183
195
|
this.started = true;
|
|
184
196
|
this.facade.start(this.path, this.initialData);
|
|
185
197
|
}
|
|
@@ -193,7 +205,7 @@ export class PathShellComponent {
|
|
|
193
205
|
* ```
|
|
194
206
|
*/
|
|
195
207
|
restart() {
|
|
196
|
-
return this.facade.restart(
|
|
208
|
+
return this.facade.restart();
|
|
197
209
|
}
|
|
198
210
|
/** Returns Object.entries(s.fieldErrors) for use in *ngFor. */
|
|
199
211
|
fieldEntries(s) {
|
|
@@ -209,13 +221,8 @@ export class PathShellComponent {
|
|
|
209
221
|
? (s.stepCount === 1 && s.nestingLevel === 0 ? "form" : "wizard")
|
|
210
222
|
: this.footerLayout;
|
|
211
223
|
}
|
|
212
|
-
/** Converts a camelCase or lowercase field key to a display label.
|
|
213
|
-
* e.g. "firstName" → "First Name", "email" → "Email" */
|
|
214
|
-
formatFieldKey(key) {
|
|
215
|
-
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
216
|
-
}
|
|
217
224
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PathShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
218
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: PathShellComponent, isStandalone: true, selector: "pw-shell", inputs: { path: "path", initialData: "initialData", autoStart: "autoStart", backLabel: "backLabel", nextLabel: "nextLabel", completeLabel: "completeLabel", cancelLabel: "cancelLabel", hideCancel: "hideCancel", hideProgress: "hideProgress", footerLayout: "footerLayout", validationDisplay: "validationDisplay", progressLayout: "progressLayout" }, outputs: {
|
|
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: `
|
|
219
226
|
<!-- Empty state -->
|
|
220
227
|
<div class="pw-shell" *ngIf="!(facade.state$ | async)">
|
|
221
228
|
<div class="pw-shell__empty" *ngIf="!started">
|
|
@@ -289,10 +296,43 @@ export class PathShellComponent {
|
|
|
289
296
|
</li>
|
|
290
297
|
</ul>
|
|
291
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>
|
|
292
330
|
<!-- Footer — custom or default navigation buttons -->
|
|
293
|
-
<ng-
|
|
294
|
-
<ng-container *
|
|
295
|
-
|
|
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>
|
|
296
336
|
<ng-template #defaultFooter>
|
|
297
337
|
<div class="pw-shell__footer">
|
|
298
338
|
<div class="pw-shell__footer-left">
|
|
@@ -301,7 +341,7 @@ export class PathShellComponent {
|
|
|
301
341
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
302
342
|
type="button"
|
|
303
343
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
304
|
-
[disabled]="s.
|
|
344
|
+
[disabled]="s.status !== 'idle'"
|
|
305
345
|
(click)="facade.cancel()"
|
|
306
346
|
>{{ cancelLabel }}</button>
|
|
307
347
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -309,7 +349,7 @@ export class PathShellComponent {
|
|
|
309
349
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
310
350
|
type="button"
|
|
311
351
|
class="pw-shell__btn pw-shell__btn--back"
|
|
312
|
-
[disabled]="s.
|
|
352
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
313
353
|
(click)="facade.previous()"
|
|
314
354
|
>{{ backLabel }}</button>
|
|
315
355
|
</div>
|
|
@@ -319,16 +359,17 @@ export class PathShellComponent {
|
|
|
319
359
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
320
360
|
type="button"
|
|
321
361
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
322
|
-
[disabled]="s.
|
|
362
|
+
[disabled]="s.status !== 'idle'"
|
|
323
363
|
(click)="facade.cancel()"
|
|
324
364
|
>{{ cancelLabel }}</button>
|
|
325
365
|
<!-- Both modes: Submit on the right -->
|
|
326
366
|
<button
|
|
327
367
|
type="button"
|
|
328
368
|
class="pw-shell__btn pw-shell__btn--next"
|
|
329
|
-
[
|
|
369
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
370
|
+
[disabled]="s.status !== 'idle'"
|
|
330
371
|
(click)="facade.next()"
|
|
331
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
372
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
332
373
|
</div>
|
|
333
374
|
</div>
|
|
334
375
|
</ng-template>
|
|
@@ -417,10 +458,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
417
458
|
</li>
|
|
418
459
|
</ul>
|
|
419
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>
|
|
420
492
|
<!-- Footer — custom or default navigation buttons -->
|
|
421
|
-
<ng-
|
|
422
|
-
<ng-container *
|
|
423
|
-
|
|
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>
|
|
424
498
|
<ng-template #defaultFooter>
|
|
425
499
|
<div class="pw-shell__footer">
|
|
426
500
|
<div class="pw-shell__footer-left">
|
|
@@ -429,7 +503,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
429
503
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
430
504
|
type="button"
|
|
431
505
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
432
|
-
[disabled]="s.
|
|
506
|
+
[disabled]="s.status !== 'idle'"
|
|
433
507
|
(click)="facade.cancel()"
|
|
434
508
|
>{{ cancelLabel }}</button>
|
|
435
509
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -437,7 +511,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
437
511
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
438
512
|
type="button"
|
|
439
513
|
class="pw-shell__btn pw-shell__btn--back"
|
|
440
|
-
[disabled]="s.
|
|
514
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
441
515
|
(click)="facade.previous()"
|
|
442
516
|
>{{ backLabel }}</button>
|
|
443
517
|
</div>
|
|
@@ -447,16 +521,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
447
521
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
448
522
|
type="button"
|
|
449
523
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
450
|
-
[disabled]="s.
|
|
524
|
+
[disabled]="s.status !== 'idle'"
|
|
451
525
|
(click)="facade.cancel()"
|
|
452
526
|
>{{ cancelLabel }}</button>
|
|
453
527
|
<!-- Both modes: Submit on the right -->
|
|
454
528
|
<button
|
|
455
529
|
type="button"
|
|
456
530
|
class="pw-shell__btn pw-shell__btn--next"
|
|
457
|
-
[
|
|
531
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
532
|
+
[disabled]="s.status !== 'idle'"
|
|
458
533
|
(click)="facade.next()"
|
|
459
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
534
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
460
535
|
</div>
|
|
461
536
|
</div>
|
|
462
537
|
</ng-template>
|
|
@@ -464,8 +539,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
464
539
|
`
|
|
465
540
|
}]
|
|
466
541
|
}], propDecorators: { path: [{
|
|
467
|
-
type: Input
|
|
468
|
-
|
|
542
|
+
type: Input
|
|
543
|
+
}], engine: [{
|
|
544
|
+
type: Input
|
|
469
545
|
}], initialData: [{
|
|
470
546
|
type: Input
|
|
471
547
|
}], autoStart: [{
|
|
@@ -476,6 +552,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
476
552
|
type: Input
|
|
477
553
|
}], completeLabel: [{
|
|
478
554
|
type: Input
|
|
555
|
+
}], loadingLabel: [{
|
|
556
|
+
type: Input
|
|
479
557
|
}], cancelLabel: [{
|
|
480
558
|
type: Input
|
|
481
559
|
}], hideCancel: [{
|
|
@@ -488,11 +566,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
488
566
|
type: Input
|
|
489
567
|
}], progressLayout: [{
|
|
490
568
|
type: Input
|
|
491
|
-
}],
|
|
569
|
+
}], complete: [{
|
|
492
570
|
type: Output
|
|
493
|
-
}],
|
|
571
|
+
}], cancel: [{
|
|
494
572
|
type: Output
|
|
495
|
-
}],
|
|
573
|
+
}], event: [{
|
|
496
574
|
type: Output
|
|
497
575
|
}], stepDirectives: [{
|
|
498
576
|
type: ContentChildren,
|
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,
|
|
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
|
@@ -41,7 +41,7 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Adopt an externally-managed `PathEngine` — for example, the engine returned
|
|
44
|
-
* by `restoreOrStart()` from `@daltonr/pathwrite-store
|
|
44
|
+
* by `restoreOrStart()` from `@daltonr/pathwrite-store`.
|
|
45
45
|
*
|
|
46
46
|
* The facade immediately reflects the engine's current state and forwards all
|
|
47
47
|
* subsequent events. The **caller** is responsible for the engine's lifecycle
|
|
@@ -94,8 +94,18 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
|
|
|
94
94
|
* Use for "Start over" / retry flows without destroying and re-creating the
|
|
95
95
|
* component that provides this facade.
|
|
96
96
|
*/
|
|
97
|
-
public restart(
|
|
98
|
-
return this._engine.restart(
|
|
97
|
+
public restart(): Promise<void> {
|
|
98
|
+
return this._engine.restart();
|
|
99
|
+
}
|
|
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();
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
public startSubPath(path: PathDefinition<any>, initialData: PathData = {}, meta?: Record<string, unknown>): Promise<void> {
|
|
@@ -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. */
|
|
@@ -174,17 +184,21 @@ export interface InjectPathReturn<TData extends PathData = PathData> {
|
|
|
174
184
|
* Tears down any active path and immediately starts the given path fresh.
|
|
175
185
|
* Use for "Start over" / retry flows.
|
|
176
186
|
*/
|
|
177
|
-
restart: (
|
|
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
|
}
|
|
@@ -234,7 +248,9 @@ export function injectPath<TData extends PathData = PathData>(): InjectPathRetur
|
|
|
234
248
|
resetStep: () => facade.resetStep(),
|
|
235
249
|
goToStep: (stepId) => facade.goToStep(stepId),
|
|
236
250
|
goToStepChecked: (stepId) => facade.goToStepChecked(stepId),
|
|
237
|
-
restart: (
|
|
251
|
+
restart: () => facade.restart(),
|
|
252
|
+
retry: () => facade.retry(),
|
|
253
|
+
suspend: () => facade.suspend(),
|
|
238
254
|
};
|
|
239
255
|
}
|
|
240
256
|
|
package/src/shell.ts
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
ContentChildren,
|
|
10
10
|
QueryList,
|
|
11
11
|
OnInit,
|
|
12
|
+
OnChanges,
|
|
12
13
|
OnDestroy,
|
|
14
|
+
SimpleChanges,
|
|
13
15
|
inject,
|
|
14
16
|
Injector,
|
|
15
17
|
ChangeDetectionStrategy
|
|
@@ -20,10 +22,13 @@ import { takeUntil } from "rxjs/operators";
|
|
|
20
22
|
import {
|
|
21
23
|
PathData,
|
|
22
24
|
PathDefinition,
|
|
25
|
+
PathEngine,
|
|
23
26
|
PathEvent,
|
|
24
27
|
PathSnapshot,
|
|
25
28
|
ProgressLayout,
|
|
26
|
-
RootProgress
|
|
29
|
+
RootProgress,
|
|
30
|
+
formatFieldKey,
|
|
31
|
+
errorPhaseMessage,
|
|
27
32
|
} from "@daltonr/pathwrite-core";
|
|
28
33
|
import { PathFacade } from "./index";
|
|
29
34
|
|
|
@@ -45,6 +50,10 @@ export interface PathShellActions {
|
|
|
45
50
|
setData: (key: string, value: unknown) => Promise<void>;
|
|
46
51
|
/** Restart the shell's current path with its current `initialData`. */
|
|
47
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>;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
// ---------------------------------------------------------------------------
|
|
@@ -127,7 +136,7 @@ export class PathShellFooterDirective {
|
|
|
127
136
|
* and navigation buttons.
|
|
128
137
|
*
|
|
129
138
|
* ```html
|
|
130
|
-
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (
|
|
139
|
+
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (complete)="onDone($event)">
|
|
131
140
|
* <ng-template pwStep="details"><app-details-form /></ng-template>
|
|
132
141
|
* <ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
133
142
|
* </pw-shell>
|
|
@@ -213,10 +222,43 @@ export class PathShellFooterDirective {
|
|
|
213
222
|
</li>
|
|
214
223
|
</ul>
|
|
215
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>
|
|
216
256
|
<!-- Footer — custom or default navigation buttons -->
|
|
217
|
-
<ng-
|
|
218
|
-
<ng-container *
|
|
219
|
-
|
|
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>
|
|
220
262
|
<ng-template #defaultFooter>
|
|
221
263
|
<div class="pw-shell__footer">
|
|
222
264
|
<div class="pw-shell__footer-left">
|
|
@@ -225,7 +267,7 @@ export class PathShellFooterDirective {
|
|
|
225
267
|
*ngIf="getResolvedFooterLayout(s) === 'form' && !hideCancel"
|
|
226
268
|
type="button"
|
|
227
269
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
228
|
-
[disabled]="s.
|
|
270
|
+
[disabled]="s.status !== 'idle'"
|
|
229
271
|
(click)="facade.cancel()"
|
|
230
272
|
>{{ cancelLabel }}</button>
|
|
231
273
|
<!-- Wizard mode: Back on the left -->
|
|
@@ -233,7 +275,7 @@ export class PathShellFooterDirective {
|
|
|
233
275
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !s.isFirstStep"
|
|
234
276
|
type="button"
|
|
235
277
|
class="pw-shell__btn pw-shell__btn--back"
|
|
236
|
-
[disabled]="s.
|
|
278
|
+
[disabled]="s.status !== 'idle' || !s.canMovePrevious"
|
|
237
279
|
(click)="facade.previous()"
|
|
238
280
|
>{{ backLabel }}</button>
|
|
239
281
|
</div>
|
|
@@ -243,25 +285,41 @@ export class PathShellFooterDirective {
|
|
|
243
285
|
*ngIf="getResolvedFooterLayout(s) === 'wizard' && !hideCancel"
|
|
244
286
|
type="button"
|
|
245
287
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
246
|
-
[disabled]="s.
|
|
288
|
+
[disabled]="s.status !== 'idle'"
|
|
247
289
|
(click)="facade.cancel()"
|
|
248
290
|
>{{ cancelLabel }}</button>
|
|
249
291
|
<!-- Both modes: Submit on the right -->
|
|
250
292
|
<button
|
|
251
293
|
type="button"
|
|
252
294
|
class="pw-shell__btn pw-shell__btn--next"
|
|
253
|
-
[
|
|
295
|
+
[class.pw-shell__btn--loading]="s.status !== 'idle'"
|
|
296
|
+
[disabled]="s.status !== 'idle'"
|
|
254
297
|
(click)="facade.next()"
|
|
255
|
-
>{{ s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
298
|
+
>{{ s.status !== 'idle' && loadingLabel ? loadingLabel : s.isLastStep ? completeLabel : nextLabel }}</button>
|
|
256
299
|
</div>
|
|
257
300
|
</div>
|
|
258
301
|
</ng-template>
|
|
259
302
|
</div>
|
|
260
303
|
`
|
|
261
304
|
})
|
|
262
|
-
export class PathShellComponent implements OnInit, OnDestroy {
|
|
263
|
-
/** The path definition to run. Required. */
|
|
264
|
-
@Input(
|
|
305
|
+
export class PathShellComponent implements OnInit, OnChanges, OnDestroy {
|
|
306
|
+
/** The path definition to run. Required unless [engine] is provided. */
|
|
307
|
+
@Input() path?: PathDefinition<any>;
|
|
308
|
+
/**
|
|
309
|
+
* An externally-managed `PathEngine` to adopt — for example, the engine
|
|
310
|
+
* returned by `restoreOrStart()` from `@daltonr/pathwrite-store`.
|
|
311
|
+
*
|
|
312
|
+
* When provided the shell skips `autoStart` and immediately reflects the
|
|
313
|
+
* engine's current state. Gate the shell's existence on the engine being
|
|
314
|
+
* ready using `@if (engine)` so the input is always non-null on mount:
|
|
315
|
+
*
|
|
316
|
+
* ```html
|
|
317
|
+
* @if (engine) {
|
|
318
|
+
* <pw-shell [engine]="engine" [path]="myPath" ...></pw-shell>
|
|
319
|
+
* }
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
@Input() engine?: PathEngine;
|
|
265
323
|
/** Initial data merged into the path engine on start. */
|
|
266
324
|
@Input() initialData: PathData = {};
|
|
267
325
|
/** Start the path automatically on ngOnInit. Set to false to call doStart() manually. */
|
|
@@ -272,6 +330,8 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
272
330
|
@Input() nextLabel = "Next";
|
|
273
331
|
/** Label for the Next button when on the last step. */
|
|
274
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;
|
|
275
335
|
/** Label for the Cancel button. */
|
|
276
336
|
@Input() cancelLabel = "Cancel";
|
|
277
337
|
/** Hide the Cancel button entirely. */
|
|
@@ -291,7 +351,7 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
291
351
|
* - `"inline"`: Suppress the summary — handle errors inside the step template instead.
|
|
292
352
|
* - `"both"`: Render the shell summary AND whatever the step template renders.
|
|
293
353
|
*/
|
|
294
|
-
@Input() validationDisplay: "summary" | "inline" | "both" = "
|
|
354
|
+
@Input() validationDisplay: "summary" | "inline" | "both" = "summary";
|
|
295
355
|
/**
|
|
296
356
|
* Controls how progress bars are arranged when a sub-path is active.
|
|
297
357
|
* - "merged" (default): Root and sub-path bars in one card.
|
|
@@ -301,9 +361,9 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
301
361
|
*/
|
|
302
362
|
@Input() progressLayout: ProgressLayout = "merged";
|
|
303
363
|
|
|
304
|
-
@Output()
|
|
305
|
-
@Output()
|
|
306
|
-
@Output()
|
|
364
|
+
@Output() complete = new EventEmitter<PathData>();
|
|
365
|
+
@Output() cancel = new EventEmitter<PathData>();
|
|
366
|
+
@Output() event = new EventEmitter<PathEvent>();
|
|
307
367
|
|
|
308
368
|
@ContentChildren(PathStepDirective) stepDirectives!: QueryList<PathStepDirective>;
|
|
309
369
|
@ContentChild(PathShellHeaderDirective) customHeader?: PathShellHeaderDirective;
|
|
@@ -323,19 +383,27 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
323
383
|
goToStep: (id) => this.facade.goToStep(id),
|
|
324
384
|
goToStepChecked: (id) => this.facade.goToStepChecked(id),
|
|
325
385
|
setData: (key, value) => this.facade.setData(key, value as never),
|
|
326
|
-
restart: () => this.facade.restart(
|
|
386
|
+
restart: () => this.facade.restart(),
|
|
387
|
+
retry: () => this.facade.retry(),
|
|
388
|
+
suspend: () => this.facade.suspend(),
|
|
327
389
|
};
|
|
328
390
|
|
|
329
391
|
private readonly destroy$ = new Subject<void>();
|
|
330
392
|
|
|
393
|
+
public ngOnChanges(changes: SimpleChanges): void {
|
|
394
|
+
if (changes['engine'] && this.engine) {
|
|
395
|
+
this.facade.adoptEngine(this.engine);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
331
399
|
public ngOnInit(): void {
|
|
332
400
|
this.facade.events$.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
|
333
|
-
this.
|
|
334
|
-
if (event.type === "completed") this.
|
|
335
|
-
if (event.type === "cancelled") this.
|
|
401
|
+
this.event.emit(event);
|
|
402
|
+
if (event.type === "completed") this.complete.emit(event.data);
|
|
403
|
+
if (event.type === "cancelled") this.cancel.emit(event.data);
|
|
336
404
|
});
|
|
337
405
|
|
|
338
|
-
if (this.autoStart) {
|
|
406
|
+
if (this.autoStart && !this.engine) {
|
|
339
407
|
this.doStart();
|
|
340
408
|
}
|
|
341
409
|
}
|
|
@@ -346,6 +414,7 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
346
414
|
}
|
|
347
415
|
|
|
348
416
|
public doStart(): void {
|
|
417
|
+
if (!this.path) throw new Error('[pw-shell] [path] is required when no [engine] is provided');
|
|
349
418
|
this.started = true;
|
|
350
419
|
this.facade.start(this.path, this.initialData);
|
|
351
420
|
}
|
|
@@ -360,7 +429,7 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
360
429
|
* ```
|
|
361
430
|
*/
|
|
362
431
|
public restart(): Promise<void> {
|
|
363
|
-
return this.facade.restart(
|
|
432
|
+
return this.facade.restart();
|
|
364
433
|
}
|
|
365
434
|
|
|
366
435
|
/** Returns Object.entries(s.fieldErrors) for use in *ngFor. */
|
|
@@ -380,9 +449,6 @@ export class PathShellComponent implements OnInit, OnDestroy {
|
|
|
380
449
|
: this.footerLayout;
|
|
381
450
|
}
|
|
382
451
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
protected formatFieldKey(key: string): string {
|
|
386
|
-
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
387
|
-
}
|
|
452
|
+
protected errorPhaseMessage = errorPhaseMessage;
|
|
453
|
+
protected formatFieldKey = formatFieldKey;
|
|
388
454
|
}
|