@daltonr/pathwrite-vue 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 +2 -1
- package/dist/index.css +39 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +296 -0
package/README.md
CHANGED
|
@@ -50,9 +50,10 @@ const currentStep = computed(() => snapshot.value?.stepId ?? null);
|
|
|
50
50
|
| `start(definition, data?)` | `function` | Start or re-start a path. |
|
|
51
51
|
| `startSubPath(definition, data?)` | `function` | Push a sub-path. Requires an active path. |
|
|
52
52
|
| `next()` | `function` | Advance one step. Completes the path on the last step. |
|
|
53
|
-
| `previous()` | `function` | Go back one step.
|
|
53
|
+
| `previous()` | `function` | Go back one step. No-op when already on the first step of a top-level path. |
|
|
54
54
|
| `cancel()` | `function` | Cancel the active path (or sub-path). |
|
|
55
55
|
| `goToStep(stepId)` | `function` | Jump directly to a step by ID. Calls `onLeave` / `onEnter` but bypasses guards and `shouldSkip`. |
|
|
56
|
+
| `goToStepChecked(stepId)` | `function` | Jump to a step by ID, checking `canMoveNext` (forward) or `canMovePrevious` (backward) first. Navigation is blocked if the guard returns false. |
|
|
56
57
|
| `setData(key, value)` | `function` | Update a single data value; triggers re-render via `stateChanged`. When `TData` is specified, `key` and `value` are type-checked against your data shape. |
|
|
57
58
|
|
|
58
59
|
### Typed snapshot data
|
package/dist/index.css
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* Every visual value is a CSS custom property (--pw-*) so you can
|
|
7
7
|
* theme the shell without overriding selectors.
|
|
8
8
|
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* import "
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Usage (React / Vue):
|
|
10
|
+
* import "adapter-package/styles.css";
|
|
11
|
+
*
|
|
12
|
+
* Usage (Angular) — add to the styles array in angular.json:
|
|
13
|
+
* "node_modules/@daltonr/pathwrite-angular/dist/index.css"
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/* ------------------------------------------------------------------ */
|
|
@@ -170,6 +170,40 @@
|
|
|
170
170
|
min-height: 120px;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/* ------------------------------------------------------------------ */
|
|
174
|
+
/* Validation messages */
|
|
175
|
+
/* ------------------------------------------------------------------ */
|
|
176
|
+
:root {
|
|
177
|
+
--pw-color-error: #dc2626;
|
|
178
|
+
--pw-color-error-bg: #fef2f2;
|
|
179
|
+
--pw-color-error-border: #fecaca;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.pw-shell__validation {
|
|
183
|
+
list-style: none;
|
|
184
|
+
margin: 0;
|
|
185
|
+
padding: 12px 16px;
|
|
186
|
+
background: var(--pw-color-error-bg);
|
|
187
|
+
border: 1px solid var(--pw-color-error-border);
|
|
188
|
+
border-radius: var(--pw-shell-radius);
|
|
189
|
+
display: flex;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
gap: 4px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.pw-shell__validation-item {
|
|
195
|
+
font-size: 13px;
|
|
196
|
+
color: var(--pw-color-error);
|
|
197
|
+
padding-left: 16px;
|
|
198
|
+
position: relative;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.pw-shell__validation-item::before {
|
|
202
|
+
content: "•";
|
|
203
|
+
position: absolute;
|
|
204
|
+
left: 4px;
|
|
205
|
+
}
|
|
206
|
+
|
|
173
207
|
/* ------------------------------------------------------------------ */
|
|
174
208
|
/* Footer — navigation buttons */
|
|
175
209
|
/* ------------------------------------------------------------------ */
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
19
19
|
cancel: () => Promise<void>;
|
|
20
20
|
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
|
|
21
21
|
goToStep: (stepId: string) => Promise<void>;
|
|
22
|
+
/** Jump directly to a step by ID, checking the current step's canMoveNext (forward) or canMovePrevious (backward) guard first. Navigation is blocked if the guard returns false. */
|
|
23
|
+
goToStepChecked: (stepId: string) => Promise<void>;
|
|
22
24
|
/** Update a single data value; triggers a re-render via stateChanged. When `TData` is specified, `key` and `value` are type-checked against your data shape. */
|
|
23
25
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
24
26
|
}
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,9 @@ export function usePath(options) {
|
|
|
23
23
|
const previous = () => engine.previous();
|
|
24
24
|
const cancel = () => engine.cancel();
|
|
25
25
|
const goToStep = (stepId) => engine.goToStep(stepId);
|
|
26
|
+
const goToStepChecked = (stepId) => engine.goToStepChecked(stepId);
|
|
26
27
|
const setData = ((key, value) => engine.setData(key, value));
|
|
27
|
-
return { snapshot, start, startSubPath, next, previous, cancel, goToStep, setData };
|
|
28
|
+
return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
|
|
28
29
|
}
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// Context — provide / inject
|
|
@@ -116,6 +117,10 @@ export const PathShell = defineComponent({
|
|
|
116
117
|
: renderVueHeader(snap)),
|
|
117
118
|
// Body — step content
|
|
118
119
|
h("div", { class: "pw-shell__body" }, stepContent ?? []),
|
|
120
|
+
// Validation messages
|
|
121
|
+
snap.validationMessages.length > 0
|
|
122
|
+
? h("ul", { class: "pw-shell__validation" }, snap.validationMessages.map((msg, i) => h("li", { key: i, class: "pw-shell__validation-item" }, msg)))
|
|
123
|
+
: null,
|
|
119
124
|
// Footer — navigation
|
|
120
125
|
slots.footer
|
|
121
126
|
? slots.footer({ snapshot: snap, actions })
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,UAAU,EACV,QAAQ,EACR,cAAc,EACd,eAAe,EACf,CAAC,EAED,SAAS,EACT,OAAO,EACP,MAAM,EAMP,MAAM,KAAK,CAAC;AACb,OAAO,EAGL,UAAU,EAGX,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Vue 3 adapter for @daltonr/pathwrite-core — composables with reactive refs, and optional <PathShell> default UI.",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"types": "dist/index.d.ts",
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
36
|
+
"src",
|
|
36
37
|
"README.md",
|
|
37
38
|
"LICENSE"
|
|
38
39
|
],
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"vue": ">=3.3.0"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@daltonr/pathwrite-core": "^0.
|
|
49
|
+
"@daltonr/pathwrite-core": "^0.2.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@vue/test-utils": "^2.4.6",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ref,
|
|
3
|
+
shallowRef,
|
|
4
|
+
readonly,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
defineComponent,
|
|
7
|
+
h,
|
|
8
|
+
computed,
|
|
9
|
+
onMounted,
|
|
10
|
+
provide,
|
|
11
|
+
inject,
|
|
12
|
+
type Ref,
|
|
13
|
+
type DeepReadonly,
|
|
14
|
+
type PropType,
|
|
15
|
+
type InjectionKey,
|
|
16
|
+
type VNode
|
|
17
|
+
} from "vue";
|
|
18
|
+
import {
|
|
19
|
+
PathData,
|
|
20
|
+
PathDefinition,
|
|
21
|
+
PathEngine,
|
|
22
|
+
PathEvent,
|
|
23
|
+
PathSnapshot
|
|
24
|
+
} from "@daltonr/pathwrite-core";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Types
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export interface UsePathOptions {
|
|
31
|
+
/** Called for every engine event (stateChanged, completed, cancelled, resumed). */
|
|
32
|
+
onEvent?: (event: PathEvent) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UsePathReturn<TData extends PathData = PathData> {
|
|
36
|
+
/** Current path snapshot, or `null` when no path is active. Reactive — triggers Vue re-renders on change. */
|
|
37
|
+
snapshot: DeepReadonly<Ref<PathSnapshot<TData> | null>>;
|
|
38
|
+
/** Start (or restart) a path. */
|
|
39
|
+
start: (path: PathDefinition, initialData?: PathData) => Promise<void>;
|
|
40
|
+
/** Push a sub-path onto the stack. Requires an active path. */
|
|
41
|
+
startSubPath: (path: PathDefinition, initialData?: PathData) => Promise<void>;
|
|
42
|
+
/** Advance one step. Completes the path on the last step. */
|
|
43
|
+
next: () => Promise<void>;
|
|
44
|
+
/** Go back one step. Cancels the path from the first step. */
|
|
45
|
+
previous: () => Promise<void>;
|
|
46
|
+
/** Cancel the active path (or sub-path). */
|
|
47
|
+
cancel: () => Promise<void>;
|
|
48
|
+
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
|
|
49
|
+
goToStep: (stepId: string) => Promise<void>;
|
|
50
|
+
/** Jump directly to a step by ID, checking the current step's canMoveNext (forward) or canMovePrevious (backward) guard first. Navigation is blocked if the guard returns false. */
|
|
51
|
+
goToStepChecked: (stepId: string) => Promise<void>;
|
|
52
|
+
/** Update a single data value; triggers a re-render via stateChanged. When `TData` is specified, `key` and `value` are type-checked against your data shape. */
|
|
53
|
+
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// usePath composable
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
export function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData> {
|
|
61
|
+
const engine = new PathEngine();
|
|
62
|
+
const _snapshot = shallowRef<PathSnapshot<TData> | null>(null);
|
|
63
|
+
|
|
64
|
+
const unsubscribe = engine.subscribe((event: PathEvent) => {
|
|
65
|
+
if (event.type === "stateChanged" || event.type === "resumed") {
|
|
66
|
+
_snapshot.value = event.snapshot as PathSnapshot<TData>;
|
|
67
|
+
} else if (event.type === "completed" || event.type === "cancelled") {
|
|
68
|
+
_snapshot.value = null;
|
|
69
|
+
}
|
|
70
|
+
options?.onEvent?.(event);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
onScopeDispose(unsubscribe);
|
|
74
|
+
|
|
75
|
+
const snapshot = readonly(_snapshot) as DeepReadonly<Ref<PathSnapshot<TData> | null>>;
|
|
76
|
+
|
|
77
|
+
const start = (path: PathDefinition, initialData: PathData = {}): Promise<void> =>
|
|
78
|
+
engine.start(path, initialData);
|
|
79
|
+
|
|
80
|
+
const startSubPath = (path: PathDefinition, initialData: PathData = {}): Promise<void> =>
|
|
81
|
+
engine.startSubPath(path, initialData);
|
|
82
|
+
|
|
83
|
+
const next = (): Promise<void> => engine.next();
|
|
84
|
+
const previous = (): Promise<void> => engine.previous();
|
|
85
|
+
const cancel = (): Promise<void> => engine.cancel();
|
|
86
|
+
|
|
87
|
+
const goToStep = (stepId: string): Promise<void> => engine.goToStep(stepId);
|
|
88
|
+
const goToStepChecked = (stepId: string): Promise<void> => engine.goToStepChecked(stepId);
|
|
89
|
+
|
|
90
|
+
const setData = (<K extends string & keyof TData>(key: K, value: TData[K]): Promise<void> =>
|
|
91
|
+
engine.setData(key, value as unknown)) as UsePathReturn<TData>["setData"];
|
|
92
|
+
|
|
93
|
+
return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Context — provide / inject
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/** Injection key used by PathShell and usePathContext. */
|
|
101
|
+
const PathInjectionKey: InjectionKey<UsePathReturn> = Symbol("PathContext");
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Access the nearest `PathShell`'s path instance via Vue `inject`.
|
|
105
|
+
* Throws if used outside of a `<PathShell>`.
|
|
106
|
+
*
|
|
107
|
+
* The optional generic narrows `snapshot.data` for convenience — it is a
|
|
108
|
+
* **type-level assertion**, not a runtime guarantee.
|
|
109
|
+
*/
|
|
110
|
+
export function usePathContext<TData extends PathData = PathData>(): UsePathReturn<TData> {
|
|
111
|
+
const ctx = inject(PathInjectionKey, null);
|
|
112
|
+
if (ctx === null) {
|
|
113
|
+
throw new Error("usePathContext must be used within a <PathShell>.");
|
|
114
|
+
}
|
|
115
|
+
return ctx as UsePathReturn<TData>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Default UI — PathShell
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
export interface PathShellActions {
|
|
123
|
+
next: () => Promise<void>;
|
|
124
|
+
previous: () => Promise<void>;
|
|
125
|
+
cancel: () => Promise<void>;
|
|
126
|
+
goToStep: (stepId: string) => Promise<void>;
|
|
127
|
+
setData: (key: string, value: unknown) => Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `<PathShell>` — default UI shell that renders a progress indicator,
|
|
132
|
+
* step content, and navigation buttons. Step content is provided via
|
|
133
|
+
* **named slots** matching each step's `id`.
|
|
134
|
+
*
|
|
135
|
+
* ```vue
|
|
136
|
+
* <PathShell :path="myPath" :initial-data="{ name: '' }" @complete="handleDone">
|
|
137
|
+
* <template #details><DetailsForm /></template>
|
|
138
|
+
* <template #review><ReviewPanel /></template>
|
|
139
|
+
* </PathShell>
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export const PathShell = defineComponent({
|
|
143
|
+
name: "PathShell",
|
|
144
|
+
props: {
|
|
145
|
+
path: { type: Object as PropType<PathDefinition>, required: true },
|
|
146
|
+
initialData: { type: Object as PropType<PathData>, default: () => ({}) },
|
|
147
|
+
autoStart: { type: Boolean, default: true },
|
|
148
|
+
backLabel: { type: String, default: "Back" },
|
|
149
|
+
nextLabel: { type: String, default: "Next" },
|
|
150
|
+
finishLabel: { type: String, default: "Finish" },
|
|
151
|
+
cancelLabel: { type: String, default: "Cancel" },
|
|
152
|
+
hideCancel: { type: Boolean, default: false },
|
|
153
|
+
hideProgress: { type: Boolean, default: false }
|
|
154
|
+
},
|
|
155
|
+
emits: ["complete", "cancel", "event"],
|
|
156
|
+
setup(props, { slots, emit }) {
|
|
157
|
+
const pathReturn = usePath({
|
|
158
|
+
onEvent(event) {
|
|
159
|
+
emit("event", event);
|
|
160
|
+
if (event.type === "completed") emit("complete", event.data);
|
|
161
|
+
if (event.type === "cancelled") emit("cancel", event.data);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const { snapshot, start, next, previous, cancel, goToStep, setData } = pathReturn;
|
|
166
|
+
|
|
167
|
+
// Provide context so child components can use usePathContext()
|
|
168
|
+
provide(PathInjectionKey, pathReturn);
|
|
169
|
+
|
|
170
|
+
const started = ref(false);
|
|
171
|
+
onMounted(() => {
|
|
172
|
+
if (props.autoStart && !started.value) {
|
|
173
|
+
started.value = true;
|
|
174
|
+
start(props.path, props.initialData);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const actions: PathShellActions = { next, previous, cancel, goToStep, setData };
|
|
179
|
+
|
|
180
|
+
return () => {
|
|
181
|
+
const snap = snapshot.value as PathSnapshot | null;
|
|
182
|
+
|
|
183
|
+
if (!snap) {
|
|
184
|
+
return h("div", { class: "pw-shell" },
|
|
185
|
+
h("div", { class: "pw-shell__empty" }, [
|
|
186
|
+
h("p", "No active path."),
|
|
187
|
+
!props.autoStart
|
|
188
|
+
? h("button", {
|
|
189
|
+
type: "button",
|
|
190
|
+
class: "pw-shell__start-btn",
|
|
191
|
+
onClick: () => start(props.path, props.initialData)
|
|
192
|
+
}, "Start")
|
|
193
|
+
: null
|
|
194
|
+
])
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Resolve step content from named slot matching the current step ID
|
|
199
|
+
const stepSlot = slots[snap.stepId];
|
|
200
|
+
const stepContent = stepSlot ? stepSlot({ snapshot: snap }) : null;
|
|
201
|
+
|
|
202
|
+
return h("div", { class: "pw-shell" }, [
|
|
203
|
+
// Header — progress
|
|
204
|
+
!props.hideProgress && (
|
|
205
|
+
slots.header
|
|
206
|
+
? slots.header({ snapshot: snap })
|
|
207
|
+
: renderVueHeader(snap)
|
|
208
|
+
),
|
|
209
|
+
// Body — step content
|
|
210
|
+
h("div", { class: "pw-shell__body" }, stepContent ?? []),
|
|
211
|
+
// Validation messages
|
|
212
|
+
snap.validationMessages.length > 0
|
|
213
|
+
? h("ul", { class: "pw-shell__validation" },
|
|
214
|
+
snap.validationMessages.map((msg, i) =>
|
|
215
|
+
h("li", { key: i, class: "pw-shell__validation-item" }, msg)
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
: null,
|
|
219
|
+
// Footer — navigation
|
|
220
|
+
slots.footer
|
|
221
|
+
? slots.footer({ snapshot: snap, actions })
|
|
222
|
+
: renderVueFooter(snap, actions, props)
|
|
223
|
+
]);
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Default header (progress indicator)
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
function renderVueHeader(snapshot: PathSnapshot): VNode {
|
|
233
|
+
return h("div", { class: "pw-shell__header" }, [
|
|
234
|
+
h("div", { class: "pw-shell__steps" },
|
|
235
|
+
snapshot.steps.map((step, i) =>
|
|
236
|
+
h("div", {
|
|
237
|
+
key: step.id,
|
|
238
|
+
class: ["pw-shell__step", `pw-shell__step--${step.status}`]
|
|
239
|
+
}, [
|
|
240
|
+
h("span", { class: "pw-shell__step-dot" },
|
|
241
|
+
step.status === "completed" ? "✓" : String(i + 1)
|
|
242
|
+
),
|
|
243
|
+
h("span", { class: "pw-shell__step-label" },
|
|
244
|
+
step.title ?? step.id
|
|
245
|
+
)
|
|
246
|
+
])
|
|
247
|
+
)
|
|
248
|
+
),
|
|
249
|
+
h("div", { class: "pw-shell__track" },
|
|
250
|
+
h("div", {
|
|
251
|
+
class: "pw-shell__track-fill",
|
|
252
|
+
style: { width: `${snapshot.progress * 100}%` }
|
|
253
|
+
})
|
|
254
|
+
)
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Default footer (navigation buttons)
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
function renderVueFooter(
|
|
263
|
+
snapshot: PathSnapshot,
|
|
264
|
+
actions: PathShellActions,
|
|
265
|
+
props: { backLabel: string; nextLabel: string; finishLabel: string; cancelLabel: string; hideCancel: boolean }
|
|
266
|
+
): VNode {
|
|
267
|
+
return h("div", { class: "pw-shell__footer" }, [
|
|
268
|
+
h("div", { class: "pw-shell__footer-left" }, [
|
|
269
|
+
!snapshot.isFirstStep
|
|
270
|
+
? h("button", {
|
|
271
|
+
type: "button",
|
|
272
|
+
class: "pw-shell__btn pw-shell__btn--back",
|
|
273
|
+
disabled: snapshot.isNavigating || !snapshot.canMovePrevious,
|
|
274
|
+
onClick: actions.previous
|
|
275
|
+
}, props.backLabel)
|
|
276
|
+
: null
|
|
277
|
+
]),
|
|
278
|
+
h("div", { class: "pw-shell__footer-right" }, [
|
|
279
|
+
!props.hideCancel
|
|
280
|
+
? h("button", {
|
|
281
|
+
type: "button",
|
|
282
|
+
class: "pw-shell__btn pw-shell__btn--cancel",
|
|
283
|
+
disabled: snapshot.isNavigating,
|
|
284
|
+
onClick: actions.cancel
|
|
285
|
+
}, props.cancelLabel)
|
|
286
|
+
: null,
|
|
287
|
+
h("button", {
|
|
288
|
+
type: "button",
|
|
289
|
+
class: "pw-shell__btn pw-shell__btn--next",
|
|
290
|
+
disabled: snapshot.isNavigating || !snapshot.canMoveNext,
|
|
291
|
+
onClick: actions.next
|
|
292
|
+
}, snapshot.isLastStep ? props.finishLabel : props.nextLabel)
|
|
293
|
+
])
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
|