@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 CHANGED
@@ -410,7 +410,7 @@ import {
410
410
  @Component({
411
411
  imports: [PathShellComponent, PathStepDirective],
412
412
  template: `
413
- <pw-shell [path]="myPath" [initialData]="{ name: '' }" (completed)="onDone($event)">
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" (completed)="onDone($event)">
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` | *required* | The path definition to drive. |
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
- | `(completed)` | `PathData` | Emitted when the path finishes naturally. |
518
- | `(cancelled)` | `PathData` | Emitted when the path is cancelled. |
519
- | `(pathEvent)` | `PathEvent` | Emitted for every engine event. |
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" (completed)="isActive = false" (cancelled)="isActive = false">
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" (completed)="onDone($event)">
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-http](../store-http) for automatic state persistence. The engine is created externally via `restoreOrStart()`, then handed to the facade via `adoptEngine()`.
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, inject, OnInit } from '@angular/core';
881
- import { PathFacade, PathShellComponent, PathStepDirective } from '@daltonr/pathwrite-angular';
882
- import { HttpStore, restoreOrStart, httpPersistence } from '@daltonr/pathwrite-store-http';
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 (ready) {
892
- <pw-shell [path]="path" [autoStart]="false" (completed)="onComplete($event)">
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
- ready = false;
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
- httpPersistence({ store, key, strategy: 'onNext' })
916
+ persistence({ store, key, strategy: 'onNext' })
918
917
  ]
919
918
  });
920
919
 
921
- // Hand the running engine to the facade — it immediately
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
- - **`adoptEngine()`** connects the facade to an externally-created engine. No `@ViewChild` or shell reference needed inject `PathFacade` directly.
940
- - **`[autoStart]="false"`** prevents the shell from calling `start()` on its own the engine is already running.
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-http`.
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(path: PathDefinition<any>, initialData?: PathData): Promise<void>;
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 `injectPath()`. Provides signal-based reactive access to the
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 InjectPathReturn<TData extends PathData = PathData> {
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: (path: PathDefinition<any>, initialData?: PathData) => Promise<void>;
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
- * Inject a PathFacade and return a signal-based API for use in Angular components.
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, framework-native API that React's `usePathContext()`
110
- * and Vue's `usePath()` offer. No template references or manual facade injection needed.
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 = injectPath<ContactData>();
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 injectPath<TData extends PathData = PathData>(): InjectPathReturn<TData>;
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-http`.
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(path, initialData = {}) {
84
- return this._engine.restart(path, initialData);
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
- * Inject a PathFacade and return a signal-based API for use in Angular components.
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, framework-native API that React's `usePathContext()`
131
- * and Vue's `usePath()` offer. No template references or manual facade injection needed.
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 = injectPath<ContactData>();
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 injectPath() {
168
+ export function usePathContext() {
161
169
  const facade = inject(PathFacade, { optional: true });
162
170
  if (!facade) {
163
- throw new Error("injectPath() requires PathFacade to be provided. " +
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: (path, initialData = {}) => facade.restart(path, initialData),
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
- 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: '' }" (completed)="onDone($event)">
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: PathDefinition<any>;
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
- completed: EventEmitter<PathData>;
141
- cancelled: EventEmitter<PathData>;
142
- pathEvent: EventEmitter<PathEvent>;
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
- /** Converts a camelCase or lowercase field key to a display label.
174
- * e.g. "firstName" "First Name", "email" → "Email" */
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": true; }; "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; }; }, { "completed": "completed"; "cancelled": "cancelled"; "pathEvent": "pathEvent"; }, ["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>;
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: '' }" (completed)="onDone($event)">
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 = "inline";
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.completed = new EventEmitter();
147
- this.cancelled = new EventEmitter();
148
- this.pathEvent = new EventEmitter();
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(this.path, this.initialData),
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.pathEvent.emit(event);
178
+ this.event.emit(event);
169
179
  if (event.type === "completed")
170
- this.completed.emit(event.data);
180
+ this.complete.emit(event.data);
171
181
  if (event.type === "cancelled")
172
- this.cancelled.emit(event.data);
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(this.path, this.initialData);
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: { completed: "completed", cancelled: "cancelled", pathEvent: "pathEvent" }, providers: [PathFacade], queries: [{ propertyName: "customHeader", first: true, predicate: PathShellHeaderDirective, descendants: true }, { propertyName: "customFooter", first: true, predicate: PathShellFooterDirective, descendants: true }, { propertyName: "stepDirectives", predicate: PathStepDirective }], 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: `
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-container *ngIf="customFooter; else defaultFooter">
294
- <ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
295
- </ng-container>
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.isNavigating"
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.isNavigating || !s.canMovePrevious"
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.isNavigating"
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
- [disabled]="s.isNavigating"
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-container *ngIf="customFooter; else defaultFooter">
422
- <ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
423
- </ng-container>
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.isNavigating"
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.isNavigating || !s.canMovePrevious"
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.isNavigating"
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
- [disabled]="s.isNavigating"
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
- args: [{ required: true }]
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
- }], completed: [{
569
+ }], complete: [{
492
570
  type: Output
493
- }], cancelled: [{
571
+ }], cancel: [{
494
572
  type: Output
495
- }], pathEvent: [{
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":"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"}
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.8.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.8.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-http`.
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(path: PathDefinition<any>, initialData: PathData = {}): Promise<void> {
98
- return this._engine.restart(path, initialData);
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
- // injectPath() - Signal-based path access
154
+ // usePathContext() - Signal-based path access
145
155
  // ---------------------------------------------------------------------------
146
156
 
147
157
  /**
148
- * Return type of `injectPath()`. Provides signal-based reactive access to the
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 InjectPathReturn<TData extends PathData = PathData> {
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: (path: PathDefinition<any>, initialData?: PathData) => Promise<void>;
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
- * Inject a PathFacade and return a signal-based API for use in Angular components.
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, framework-native API that React's `usePathContext()`
187
- * and Vue's `usePath()` offer. No template references or manual facade injection needed.
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 = injectPath<ContactData>();
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 injectPath<TData extends PathData = PathData>(): InjectPathReturn<TData> {
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
- "injectPath() requires PathFacade to be provided. " +
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: (path, initialData = {}) => facade.restart(path, initialData),
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: '' }" (completed)="onDone($event)">
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-container *ngIf="customFooter; else defaultFooter">
218
- <ng-container *ngTemplateOutlet="customFooter.templateRef; context: { $implicit: s, actions: shellActions }"></ng-container>
219
- </ng-container>
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.isNavigating"
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.isNavigating || !s.canMovePrevious"
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.isNavigating"
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
- [disabled]="s.isNavigating"
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({ required: true }) path!: PathDefinition<any>;
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" = "inline";
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() completed = new EventEmitter<PathData>();
305
- @Output() cancelled = new EventEmitter<PathData>();
306
- @Output() pathEvent = new EventEmitter<PathEvent>();
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(this.path, this.initialData),
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.pathEvent.emit(event);
334
- if (event.type === "completed") this.completed.emit(event.data);
335
- if (event.type === "cancelled") this.cancelled.emit(event.data);
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(this.path, this.initialData);
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
- /** Converts a camelCase or lowercase field key to a display label.
384
- * e.g. "firstName" "First Name", "email" → "Email" */
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
  }