@daltonr/pathwrite-angular 0.3.0 → 0.5.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
@@ -2,6 +2,32 @@
2
2
 
3
3
  Angular `@Injectable` facade over `@daltonr/pathwrite-core`. Exposes path state and events as RxJS observables that work seamlessly with Angular signals, the `async` pipe, and `takeUntilDestroyed`.
4
4
 
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @daltonr/pathwrite-core @daltonr/pathwrite-angular
9
+ ```
10
+
11
+ ## Exported Types
12
+
13
+ For convenience, this package re-exports core types so you don't need to import from `@daltonr/pathwrite-core`:
14
+
15
+ ```typescript
16
+ import {
17
+ PathFacade, // Angular-specific
18
+ PathEngine, // Re-exported from core (value + type)
19
+ PathData, // Re-exported from core
20
+ PathDefinition, // Re-exported from core
21
+ PathEvent, // Re-exported from core
22
+ PathSnapshot, // Re-exported from core
23
+ PathStep, // Re-exported from core
24
+ PathStepContext, // Re-exported from core
25
+ SerializedPathState // Re-exported from core
26
+ } from "@daltonr/pathwrite-angular";
27
+ ```
28
+
29
+ ---
30
+
5
31
  ## Setup
6
32
 
7
33
  Provide `PathFacade` at the component level so each component gets its own isolated path instance, and Angular handles cleanup automatically via `ngOnDestroy`.
@@ -40,7 +66,9 @@ export class MyComponent {
40
66
 
41
67
  | Method | Description |
42
68
  |--------|-------------|
69
+ | `adoptEngine(engine)` | Adopt an externally-managed `PathEngine` (e.g. from `restoreOrStart()`). The facade immediately reflects the engine's current state and forwards all subsequent events. The caller is responsible for the engine's lifecycle. |
43
70
  | `start(definition, data?)` | Start or re-start a path. |
71
+ | `restart(definition, data?)` | Tear down any active path (without firing hooks) and start the given path fresh. Safe to call at any time. Use for "Start over" / retry flows. |
44
72
  | `startSubPath(definition, data?, meta?)` | Push a sub-path. Requires an active path. `meta` is returned unchanged to `onSubPathComplete` / `onSubPathCancel`. |
45
73
  | `next()` | Advance one step. Completes the path on the last step. |
46
74
  | `previous()` | Go back one step. No-op when already on the first step of a top-level path. |
@@ -273,9 +301,9 @@ export class MyComponent {
273
301
  | `path` | `PathDefinition` | *required* | The path definition to drive. |
274
302
  | `initialData` | `PathData` | `{}` | Initial data passed to `facade.start()`. |
275
303
  | `autoStart` | `boolean` | `true` | Start the path automatically on `ngOnInit`. |
276
- | `backLabel` | `string` | `"Back"` | Back button label. |
304
+ | `backLabel` | `string` | `"Previous"` | Previous button label. |
277
305
  | `nextLabel` | `string` | `"Next"` | Next button label. |
278
- | `finishLabel` | `string` | `"Finish"` | Finish button label (last step). |
306
+ | `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
279
307
  | `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
280
308
  | `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
281
309
  | `hideProgress` | `boolean` | `false` | Hide the progress indicator. |
@@ -320,7 +348,7 @@ export class MyComponent { ... }
320
348
  <ng-template pwShellFooter let-s let-actions="actions">
321
349
  <button (click)="actions.previous()" [disabled]="s.isFirstStep || s.isNavigating">Back</button>
322
350
  <button (click)="actions.next()" [disabled]="!s.canMoveNext || s.isNavigating">
323
- {{ s.isLastStep ? 'Finish' : 'Next' }}
351
+ {{ s.isLastStep ? 'Complete' : 'Next' }}
324
352
  </button>
325
353
  </ng-template>
326
354
  <ng-template pwStep="details"><app-details-form /></ng-template>
@@ -330,12 +358,357 @@ export class MyComponent { ... }
330
358
  export class MyComponent { ... }
331
359
  ```
332
360
 
333
- `actions` (`PathShellActions`) contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`. All return `Promise<void>`.
361
+ `actions` (`PathShellActions`) contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`. All return `Promise<void>`.
362
+
363
+ `restart()` restarts the shell's own `[path]` input with its own `[initialData]` input — useful for a "Start over" button in a custom footer.
334
364
 
335
365
  Both directives can be combined. Only the sections you override are replaced — a custom header still shows the default footer, and vice versa.
336
366
 
337
367
  ---
338
368
 
369
+ ## Sub-Paths
370
+
371
+ Sub-paths allow you to nest multi-step workflows. Common use cases include:
372
+ - Running a child workflow per collection item (e.g., approve each document)
373
+ - Conditional drill-down flows (e.g., "Add payment method" modal)
374
+ - Reusable wizard components
375
+
376
+ ### Basic Sub-Path Flow
377
+
378
+ When a sub-path is active:
379
+ - The shell switches to show the sub-path's steps
380
+ - The progress bar displays sub-path steps (not main path steps)
381
+ - Pressing Back on the first sub-path step **cancels** the sub-path and returns to the parent
382
+ - The `PathFacade` (and thus `state$`, `stateSignal`) reflects the **sub-path** snapshot, not the parent's
383
+
384
+ ### Complete Example: Approver Collection
385
+
386
+ ```typescript
387
+ import { PathData, PathDefinition, PathFacade } from "@daltonr/pathwrite-angular";
388
+
389
+ // Sub-path data shape
390
+ interface ApproverReviewData extends PathData {
391
+ decision: "approve" | "reject" | "";
392
+ comments: string;
393
+ }
394
+
395
+ // Main path data shape
396
+ interface ApprovalWorkflowData extends PathData {
397
+ documentTitle: string;
398
+ approvers: string[];
399
+ approvals: Array<{ approver: string; decision: string; comments: string }>;
400
+ }
401
+
402
+ // Define the sub-path (approver review wizard)
403
+ const approverReviewPath: PathDefinition<ApproverReviewData> = {
404
+ id: "approver-review",
405
+ steps: [
406
+ { id: "review", title: "Review Document" },
407
+ {
408
+ id: "decision",
409
+ title: "Make Decision",
410
+ canMoveNext: ({ data }) =>
411
+ data.decision === "approve" || data.decision === "reject",
412
+ validationMessages: ({ data }) =>
413
+ !data.decision ? ["Please select Approve or Reject"] : []
414
+ },
415
+ { id: "comments", title: "Add Comments" }
416
+ ]
417
+ };
418
+
419
+ // Define the main path
420
+ const approvalWorkflowPath: PathDefinition<ApprovalWorkflowData> = {
421
+ id: "approval-workflow",
422
+ steps: [
423
+ {
424
+ id: "setup",
425
+ title: "Setup Approval",
426
+ canMoveNext: ({ data }) =>
427
+ (data.documentTitle ?? "").trim().length > 0 &&
428
+ data.approvers.length > 0
429
+ },
430
+ {
431
+ id: "run-approvals",
432
+ title: "Collect Approvals",
433
+ // Block "Next" until all approvers have completed their reviews
434
+ canMoveNext: ({ data }) =>
435
+ data.approvals.length === data.approvers.length,
436
+ validationMessages: ({ data }) => {
437
+ const remaining = data.approvers.length - data.approvals.length;
438
+ return remaining > 0
439
+ ? [`${remaining} approver(s) pending review`]
440
+ : [];
441
+ },
442
+ // When an approver finishes their sub-path, record the result
443
+ onSubPathComplete(subPathId, subPathData, ctx, meta) {
444
+ const approverName = meta?.approverName as string;
445
+ const result = subPathData as ApproverReviewData;
446
+ return {
447
+ approvals: [
448
+ ...ctx.data.approvals,
449
+ {
450
+ approver: approverName,
451
+ decision: result.decision,
452
+ comments: result.comments
453
+ }
454
+ ]
455
+ };
456
+ },
457
+ // If an approver cancels (presses Back on first step), you can track it
458
+ onSubPathCancel(subPathId, subPathData, ctx, meta) {
459
+ console.log(`${meta?.approverName} cancelled their review`);
460
+ // Optionally return data changes, or just log
461
+ }
462
+ },
463
+ { id: "summary", title: "Summary" }
464
+ ]
465
+ };
466
+
467
+ // Component
468
+ @Component({
469
+ selector: 'app-approval-workflow',
470
+ standalone: true,
471
+ imports: [PathShellComponent, PathStepDirective],
472
+ providers: [PathFacade],
473
+ template: `
474
+ <pw-shell [path]="approvalWorkflowPath" [initialData]="initialData">
475
+ <!-- Main path steps -->
476
+ <ng-template pwStep="setup">
477
+ <input [(ngModel)]="facade.snapshot()!.data.documentTitle" placeholder="Document title" />
478
+ <!-- approver selection UI here -->
479
+ </ng-template>
480
+
481
+ <ng-template pwStep="run-approvals">
482
+ <h3>Approvers</h3>
483
+ <ul>
484
+ @for (approver of facade.snapshot()!.data.approvers; track $index) {
485
+ <li>
486
+ {{ approver }}
487
+ @if (!hasApproval(approver)) {
488
+ <button (click)="launchReviewForApprover(approver, $index)">
489
+ Start Review
490
+ </button>
491
+ } @else {
492
+ <span>✓ {{ getApproval(approver)?.decision }}</span>
493
+ }
494
+ </li>
495
+ }
496
+ </ul>
497
+ </ng-template>
498
+
499
+ <ng-template pwStep="summary">
500
+ <h3>All Approvals Collected</h3>
501
+ <ul>
502
+ @for (approval of facade.snapshot()!.data.approvals; track approval.approver) {
503
+ <li>{{ approval.approver }}: {{ approval.decision }}</li>
504
+ }
505
+ </ul>
506
+ </ng-template>
507
+
508
+ <!-- Sub-path steps (must be co-located in the same pw-shell) -->
509
+ <ng-template pwStep="review">
510
+ <p>Review the document: "{{ facade.snapshot()!.data.documentTitle }}"</p>
511
+ </ng-template>
512
+
513
+ <ng-template pwStep="decision">
514
+ <label><input type="radio" value="approve" [(ngModel)]="facade.snapshot()!.data.decision" /> Approve</label>
515
+ <label><input type="radio" value="reject" [(ngModel)]="facade.snapshot()!.data.decision" /> Reject</label>
516
+ </ng-template>
517
+
518
+ <ng-template pwStep="comments">
519
+ <textarea [(ngModel)]="facade.snapshot()!.data.comments" placeholder="Optional comments"></textarea>
520
+ </ng-template>
521
+ </pw-shell>
522
+ `
523
+ })
524
+ export class ApprovalWorkflowComponent {
525
+ protected readonly facade = inject(PathFacade) as PathFacade<ApprovalWorkflowData>;
526
+ protected readonly approvalWorkflowPath = approvalWorkflowPath;
527
+ protected readonly initialData = { documentTitle: '', approvers: [], approvals: [] };
528
+
529
+ protected launchReviewForApprover(approverName: string, index: number): void {
530
+ // Pass correlation data via `meta` — it's echoed back to onSubPathComplete
531
+ void this.facade.startSubPath(
532
+ approverReviewPath,
533
+ { decision: "", comments: "" },
534
+ { approverName, approverIndex: index }
535
+ );
536
+ }
537
+
538
+ protected hasApproval(approver: string): boolean {
539
+ return this.facade.snapshot()!.data.approvals.some(a => a.approver === approver);
540
+ }
541
+
542
+ protected getApproval(approver: string) {
543
+ return this.facade.snapshot()!.data.approvals.find(a => a.approver === approver);
544
+ }
545
+ }
546
+ ```
547
+
548
+ ### Key Notes
549
+
550
+ **1. Sub-path steps must be co-located with main path steps**
551
+ All `pwStep` templates (main path + sub-path steps) live in the same `<pw-shell>`. When a sub-path is active, the shell renders the sub-path's step templates. This means:
552
+ - Parent and sub-path step IDs **must not collide** (e.g., don't use `summary` in both)
553
+ - The shell matches step IDs from the current path only (main or sub), but all templates are registered globally
554
+
555
+ **2. The `meta` correlation field**
556
+ `startSubPath` accepts an optional third argument (`meta`) that is returned unchanged to `onSubPathComplete` and `onSubPathCancel`. Use it to correlate which collection item triggered the sub-path:
557
+
558
+ ```typescript
559
+ facade.startSubPath(subPath, initialData, { itemIndex: 3, itemId: "abc" });
560
+
561
+ // In the parent step:
562
+ onSubPathComplete(subPathId, subPathData, ctx, meta) {
563
+ const itemIndex = meta?.itemIndex; // 3
564
+ }
565
+ ```
566
+
567
+ **3. Progress bar switches during sub-paths**
568
+ When `snapshot.nestingLevel > 0`, you're in a sub-path. The `steps` array in the snapshot contains the sub-path's steps, not the main path's. The default PathShell progress bar shows sub-path progress. You can check `nestingLevel` to show a breadcrumb or "back to main flow" indicator.
569
+
570
+ **4. Accessing parent path data from sub-path components**
571
+ There is currently no way to inject a "parent facade" in sub-path step components. If a sub-path step needs parent data (e.g., the document title), pass it via `initialData` when calling `startSubPath`:
572
+
573
+ ```typescript
574
+ facade.startSubPath(approverReviewPath, {
575
+ decision: "",
576
+ comments: "",
577
+ documentTitle: facade.snapshot()!.data.documentTitle // copy from parent
578
+ });
579
+ ```
580
+
581
+ ---
582
+
583
+ ## Guards and Lifecycle Hooks
584
+
585
+ ### Defensive Guards (Important!)
586
+
587
+ **Guards and `validationMessages` are evaluated *before* `onEnter` runs on first entry.**
588
+
589
+ If you access fields in a guard that `onEnter` is supposed to initialize, the guard will throw a `TypeError` on startup. Write guards defensively using nullish coalescing:
590
+
591
+ ```typescript
592
+ // ✗ Unsafe — crashes if data.name is undefined
593
+ canMoveNext: ({ data }) => data.name.trim().length > 0
594
+
595
+ // ✓ Safe — handles undefined gracefully
596
+ canMoveNext: ({ data }) => (data.name ?? "").trim().length > 0
597
+ ```
598
+
599
+ Alternatively, pass `initialData` to `start()` / `<pw-shell>` so all fields are present from the first snapshot:
600
+
601
+ ```typescript
602
+ <pw-shell [path]="myPath" [initialData]="{ name: '', age: 0 }" />
603
+ ```
604
+
605
+ If a guard throws, the engine catches it, logs a warning, and returns `true` (allow navigation) as a safe default.
606
+
607
+ ### Async Guards and Validation Messages
608
+
609
+ Guards and `validationMessages` must be **synchronous** for inclusion in snapshots. Async functions are detected and warned about:
610
+ - Async `canMoveNext` / `canMovePrevious` default to `true` (optimistic)
611
+ - Async `validationMessages` default to `[]`
612
+
613
+ The async version is still enforced during actual navigation (when you call `next()` / `previous()`), but the snapshot won't reflect the pending state. If you need async validation, perform it in the guard and store the result in `data` so the guard can read it synchronously.
614
+
615
+ ### `isFirstEntry` Flag
616
+
617
+ The `PathStepContext` passed to all hooks includes an `isFirstEntry: boolean` flag. It's `true` the first time a step is visited, `false` on re-entry (e.g., after navigating back then forward again).
618
+
619
+ Use it to distinguish initialization from re-entry:
620
+
621
+ ```typescript
622
+ {
623
+ id: "details",
624
+ onEnter: ({ isFirstEntry, data }) => {
625
+ if (isFirstEntry) {
626
+ // Only pre-fill on first visit, not when returning via Back
627
+ return { name: "Default Name" };
628
+ }
629
+ }
630
+ }
631
+ ```
632
+
633
+ **Important:** `onEnter` fires every time you enter the step. If you want "initialize once" behavior, either:
634
+ 1. Use `isFirstEntry` to conditionally return data
635
+ 2. Provide `initialData` to `start()` instead of using `onEnter`
636
+
637
+ ---
638
+
639
+ ## Persistence
640
+
641
+ 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()`.
642
+
643
+ ### Simple persistence example
644
+
645
+ ```typescript
646
+ import { Component, inject, OnInit } from '@angular/core';
647
+ import { PathFacade, PathShellComponent, PathStepDirective } from '@daltonr/pathwrite-angular';
648
+ import { HttpStore, restoreOrStart, httpPersistence } from '@daltonr/pathwrite-store-http';
649
+ import { signupPath } from './signup-path';
650
+
651
+ @Component({
652
+ selector: 'app-signup-wizard',
653
+ standalone: true,
654
+ imports: [PathShellComponent, PathStepDirective],
655
+ providers: [PathFacade],
656
+ template: `
657
+ @if (ready) {
658
+ <pw-shell [path]="path" [autoStart]="false" (completed)="onComplete($event)">
659
+ <ng-template pwStep="details"><app-details-form /></ng-template>
660
+ <ng-template pwStep="review"><app-review-panel /></ng-template>
661
+ </pw-shell>
662
+ } @else {
663
+ <p>Loading…</p>
664
+ }
665
+ `
666
+ })
667
+ export class SignupWizardComponent implements OnInit {
668
+ private readonly facade = inject(PathFacade);
669
+
670
+ path = signupPath;
671
+ ready = false;
672
+
673
+ async ngOnInit() {
674
+ const store = new HttpStore({ baseUrl: '/api/wizard' });
675
+ const key = 'user:signup';
676
+
677
+ const { engine, restored } = await restoreOrStart({
678
+ store,
679
+ key,
680
+ path: this.path,
681
+ initialData: { name: '', email: '' },
682
+ observers: [
683
+ httpPersistence({ store, key, strategy: 'onNext' })
684
+ ]
685
+ });
686
+
687
+ // Hand the running engine to the facade — it immediately
688
+ // reflects the engine's current state and forwards all events.
689
+ this.facade.adoptEngine(engine);
690
+ this.ready = true;
691
+
692
+ if (restored) {
693
+ console.log('Progress restored — resuming from', engine.snapshot()?.stepId);
694
+ }
695
+ }
696
+
697
+ onComplete(data: any) {
698
+ console.log('Wizard completed:', data);
699
+ }
700
+ }
701
+ ```
702
+
703
+ ### Key points
704
+
705
+ - **`adoptEngine()`** connects the facade to an externally-created engine. No `@ViewChild` or shell reference needed — inject `PathFacade` directly.
706
+ - **`[autoStart]="false"`** prevents the shell from calling `start()` on its own — the engine is already running.
707
+ - **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.
708
+ - **`restoreOrStart()`** handles the load-or-create logic: if saved state exists on the server, it restores the engine mid-flow; otherwise it starts fresh.
709
+
710
+ ---
711
+
339
712
  ## Styling
340
713
 
341
714
  Import the optional stylesheet for sensible default styles. All visual values are CSS custom properties (`--pw-*`) so you can theme without overriding selectors.
@@ -365,3 +738,36 @@ Override any `--pw-*` variable to customise the appearance:
365
738
  --pw-shell-radius: 12px;
366
739
  }
367
740
  ```
741
+
742
+ ### Available CSS Custom Properties
743
+
744
+ **Layout:**
745
+ - `--pw-shell-max-width` — Maximum width of the shell (default: `720px`)
746
+ - `--pw-shell-padding` — Internal padding (default: `24px`)
747
+ - `--pw-shell-gap` — Gap between header, body, footer (default: `20px`)
748
+ - `--pw-shell-radius` — Border radius for cards (default: `10px`)
749
+
750
+ **Colors:**
751
+ - `--pw-color-bg` — Background color (default: `#ffffff`)
752
+ - `--pw-color-border` — Border color (default: `#dbe4f0`)
753
+ - `--pw-color-text` — Primary text color (default: `#1f2937`)
754
+ - `--pw-color-muted` — Muted text color (default: `#5b677a`)
755
+ - `--pw-color-primary` — Primary/accent color (default: `#2563eb`)
756
+ - `--pw-color-primary-light` — Light primary for backgrounds (default: `rgba(37, 99, 235, 0.12)`)
757
+ - `--pw-color-btn-bg` — Button background (default: `#f8fbff`)
758
+ - `--pw-color-btn-border` — Button border (default: `#c2d0e5`)
759
+
760
+ **Validation:**
761
+ - `--pw-color-error` — Error text color (default: `#dc2626`)
762
+ - `--pw-color-error-bg` — Error background (default: `#fef2f2`)
763
+ - `--pw-color-error-border` — Error border (default: `#fecaca`)
764
+
765
+ **Progress Indicator:**
766
+ - `--pw-dot-size` — Step dot size (default: `32px`)
767
+ - `--pw-dot-font-size` — Font size inside dots (default: `13px`)
768
+ - `--pw-track-height` — Progress track height (default: `4px`)
769
+
770
+ **Buttons:**
771
+ - `--pw-btn-padding` — Button padding (default: `8px 16px`)
772
+ - `--pw-btn-radius` — Button border radius (default: `6px`)
773
+
package/dist/index.css CHANGED
@@ -250,6 +250,16 @@
250
250
  border-color: #1d4ed8;
251
251
  }
252
252
 
253
+ .pw-shell__btn--back {
254
+ background: transparent;
255
+ border-color: var(--pw-color-primary);
256
+ color: var(--pw-color-primary);
257
+ }
258
+
259
+ .pw-shell__btn--back:hover:not(:disabled) {
260
+ background: var(--pw-color-primary-light);
261
+ }
262
+
253
263
  .pw-shell__btn--cancel {
254
264
  color: var(--pw-color-muted);
255
265
  border-color: transparent;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OnDestroy, DestroyRef, Signal } from "@angular/core";
2
2
  import { Observable } from "rxjs";
3
- import { PathData, PathDefinition, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
3
+ import { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
4
4
  import * as i0 from "@angular/core";
5
5
  /**
6
6
  * Angular facade over PathEngine. Provide at component level for an isolated
@@ -17,18 +17,40 @@ import * as i0 from "@angular/core";
17
17
  * ```
18
18
  */
19
19
  export declare class PathFacade<TData extends PathData = PathData> implements OnDestroy {
20
- private readonly engine;
20
+ private _engine;
21
21
  private readonly _state$;
22
22
  private readonly _events$;
23
- private readonly unsubscribeFromEngine;
23
+ private _unsubscribeFromEngine;
24
24
  private readonly _stateSignal;
25
25
  readonly state$: Observable<PathSnapshot<TData> | null>;
26
26
  readonly events$: Observable<PathEvent>;
27
27
  /** Signal version of state$. Updates on every path state change. Requires Angular 16+. */
28
28
  readonly stateSignal: Signal<PathSnapshot<TData> | null>;
29
29
  constructor();
30
+ /**
31
+ * Adopt an externally-managed `PathEngine` — for example, the engine returned
32
+ * by `restoreOrStart()` from `@daltonr/pathwrite-store-http`.
33
+ *
34
+ * The facade immediately reflects the engine's current state and forwards all
35
+ * subsequent events. The **caller** is responsible for the engine's lifecycle
36
+ * (starting, cleanup); the facade only subscribes to it.
37
+ *
38
+ * ```typescript
39
+ * const { engine } = await restoreOrStart({ store, key, path, initialData, observers: [...] });
40
+ * facade.adoptEngine(engine);
41
+ * ```
42
+ */
43
+ adoptEngine(engine: PathEngine): void;
44
+ private connectEngine;
30
45
  ngOnDestroy(): void;
31
46
  start(path: PathDefinition<any>, initialData?: PathData): Promise<void>;
47
+ /**
48
+ * Tears down any active path (without firing lifecycle hooks) and immediately
49
+ * starts the given path fresh. Safe to call whether or not a path is running.
50
+ * Use for "Start over" / retry flows without destroying and re-creating the
51
+ * component that provides this facade.
52
+ */
53
+ restart(path: PathDefinition<any>, initialData?: PathData): Promise<void>;
32
54
  startSubPath(path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>): Promise<void>;
33
55
  next(): Promise<void>;
34
56
  previous(): Promise<void>;
@@ -84,3 +106,5 @@ export interface FormGroupLike {
84
106
  * ```
85
107
  */
86
108
  export declare function syncFormGroup<TData extends PathData = PathData>(facade: PathFacade<TData>, formGroup: FormGroupLike, destroyRef?: DestroyRef): () => void;
109
+ export type { PathData, PathDefinition, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
110
+ export { PathEngine } from "@daltonr/pathwrite-core";
package/dist/index.js CHANGED
@@ -18,15 +18,43 @@ import * as i0 from "@angular/core";
18
18
  */
19
19
  export class PathFacade {
20
20
  constructor() {
21
- this.engine = new PathEngine();
21
+ this._engine = new PathEngine();
22
22
  this._state$ = new BehaviorSubject(null);
23
23
  this._events$ = new Subject();
24
+ this._unsubscribeFromEngine = () => { };
24
25
  this._stateSignal = signal(null);
25
26
  this.state$ = this._state$.asObservable();
26
27
  this.events$ = this._events$.asObservable();
27
28
  /** Signal version of state$. Updates on every path state change. Requires Angular 16+. */
28
29
  this.stateSignal = this._stateSignal.asReadonly();
29
- this.unsubscribeFromEngine = this.engine.subscribe((event) => {
30
+ this.connectEngine(this._engine);
31
+ }
32
+ /**
33
+ * Adopt an externally-managed `PathEngine` — for example, the engine returned
34
+ * by `restoreOrStart()` from `@daltonr/pathwrite-store-http`.
35
+ *
36
+ * The facade immediately reflects the engine's current state and forwards all
37
+ * subsequent events. The **caller** is responsible for the engine's lifecycle
38
+ * (starting, cleanup); the facade only subscribes to it.
39
+ *
40
+ * ```typescript
41
+ * const { engine } = await restoreOrStart({ store, key, path, initialData, observers: [...] });
42
+ * facade.adoptEngine(engine);
43
+ * ```
44
+ */
45
+ adoptEngine(engine) {
46
+ // Disconnect from whatever engine we're currently listening to
47
+ this._unsubscribeFromEngine();
48
+ this._engine = engine;
49
+ this.connectEngine(engine);
50
+ }
51
+ connectEngine(engine) {
52
+ // Seed state immediately — critical when restoring a persisted path since
53
+ // the engine is already running before the facade connects to it.
54
+ const current = engine.snapshot();
55
+ this._state$.next(current);
56
+ this._stateSignal.set(current);
57
+ this._unsubscribeFromEngine = engine.subscribe((event) => {
30
58
  this._events$.next(event);
31
59
  if (event.type === "stateChanged" || event.type === "resumed") {
32
60
  this._state$.next(event.snapshot);
@@ -39,36 +67,45 @@ export class PathFacade {
39
67
  });
40
68
  }
41
69
  ngOnDestroy() {
42
- this.unsubscribeFromEngine();
70
+ this._unsubscribeFromEngine();
43
71
  this._events$.complete();
44
72
  this._state$.complete();
45
73
  }
46
74
  start(path, initialData = {}) {
47
- return this.engine.start(path, initialData);
75
+ return this._engine.start(path, initialData);
76
+ }
77
+ /**
78
+ * Tears down any active path (without firing lifecycle hooks) and immediately
79
+ * starts the given path fresh. Safe to call whether or not a path is running.
80
+ * Use for "Start over" / retry flows without destroying and re-creating the
81
+ * component that provides this facade.
82
+ */
83
+ restart(path, initialData = {}) {
84
+ return this._engine.restart(path, initialData);
48
85
  }
49
86
  startSubPath(path, initialData = {}, meta) {
50
- return this.engine.startSubPath(path, initialData, meta);
87
+ return this._engine.startSubPath(path, initialData, meta);
51
88
  }
52
89
  next() {
53
- return this.engine.next();
90
+ return this._engine.next();
54
91
  }
55
92
  previous() {
56
- return this.engine.previous();
93
+ return this._engine.previous();
57
94
  }
58
95
  cancel() {
59
- return this.engine.cancel();
96
+ return this._engine.cancel();
60
97
  }
61
98
  setData(key, value) {
62
- return this.engine.setData(key, value);
99
+ return this._engine.setData(key, value);
63
100
  }
64
101
  goToStep(stepId) {
65
- return this.engine.goToStep(stepId);
102
+ return this._engine.goToStep(stepId);
66
103
  }
67
104
  /** Jump to a step by ID, checking the current step's canMoveNext (forward) or
68
105
  * canMovePrevious (backward) guard first. Navigation is blocked if the guard
69
106
  * returns false. Throws if the step ID does not exist. */
70
107
  goToStepChecked(stepId) {
71
- return this.engine.goToStepChecked(stepId);
108
+ return this._engine.goToStepChecked(stepId);
72
109
  }
73
110
  snapshot() {
74
111
  return this._state$.getValue();
@@ -122,4 +159,5 @@ export function syncFormGroup(facade, formGroup, destroyRef) {
122
159
  destroyRef?.onDestroy(cleanup);
123
160
  return cleanup;
124
161
  }
162
+ export { PathEngine } from "@daltonr/pathwrite-core";
125
163
  //# sourceMappingURL=index.js.map
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
@@ -14,6 +14,8 @@ export interface PathShellActions {
14
14
  goToStep: (stepId: string) => Promise<void>;
15
15
  goToStepChecked: (stepId: string) => Promise<void>;
16
16
  setData: (key: string, value: unknown) => Promise<void>;
17
+ /** Restart the shell's current path with its current `initialData`. */
18
+ restart: () => Promise<void>;
17
19
  }
18
20
  /**
19
21
  * Structural directive that associates a template with a step ID.
@@ -106,7 +108,7 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
106
108
  /** Label for the Next navigation button. */
107
109
  nextLabel: string;
108
110
  /** Label for the Next button when on the last step. */
109
- finishLabel: string;
111
+ completeLabel: string;
110
112
  /** Label for the Cancel button. */
111
113
  cancelLabel: string;
112
114
  /** Hide the Cancel button entirely. */
@@ -131,5 +133,5 @@ export declare class PathShellComponent implements OnInit, OnDestroy {
131
133
  ngOnDestroy(): void;
132
134
  doStart(): void;
133
135
  static ɵfac: i0.ɵɵFactoryDeclaration<PathShellComponent, never>;
134
- 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; }; "finishLabel": { "alias": "finishLabel"; "required": false; }; "cancelLabel": { "alias": "cancelLabel"; "required": false; }; "hideCancel": { "alias": "hideCancel"; "required": false; }; "hideProgress": { "alias": "hideProgress"; "required": false; }; }, { "completed": "completed"; "cancelled": "cancelled"; "pathEvent": "pathEvent"; }, ["customHeader", "customFooter", "stepDirectives"], never, true, never>;
136
+ 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; }; }, { "completed": "completed"; "cancelled": "cancelled"; "pathEvent": "pathEvent"; }, ["customHeader", "customFooter", "stepDirectives"], never, true, never>;
135
137
  }
package/dist/shell.js CHANGED
@@ -110,11 +110,11 @@ export class PathShellComponent {
110
110
  /** Start the path automatically on ngOnInit. Set to false to call doStart() manually. */
111
111
  this.autoStart = true;
112
112
  /** Label for the Back navigation button. */
113
- this.backLabel = "Back";
113
+ this.backLabel = "Previous";
114
114
  /** Label for the Next navigation button. */
115
115
  this.nextLabel = "Next";
116
116
  /** Label for the Next button when on the last step. */
117
- this.finishLabel = "Finish";
117
+ this.completeLabel = "Complete";
118
118
  /** Label for the Cancel button. */
119
119
  this.cancelLabel = "Cancel";
120
120
  /** Hide the Cancel button entirely. */
@@ -137,6 +137,7 @@ export class PathShellComponent {
137
137
  goToStep: (id) => this.facade.goToStep(id),
138
138
  goToStepChecked: (id) => this.facade.goToStepChecked(id),
139
139
  setData: (key, value) => this.facade.setData(key, value),
140
+ restart: () => this.facade.restart(this.path, this.initialData),
140
141
  };
141
142
  this.destroy$ = new Subject();
142
143
  }
@@ -161,7 +162,7 @@ export class PathShellComponent {
161
162
  this.facade.start(this.path, this.initialData);
162
163
  }
163
164
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PathShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
164
- 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", finishLabel: "finishLabel", cancelLabel: "cancelLabel", hideCancel: "hideCancel", hideProgress: "hideProgress" }, 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: `
165
+ 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" }, 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: `
165
166
  <!-- Empty state -->
166
167
  <div class="pw-shell" *ngIf="!(facade.state$ | async)">
167
168
  <div class="pw-shell__empty" *ngIf="!started">
@@ -236,7 +237,7 @@ export class PathShellComponent {
236
237
  class="pw-shell__btn pw-shell__btn--next"
237
238
  [disabled]="s.isNavigating || !s.canMoveNext"
238
239
  (click)="facade.next()"
239
- >{{ s.isLastStep ? finishLabel : nextLabel }}</button>
240
+ >{{ s.isLastStep ? completeLabel : nextLabel }}</button>
240
241
  </div>
241
242
  </div>
242
243
  </ng-template>
@@ -326,7 +327,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
326
327
  class="pw-shell__btn pw-shell__btn--next"
327
328
  [disabled]="s.isNavigating || !s.canMoveNext"
328
329
  (click)="facade.next()"
329
- >{{ s.isLastStep ? finishLabel : nextLabel }}</button>
330
+ >{{ s.isLastStep ? completeLabel : nextLabel }}</button>
330
331
  </div>
331
332
  </div>
332
333
  </ng-template>
@@ -344,7 +345,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
344
345
  type: Input
345
346
  }], nextLabel: [{
346
347
  type: Input
347
- }], finishLabel: [{
348
+ }], completeLabel: [{
348
349
  type: Input
349
350
  }], cancelLabel: [{
350
351
  type: Input
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.3.0",
3
+ "version": "0.5.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.",
@@ -60,7 +60,7 @@
60
60
  "@angular/compiler-cli": "^17.0.0"
61
61
  },
62
62
  "dependencies": {
63
- "@daltonr/pathwrite-core": "^0.3.0"
63
+ "@daltonr/pathwrite-core": "^0.5.0"
64
64
  },
65
65
  "publishConfig": {
66
66
  "access": "public"
package/src/index.ts CHANGED
@@ -24,10 +24,10 @@ import {
24
24
  */
25
25
  @Injectable()
26
26
  export class PathFacade<TData extends PathData = PathData> implements OnDestroy {
27
- private readonly engine = new PathEngine();
27
+ private _engine = new PathEngine();
28
28
  private readonly _state$ = new BehaviorSubject<PathSnapshot<TData> | null>(null);
29
29
  private readonly _events$ = new Subject<PathEvent>();
30
- private readonly unsubscribeFromEngine: () => void;
30
+ private _unsubscribeFromEngine: () => void = () => {};
31
31
  private readonly _stateSignal = signal<PathSnapshot<TData> | null>(null);
32
32
 
33
33
  public readonly state$: Observable<PathSnapshot<TData> | null> = this._state$.asObservable();
@@ -36,7 +36,37 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
36
36
  public readonly stateSignal: Signal<PathSnapshot<TData> | null> = this._stateSignal.asReadonly();
37
37
 
38
38
  public constructor() {
39
- this.unsubscribeFromEngine = this.engine.subscribe((event) => {
39
+ this.connectEngine(this._engine);
40
+ }
41
+
42
+ /**
43
+ * Adopt an externally-managed `PathEngine` — for example, the engine returned
44
+ * by `restoreOrStart()` from `@daltonr/pathwrite-store-http`.
45
+ *
46
+ * The facade immediately reflects the engine's current state and forwards all
47
+ * subsequent events. The **caller** is responsible for the engine's lifecycle
48
+ * (starting, cleanup); the facade only subscribes to it.
49
+ *
50
+ * ```typescript
51
+ * const { engine } = await restoreOrStart({ store, key, path, initialData, observers: [...] });
52
+ * facade.adoptEngine(engine);
53
+ * ```
54
+ */
55
+ public adoptEngine(engine: PathEngine): void {
56
+ // Disconnect from whatever engine we're currently listening to
57
+ this._unsubscribeFromEngine();
58
+ this._engine = engine;
59
+ this.connectEngine(engine);
60
+ }
61
+
62
+ private connectEngine(engine: PathEngine): void {
63
+ // Seed state immediately — critical when restoring a persisted path since
64
+ // the engine is already running before the facade connects to it.
65
+ const current = engine.snapshot() as PathSnapshot<TData> | null;
66
+ this._state$.next(current);
67
+ this._stateSignal.set(current);
68
+
69
+ this._unsubscribeFromEngine = engine.subscribe((event) => {
40
70
  this._events$.next(event);
41
71
  if (event.type === "stateChanged" || event.type === "resumed") {
42
72
  this._state$.next(event.snapshot as PathSnapshot<TData>);
@@ -49,44 +79,54 @@ export class PathFacade<TData extends PathData = PathData> implements OnDestroy
49
79
  }
50
80
 
51
81
  public ngOnDestroy(): void {
52
- this.unsubscribeFromEngine();
82
+ this._unsubscribeFromEngine();
53
83
  this._events$.complete();
54
84
  this._state$.complete();
55
85
  }
56
86
 
57
87
  public start(path: PathDefinition<any>, initialData: PathData = {}): Promise<void> {
58
- return this.engine.start(path, initialData);
88
+ return this._engine.start(path, initialData);
89
+ }
90
+
91
+ /**
92
+ * Tears down any active path (without firing lifecycle hooks) and immediately
93
+ * starts the given path fresh. Safe to call whether or not a path is running.
94
+ * Use for "Start over" / retry flows without destroying and re-creating the
95
+ * component that provides this facade.
96
+ */
97
+ public restart(path: PathDefinition<any>, initialData: PathData = {}): Promise<void> {
98
+ return this._engine.restart(path, initialData);
59
99
  }
60
100
 
61
101
  public startSubPath(path: PathDefinition<any>, initialData: PathData = {}, meta?: Record<string, unknown>): Promise<void> {
62
- return this.engine.startSubPath(path, initialData, meta);
102
+ return this._engine.startSubPath(path, initialData, meta);
63
103
  }
64
104
 
65
105
  public next(): Promise<void> {
66
- return this.engine.next();
106
+ return this._engine.next();
67
107
  }
68
108
 
69
109
  public previous(): Promise<void> {
70
- return this.engine.previous();
110
+ return this._engine.previous();
71
111
  }
72
112
 
73
113
  public cancel(): Promise<void> {
74
- return this.engine.cancel();
114
+ return this._engine.cancel();
75
115
  }
76
116
 
77
117
  public setData<K extends string & keyof TData>(key: K, value: TData[K]): Promise<void> {
78
- return this.engine.setData(key, value as unknown);
118
+ return this._engine.setData(key, value as unknown);
79
119
  }
80
120
 
81
121
  public goToStep(stepId: string): Promise<void> {
82
- return this.engine.goToStep(stepId);
122
+ return this._engine.goToStep(stepId);
83
123
  }
84
124
 
85
125
  /** Jump to a step by ID, checking the current step's canMoveNext (forward) or
86
126
  * canMovePrevious (backward) guard first. Navigation is blocked if the guard
87
127
  * returns false. Throws if the step ID does not exist. */
88
128
  public goToStepChecked(stepId: string): Promise<void> {
89
- return this.engine.goToStepChecked(stepId);
129
+ return this._engine.goToStepChecked(stepId);
90
130
  }
91
131
 
92
132
  public snapshot(): PathSnapshot<TData> | null {
@@ -162,3 +202,17 @@ export function syncFormGroup<TData extends PathData = PathData>(
162
202
  destroyRef?.onDestroy(cleanup);
163
203
  return cleanup;
164
204
  }
205
+
206
+ // Re-export core types for convenience (users don't need to import from @daltonr/pathwrite-core)
207
+ export type {
208
+ PathData,
209
+ PathDefinition,
210
+ PathEvent,
211
+ PathSnapshot,
212
+ PathStep,
213
+ PathStepContext,
214
+ SerializedPathState
215
+ } from "@daltonr/pathwrite-core";
216
+
217
+ export { PathEngine } from "@daltonr/pathwrite-core";
218
+
package/src/shell.ts CHANGED
@@ -41,6 +41,8 @@ export interface PathShellActions {
41
41
  goToStep: (stepId: string) => Promise<void>;
42
42
  goToStepChecked: (stepId: string) => Promise<void>;
43
43
  setData: (key: string, value: unknown) => Promise<void>;
44
+ /** Restart the shell's current path with its current `initialData`. */
45
+ restart: () => Promise<void>;
44
46
  }
45
47
 
46
48
  // ---------------------------------------------------------------------------
@@ -210,7 +212,7 @@ export class PathShellFooterDirective {
210
212
  class="pw-shell__btn pw-shell__btn--next"
211
213
  [disabled]="s.isNavigating || !s.canMoveNext"
212
214
  (click)="facade.next()"
213
- >{{ s.isLastStep ? finishLabel : nextLabel }}</button>
215
+ >{{ s.isLastStep ? completeLabel : nextLabel }}</button>
214
216
  </div>
215
217
  </div>
216
218
  </ng-template>
@@ -225,11 +227,11 @@ export class PathShellComponent implements OnInit, OnDestroy {
225
227
  /** Start the path automatically on ngOnInit. Set to false to call doStart() manually. */
226
228
  @Input() autoStart = true;
227
229
  /** Label for the Back navigation button. */
228
- @Input() backLabel = "Back";
230
+ @Input() backLabel = "Previous";
229
231
  /** Label for the Next navigation button. */
230
232
  @Input() nextLabel = "Next";
231
233
  /** Label for the Next button when on the last step. */
232
- @Input() finishLabel = "Finish";
234
+ @Input() completeLabel = "Complete";
233
235
  /** Label for the Cancel button. */
234
236
  @Input() cancelLabel = "Cancel";
235
237
  /** Hide the Cancel button entirely. */
@@ -259,6 +261,7 @@ export class PathShellComponent implements OnInit, OnDestroy {
259
261
  goToStep: (id) => this.facade.goToStep(id),
260
262
  goToStepChecked: (id) => this.facade.goToStepChecked(id),
261
263
  setData: (key, value) => this.facade.setData(key, value as never),
264
+ restart: () => this.facade.restart(this.path, this.initialData),
262
265
  };
263
266
 
264
267
  private readonly destroy$ = new Subject<void>();