@daltonr/pathwrite-angular 0.1.4 → 0.2.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 +145 -12
- package/dist/index.css +39 -5
- package/dist/index.d.ts +70 -5
- package/dist/index.js +121 -98
- package/dist/index.js.map +1 -1
- package/dist/shell.d.ts +19 -2
- package/dist/shell.js +183 -191
- package/dist/shell.js.map +1 -1
- package/package.json +14 -3
- package/src/index.ts +164 -0
- package/src/shell.ts +197 -0
package/src/shell.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
Directive,
|
|
4
|
+
TemplateRef,
|
|
5
|
+
Input,
|
|
6
|
+
Output,
|
|
7
|
+
EventEmitter,
|
|
8
|
+
ContentChildren,
|
|
9
|
+
QueryList,
|
|
10
|
+
OnInit,
|
|
11
|
+
OnDestroy,
|
|
12
|
+
inject,
|
|
13
|
+
Injector,
|
|
14
|
+
ChangeDetectionStrategy
|
|
15
|
+
} from "@angular/core";
|
|
16
|
+
import { CommonModule } from "@angular/common";
|
|
17
|
+
import { Subject } from "rxjs";
|
|
18
|
+
import { takeUntil } from "rxjs/operators";
|
|
19
|
+
import {
|
|
20
|
+
PathData,
|
|
21
|
+
PathDefinition,
|
|
22
|
+
PathEvent
|
|
23
|
+
} from "@daltonr/pathwrite-core";
|
|
24
|
+
import { PathFacade } from "./index";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// PathStepDirective
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Structural directive that associates a template with a step ID.
|
|
32
|
+
* Used inside `<pw-shell>` to define per-step content.
|
|
33
|
+
*
|
|
34
|
+
* ```html
|
|
35
|
+
* <pw-shell [path]="myPath">
|
|
36
|
+
* <ng-template pwStep="details"><app-details-form /></ng-template>
|
|
37
|
+
* <ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
38
|
+
* </pw-shell>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
@Directive({ selector: "[pwStep]", standalone: true })
|
|
42
|
+
export class PathStepDirective {
|
|
43
|
+
@Input({ required: true, alias: "pwStep" }) stepId!: string;
|
|
44
|
+
public constructor(public readonly templateRef: TemplateRef<unknown>) {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// PathShellComponent
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default UI shell component. Renders a progress indicator, step content,
|
|
53
|
+
* and navigation buttons.
|
|
54
|
+
*
|
|
55
|
+
* ```html
|
|
56
|
+
* <pw-shell [path]="myPath" [initialData]="{ name: '' }" (completed)="onDone($event)">
|
|
57
|
+
* <ng-template pwStep="details"><app-details-form /></ng-template>
|
|
58
|
+
* <ng-template pwStep="review"><app-review-panel /></ng-template>
|
|
59
|
+
* </pw-shell>
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
@Component({
|
|
63
|
+
selector: "pw-shell",
|
|
64
|
+
standalone: true,
|
|
65
|
+
imports: [CommonModule],
|
|
66
|
+
providers: [PathFacade],
|
|
67
|
+
changeDetection: ChangeDetectionStrategy.Default,
|
|
68
|
+
template: `
|
|
69
|
+
<!-- Empty state -->
|
|
70
|
+
<div class="pw-shell" *ngIf="!(facade.state$ | async)">
|
|
71
|
+
<div class="pw-shell__empty" *ngIf="!started">
|
|
72
|
+
<p>No active path.</p>
|
|
73
|
+
<button *ngIf="!autoStart" type="button" class="pw-shell__start-btn" (click)="doStart()">Start</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- Active path -->
|
|
78
|
+
<div class="pw-shell" *ngIf="facade.state$ | async as s">
|
|
79
|
+
<!-- Header — progress indicator -->
|
|
80
|
+
<div class="pw-shell__header" *ngIf="!hideProgress">
|
|
81
|
+
<div class="pw-shell__steps">
|
|
82
|
+
<div
|
|
83
|
+
*ngFor="let step of s.steps; let i = index"
|
|
84
|
+
class="pw-shell__step"
|
|
85
|
+
[ngClass]="'pw-shell__step--' + step.status"
|
|
86
|
+
>
|
|
87
|
+
<span class="pw-shell__step-dot">{{ step.status === 'completed' ? '✓' : (i + 1) }}</span>
|
|
88
|
+
<span class="pw-shell__step-label">{{ step.title ?? step.id }}</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="pw-shell__track">
|
|
92
|
+
<div class="pw-shell__track-fill" [style.width.%]="s.progress * 100"></div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Body — step content -->
|
|
97
|
+
<div class="pw-shell__body">
|
|
98
|
+
<ng-container *ngFor="let stepDir of stepDirectives">
|
|
99
|
+
<ng-container *ngIf="stepDir.stepId === s.stepId">
|
|
100
|
+
<ng-container *ngTemplateOutlet="stepDir.templateRef; injector: shellInjector"></ng-container>
|
|
101
|
+
</ng-container>
|
|
102
|
+
</ng-container>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- Validation messages -->
|
|
106
|
+
<ul class="pw-shell__validation" *ngIf="s.validationMessages.length > 0">
|
|
107
|
+
<li *ngFor="let msg of s.validationMessages" class="pw-shell__validation-item">{{ msg }}</li>
|
|
108
|
+
</ul>
|
|
109
|
+
|
|
110
|
+
<!-- Footer — navigation buttons -->
|
|
111
|
+
<div class="pw-shell__footer">
|
|
112
|
+
<div class="pw-shell__footer-left">
|
|
113
|
+
<button
|
|
114
|
+
*ngIf="!s.isFirstStep"
|
|
115
|
+
type="button"
|
|
116
|
+
class="pw-shell__btn pw-shell__btn--back"
|
|
117
|
+
[disabled]="s.isNavigating || !s.canMovePrevious"
|
|
118
|
+
(click)="facade.previous()"
|
|
119
|
+
>{{ backLabel }}</button>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="pw-shell__footer-right">
|
|
122
|
+
<button
|
|
123
|
+
*ngIf="!hideCancel"
|
|
124
|
+
type="button"
|
|
125
|
+
class="pw-shell__btn pw-shell__btn--cancel"
|
|
126
|
+
[disabled]="s.isNavigating"
|
|
127
|
+
(click)="facade.cancel()"
|
|
128
|
+
>{{ cancelLabel }}</button>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
class="pw-shell__btn pw-shell__btn--next"
|
|
132
|
+
[disabled]="s.isNavigating || !s.canMoveNext"
|
|
133
|
+
(click)="facade.next()"
|
|
134
|
+
>{{ s.isLastStep ? finishLabel : nextLabel }}</button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
`
|
|
139
|
+
})
|
|
140
|
+
export class PathShellComponent implements OnInit, OnDestroy {
|
|
141
|
+
/** The path definition to run. Required. */
|
|
142
|
+
@Input({ required: true }) path!: PathDefinition;
|
|
143
|
+
/** Initial data merged into the path engine on start. */
|
|
144
|
+
@Input() initialData: PathData = {};
|
|
145
|
+
/** Start the path automatically on ngOnInit. Set to false to call doStart() manually. */
|
|
146
|
+
@Input() autoStart = true;
|
|
147
|
+
/** Label for the Back navigation button. */
|
|
148
|
+
@Input() backLabel = "Back";
|
|
149
|
+
/** Label for the Next navigation button. */
|
|
150
|
+
@Input() nextLabel = "Next";
|
|
151
|
+
/** Label for the Next button when on the last step. */
|
|
152
|
+
@Input() finishLabel = "Finish";
|
|
153
|
+
/** Label for the Cancel button. */
|
|
154
|
+
@Input() cancelLabel = "Cancel";
|
|
155
|
+
/** Hide the Cancel button entirely. */
|
|
156
|
+
@Input() hideCancel = false;
|
|
157
|
+
/** Hide the step progress indicator in the header. */
|
|
158
|
+
@Input() hideProgress = false;
|
|
159
|
+
|
|
160
|
+
@Output() completed = new EventEmitter<PathData>();
|
|
161
|
+
@Output() cancelled = new EventEmitter<PathData>();
|
|
162
|
+
@Output() pathEvent = new EventEmitter<PathEvent>();
|
|
163
|
+
|
|
164
|
+
@ContentChildren(PathStepDirective) stepDirectives!: QueryList<PathStepDirective>;
|
|
165
|
+
|
|
166
|
+
public readonly facade = inject(PathFacade);
|
|
167
|
+
/** The shell's own component-level injector. Passed to ngTemplateOutlet so that
|
|
168
|
+
* step components can resolve PathFacade (provided by this shell) via inject(). */
|
|
169
|
+
protected readonly shellInjector = inject(Injector);
|
|
170
|
+
public started = false;
|
|
171
|
+
|
|
172
|
+
private readonly destroy$ = new Subject<void>();
|
|
173
|
+
|
|
174
|
+
public ngOnInit(): void {
|
|
175
|
+
this.facade.events$.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
|
176
|
+
this.pathEvent.emit(event);
|
|
177
|
+
if (event.type === "completed") this.completed.emit(event.data);
|
|
178
|
+
if (event.type === "cancelled") this.cancelled.emit(event.data);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (this.autoStart) {
|
|
182
|
+
this.doStart();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public ngOnDestroy(): void {
|
|
187
|
+
this.destroy$.next();
|
|
188
|
+
this.destroy$.complete();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public doStart(): void {
|
|
192
|
+
this.started = true;
|
|
193
|
+
this.facade.start(this.path, this.initialData);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|