@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 +410 -4
- package/dist/index.css +10 -0
- package/dist/index.d.ts +27 -3
- package/dist/index.js +49 -11
- package/dist/index.js.map +1 -1
- package/dist/shell.d.ts +4 -2
- package/dist/shell.js +7 -6
- package/dist/shell.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +66 -12
- package/src/shell.ts +6 -3
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` | `"
|
|
304
|
+
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
277
305
|
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
278
|
-
| `
|
|
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 ? '
|
|
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
|
|
20
|
+
private _engine;
|
|
21
21
|
private readonly _state$;
|
|
22
22
|
private readonly _events$;
|
|
23
|
-
private
|
|
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.
|
|
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.
|
|
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.
|
|
70
|
+
this._unsubscribeFromEngine();
|
|
43
71
|
this._events$.complete();
|
|
44
72
|
this._state$.complete();
|
|
45
73
|
}
|
|
46
74
|
start(path, initialData = {}) {
|
|
47
|
-
return this.
|
|
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.
|
|
87
|
+
return this._engine.startSubPath(path, initialData, meta);
|
|
51
88
|
}
|
|
52
89
|
next() {
|
|
53
|
-
return this.
|
|
90
|
+
return this._engine.next();
|
|
54
91
|
}
|
|
55
92
|
previous() {
|
|
56
|
-
return this.
|
|
93
|
+
return this._engine.previous();
|
|
57
94
|
}
|
|
58
95
|
cancel() {
|
|
59
|
-
return this.
|
|
96
|
+
return this._engine.cancel();
|
|
60
97
|
}
|
|
61
98
|
setData(key, value) {
|
|
62
|
-
return this.
|
|
99
|
+
return this._engine.setData(key, value);
|
|
63
100
|
}
|
|
64
101
|
goToStep(stepId) {
|
|
65
|
-
return this.
|
|
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.
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAyB,MAAM,EAAU,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,eAAe,EAAc,OAAO,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAGL,UAAU,EAGX,MAAM,yBAAyB,CAAC;;AAEjC;;;;;;;;;;;;;GAaG;AAEH,MAAM,OAAO,UAAU;IAYrB;
|
|
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
|
-
|
|
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; }; "
|
|
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 = "
|
|
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.
|
|
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",
|
|
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 ?
|
|
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 ?
|
|
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
|
-
}],
|
|
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":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EAET,KAAK,EACL,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,eAAe,EAIf,MAAM,EACN,QAAQ,EACR,uBAAuB,EACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAO3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;;;
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.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.
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
102
|
+
return this._engine.startSubPath(path, initialData, meta);
|
|
63
103
|
}
|
|
64
104
|
|
|
65
105
|
public next(): Promise<void> {
|
|
66
|
-
return this.
|
|
106
|
+
return this._engine.next();
|
|
67
107
|
}
|
|
68
108
|
|
|
69
109
|
public previous(): Promise<void> {
|
|
70
|
-
return this.
|
|
110
|
+
return this._engine.previous();
|
|
71
111
|
}
|
|
72
112
|
|
|
73
113
|
public cancel(): Promise<void> {
|
|
74
|
-
return this.
|
|
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.
|
|
118
|
+
return this._engine.setData(key, value as unknown);
|
|
79
119
|
}
|
|
80
120
|
|
|
81
121
|
public goToStep(stepId: string): Promise<void> {
|
|
82
|
-
return this.
|
|
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.
|
|
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 ?
|
|
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 = "
|
|
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()
|
|
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>();
|