@daltonr/pathwrite-svelte 0.11.0 → 0.12.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 +10 -3
- package/dist/PathShell.svelte +91 -13
- package/dist/PathShell.svelte.d.ts +13 -4
- package/dist/PathShell.svelte.d.ts.map +1 -1
- package/dist/index.css +25 -0
- package/dist/index.svelte.d.ts +20 -5
- package/dist/index.svelte.d.ts.map +1 -1
- package/dist/index.svelte.js +12 -3
- package/package.json +2 -2
- package/src/PathShell.svelte +91 -13
- package/src/index.svelte.ts +18 -8
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ Peer dependencies: Svelte 5+.
|
|
|
70
70
|
|
|
71
71
|
| Return value | Type | Description |
|
|
72
72
|
|---|---|---|
|
|
73
|
-
| `snapshot` | `PathSnapshot \| null` | Reactive getter. `null` when no path is active. |
|
|
73
|
+
| `snapshot` | `PathSnapshot \| null` | Reactive getter. `null` when no path is active or when `completionBehaviour: "dismiss"` is used. With the default `"stayOnFinal"`, a non-null snapshot with `status === "completed"` is returned after the path finishes. |
|
|
74
74
|
| `start(definition, data?)` | `Promise<void>` | Start or restart a path. |
|
|
75
75
|
| `restart(definition, data?)` | `Promise<void>` | Tear down any active path and start fresh. |
|
|
76
76
|
| `next()` | `Promise<void>` | Advance one step. Completes on the last step. |
|
|
@@ -80,6 +80,7 @@ Peer dependencies: Svelte 5+.
|
|
|
80
80
|
| `goToStepChecked(stepId)` | `Promise<void>` | Jump to a step by ID, checking the current step's guard first. |
|
|
81
81
|
| `setData(key, value)` | `Promise<void>` | Update a single data field. Type-safe when `TData` is specified. |
|
|
82
82
|
| `startSubPath(definition, data?, meta?)` | `Promise<void>` | Push a sub-path. `meta` is returned to `onSubPathComplete`/`onSubPathCancel`. |
|
|
83
|
+
| `validate()` | `void` | Set `snapshot.hasValidated` without navigating. Triggers all inline field errors simultaneously. Used to validate all tabs in a nested shell at once. |
|
|
83
84
|
|
|
84
85
|
**Options:**
|
|
85
86
|
|
|
@@ -98,16 +99,22 @@ Step content is supplied as Svelte 5 snippets whose names match each step's `id`
|
|
|
98
99
|
| `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. |
|
|
99
100
|
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
|
|
100
101
|
| `autoStart` | `boolean` | `true` | Start on mount. Ignored when `engine` is provided. |
|
|
101
|
-
| `
|
|
102
|
+
| `layout` | `"wizard" \| "form" \| "auto" \| "tabs"` | `"auto"` | `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back. `"tabs"`: No progress header or footer — for tabbed interfaces. `"auto"` picks `"form"` for single-step paths. |
|
|
102
103
|
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. Also hidden automatically for single-step paths. |
|
|
103
104
|
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
104
105
|
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
105
106
|
| `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
|
|
106
107
|
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
107
108
|
| `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
|
|
109
|
+
| `validateWhen` | `boolean` | `false` | When it becomes `true`, calls `validate()` on the engine. Bind to the outer snapshot's `hasAttemptedNext` when this shell is nested inside a step of an outer shell. |
|
|
110
|
+
| `restoreKey` | `string` | — | When set, the shell automatically saves its full state (data + active step) into the nearest outer `PathShell`'s data under this key on every change, and restores from it on remount. No-op on a top-level shell. |
|
|
111
|
+
| `services` | `unknown` | `null` | Arbitrary services object available to step components via `usePathContext<TData, TServices>().services`. |
|
|
108
112
|
| `oncomplete` | `(data: PathData) => void` | — | Called when the path finishes naturally. |
|
|
109
113
|
| `oncancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
|
|
110
114
|
| `onevent` | `(event: PathEvent) => void` | — | Called for every engine event. |
|
|
115
|
+
| `completion` | `Snippet<[PathSnapshot<any>]>` | — | Custom snippet rendered when `snapshot.status === "completed"` (`completionBehaviour: "stayOnFinal"`). Receives the completed snapshot. If omitted, a default "All done." panel is shown. |
|
|
116
|
+
|
|
117
|
+
> **Note:** Svelte requires event/callback props to be lowercase. Unlike React/Vue/Angular, passing `onComplete`, `onCancel`, or `onEvent` (camelCase) will be silently ignored. PathShell emits a `console.warn` in development if it detects one of these common mistakes.
|
|
111
118
|
|
|
112
119
|
You can also replace the built-in header and footer with custom snippets:
|
|
113
120
|
|
|
@@ -155,4 +162,4 @@ You can also replace the built-in header and footer with custom snippets:
|
|
|
155
162
|
|
|
156
163
|
---
|
|
157
164
|
|
|
158
|
-
|
|
165
|
+
© 2026 Devjoy Ltd. MIT License.
|
package/dist/PathShell.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
|
-
import { usePath, setPathContext, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
3
|
+
import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
4
4
|
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
|
|
5
5
|
import type { Snippet, Component } from 'svelte';
|
|
6
6
|
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
path?: PathDefinition<any>;
|
|
10
10
|
engine?: PathEngine;
|
|
11
11
|
initialData?: PathData;
|
|
12
|
+
/**
|
|
13
|
+
* When set, this shell automatically saves its state into the nearest outer `PathShell`'s
|
|
14
|
+
* data under this key on every change, and restores from that stored state on remount.
|
|
15
|
+
* No-op when used on a top-level shell with no outer `PathShell` ancestor.
|
|
16
|
+
*/
|
|
17
|
+
restoreKey?: string;
|
|
12
18
|
autoStart?: boolean;
|
|
13
19
|
backLabel?: string;
|
|
14
20
|
nextLabel?: string;
|
|
@@ -22,12 +28,13 @@
|
|
|
22
28
|
/** When true, calls `validate()` on the engine so all steps show inline errors simultaneously. Useful when this shell is nested inside a step of an outer shell: bind to the outer snapshot's `hasAttemptedNext`. */
|
|
23
29
|
validateWhen?: boolean;
|
|
24
30
|
/**
|
|
25
|
-
*
|
|
31
|
+
* Shell layout mode:
|
|
26
32
|
* - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
|
|
27
|
-
* - "wizard": Back button on left, Cancel and Submit together on right.
|
|
28
|
-
* - "form": Cancel on left, Submit alone on right. Back button never shown.
|
|
33
|
+
* - "wizard": Progress header + Back button on left, Cancel and Submit together on right.
|
|
34
|
+
* - "form": Progress header + Cancel on left, Submit alone on right. Back button never shown.
|
|
35
|
+
* - "tabs": No progress header, no footer. Use for tabbed interfaces with a custom tab bar inside the step body.
|
|
29
36
|
*/
|
|
30
|
-
|
|
37
|
+
layout?: "wizard" | "form" | "auto" | "tabs";
|
|
31
38
|
/**
|
|
32
39
|
* Controls whether the shell renders its auto-generated field-error summary box.
|
|
33
40
|
* - `"summary"` (default): Shell renders the labeled error list below the step body.
|
|
@@ -55,6 +62,8 @@
|
|
|
55
62
|
// Optional override snippets for header and footer
|
|
56
63
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
57
64
|
footer?: Snippet<[PathSnapshot<any>, object]>;
|
|
65
|
+
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
66
|
+
completion?: Snippet<[PathSnapshot<any>]>;
|
|
58
67
|
// All other props treated as step components keyed by step ID
|
|
59
68
|
[key: string]: Component<any> | any;
|
|
60
69
|
}
|
|
@@ -63,6 +72,7 @@
|
|
|
63
72
|
path,
|
|
64
73
|
engine: engineProp,
|
|
65
74
|
initialData = {},
|
|
75
|
+
restoreKey = undefined,
|
|
66
76
|
autoStart = true,
|
|
67
77
|
backLabel = 'Previous',
|
|
68
78
|
nextLabel = 'Next',
|
|
@@ -73,7 +83,7 @@
|
|
|
73
83
|
hideProgress = false,
|
|
74
84
|
hideFooter = false,
|
|
75
85
|
validateWhen = false,
|
|
76
|
-
|
|
86
|
+
layout = 'auto',
|
|
77
87
|
validationDisplay = 'summary',
|
|
78
88
|
progressLayout = 'merged',
|
|
79
89
|
services = null,
|
|
@@ -82,9 +92,14 @@
|
|
|
82
92
|
onevent,
|
|
83
93
|
header,
|
|
84
94
|
footer,
|
|
95
|
+
completion,
|
|
85
96
|
...stepSnippets
|
|
86
97
|
}: Props = $props();
|
|
87
98
|
|
|
99
|
+
// Read outer PathShell context BEFORE setting our own — gives access to
|
|
100
|
+
// parent shell's snapshot and setData for restoreKey auto-wiring.
|
|
101
|
+
const outerCtx = getPathContextOrNull();
|
|
102
|
+
|
|
88
103
|
// Initialize path engine
|
|
89
104
|
const pathReturn = usePath({
|
|
90
105
|
get engine() { return engineProp; },
|
|
@@ -92,6 +107,11 @@
|
|
|
92
107
|
onevent?.(event);
|
|
93
108
|
if (event.type === 'completed') oncomplete?.(event.data);
|
|
94
109
|
if (event.type === 'cancelled') oncancel?.(event.data);
|
|
110
|
+
if (restoreKey && outerCtx && event.type === 'stateChanged') {
|
|
111
|
+
(outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
|
|
112
|
+
restoreKey, event.snapshot
|
|
113
|
+
);
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
});
|
|
97
117
|
|
|
@@ -112,12 +132,38 @@
|
|
|
112
132
|
get services() { return services; },
|
|
113
133
|
});
|
|
114
134
|
|
|
135
|
+
// Dev-mode warning: camelCase callback props are silently ignored in Svelte.
|
|
136
|
+
// Warn if the user passed onComplete/onCancel/onEvent instead of the correct
|
|
137
|
+
// lowercase forms oncomplete/oncancel/onevent.
|
|
138
|
+
if (import.meta.env?.DEV !== false) {
|
|
139
|
+
const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
|
|
140
|
+
for (const name of camelCallbacks) {
|
|
141
|
+
if (name in stepSnippets) {
|
|
142
|
+
console.warn(
|
|
143
|
+
`[PathShell] "${name}" was passed but will be ignored. Svelte uses lowercase callback props — use "${name.toLowerCase()}" instead.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
115
149
|
// Auto-start the path when no external engine is provided
|
|
116
150
|
let started = false;
|
|
117
151
|
onMount(() => {
|
|
118
152
|
if (autoStart && !started && !engineProp) {
|
|
119
153
|
started = true;
|
|
120
|
-
|
|
154
|
+
let startData: PathData = initialData ?? {};
|
|
155
|
+
let restoreStepId: string | undefined;
|
|
156
|
+
if (restoreKey && outerCtx) {
|
|
157
|
+
const stored = outerCtx.snapshot?.data[restoreKey] as PathSnapshot<any> | undefined;
|
|
158
|
+
if (stored != null && typeof stored === 'object' && 'stepId' in stored) {
|
|
159
|
+
startData = stored.data as PathData;
|
|
160
|
+
if (stored.stepIndex > 0) restoreStepId = stored.stepId as string;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const p = start(path, startData);
|
|
164
|
+
if (restoreStepId) {
|
|
165
|
+
p.then(() => goToStep(restoreStepId!));
|
|
166
|
+
}
|
|
121
167
|
}
|
|
122
168
|
});
|
|
123
169
|
|
|
@@ -136,11 +182,14 @@
|
|
|
136
182
|
let snap = $derived(pathReturn.snapshot);
|
|
137
183
|
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
138
184
|
|
|
185
|
+
let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
|
|
186
|
+
let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
|
|
187
|
+
|
|
139
188
|
// Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
140
189
|
let resolvedFooterLayout = $derived(
|
|
141
|
-
|
|
190
|
+
(layout === 'auto' || layout === 'tabs') && snap
|
|
142
191
|
? (snap.stepCount === 1 && snap.nestingLevel === 0 ? 'form' : 'wizard')
|
|
143
|
-
: (
|
|
192
|
+
: (layout === 'auto' || layout === 'tabs' ? 'wizard' : layout)
|
|
144
193
|
);
|
|
145
194
|
|
|
146
195
|
/**
|
|
@@ -167,9 +216,38 @@
|
|
|
167
216
|
</button>
|
|
168
217
|
{/if}
|
|
169
218
|
</div>
|
|
219
|
+
{:else if snap.status === 'completed'}
|
|
220
|
+
<!-- Completion panel: shown after stayOnFinal completion -->
|
|
221
|
+
{#if !effectiveHideProgress && snap.stepCount > 1}
|
|
222
|
+
<div class="pw-shell__header">
|
|
223
|
+
<div class="pw-shell__steps">
|
|
224
|
+
{#each snap.steps as step, i}
|
|
225
|
+
<div class="pw-shell__step pw-shell__step--{step.status}">
|
|
226
|
+
<span class="pw-shell__step-dot">✓</span>
|
|
227
|
+
<span class="pw-shell__step-label">{step.title ?? step.id}</span>
|
|
228
|
+
</div>
|
|
229
|
+
{/each}
|
|
230
|
+
</div>
|
|
231
|
+
<div class="pw-shell__track">
|
|
232
|
+
<div class="pw-shell__track-fill" style="width: 100%"></div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
{/if}
|
|
236
|
+
<div class="pw-shell__body">
|
|
237
|
+
{#if completion}
|
|
238
|
+
{@render completion(snap)}
|
|
239
|
+
{:else}
|
|
240
|
+
<div class="pw-shell__completion">
|
|
241
|
+
<p class="pw-shell__completion-message">All done.</p>
|
|
242
|
+
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(path, initialData)}>
|
|
243
|
+
Start over
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
{/if}
|
|
247
|
+
</div>
|
|
170
248
|
{:else}
|
|
171
249
|
<!-- Root progress: persistent top-level bar visible during sub-paths -->
|
|
172
|
-
{#if !
|
|
250
|
+
{#if !effectiveHideProgress && snap.rootProgress && progressLayout !== 'activeOnly'}
|
|
173
251
|
<div class="pw-shell__root-progress">
|
|
174
252
|
<div class="pw-shell__steps">
|
|
175
253
|
{#each snap.rootProgress.steps as step, i}
|
|
@@ -188,7 +266,7 @@
|
|
|
188
266
|
{/if}
|
|
189
267
|
|
|
190
268
|
<!-- Header: progress indicator (overridable via header snippet) -->
|
|
191
|
-
{#if !
|
|
269
|
+
{#if !effectiveHideProgress && progressLayout !== 'rootOnly'}
|
|
192
270
|
{#if header}
|
|
193
271
|
{@render header(snap)}
|
|
194
272
|
{:else if snap.stepCount > 1 || snap.nestingLevel > 0}
|
|
@@ -283,9 +361,9 @@
|
|
|
283
361
|
</div>
|
|
284
362
|
</div>
|
|
285
363
|
<!-- Footer: navigation buttons (overridable via footer snippet) -->
|
|
286
|
-
{:else if !
|
|
364
|
+
{:else if !effectiveHideFooter && footer}
|
|
287
365
|
{@render footer(snap, actions)}
|
|
288
|
-
{:else if !
|
|
366
|
+
{:else if !effectiveHideFooter}
|
|
289
367
|
<div class="pw-shell__footer">
|
|
290
368
|
<div class="pw-shell__footer-left">
|
|
291
369
|
{#if resolvedFooterLayout === 'form' && !hideCancel}
|
|
@@ -4,6 +4,12 @@ interface Props {
|
|
|
4
4
|
path?: PathDefinition<any>;
|
|
5
5
|
engine?: PathEngine;
|
|
6
6
|
initialData?: PathData;
|
|
7
|
+
/**
|
|
8
|
+
* When set, this shell automatically saves its state into the nearest outer `PathShell`'s
|
|
9
|
+
* data under this key on every change, and restores from that stored state on remount.
|
|
10
|
+
* No-op when used on a top-level shell with no outer `PathShell` ancestor.
|
|
11
|
+
*/
|
|
12
|
+
restoreKey?: string;
|
|
7
13
|
autoStart?: boolean;
|
|
8
14
|
backLabel?: string;
|
|
9
15
|
nextLabel?: string;
|
|
@@ -17,12 +23,13 @@ interface Props {
|
|
|
17
23
|
/** When true, calls `validate()` on the engine so all steps show inline errors simultaneously. Useful when this shell is nested inside a step of an outer shell: bind to the outer snapshot's `hasAttemptedNext`. */
|
|
18
24
|
validateWhen?: boolean;
|
|
19
25
|
/**
|
|
20
|
-
*
|
|
26
|
+
* Shell layout mode:
|
|
21
27
|
* - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
|
|
22
|
-
* - "wizard": Back button on left, Cancel and Submit together on right.
|
|
23
|
-
* - "form": Cancel on left, Submit alone on right. Back button never shown.
|
|
28
|
+
* - "wizard": Progress header + Back button on left, Cancel and Submit together on right.
|
|
29
|
+
* - "form": Progress header + Cancel on left, Submit alone on right. Back button never shown.
|
|
30
|
+
* - "tabs": No progress header, no footer. Use for tabbed interfaces with a custom tab bar inside the step body.
|
|
24
31
|
*/
|
|
25
|
-
|
|
32
|
+
layout?: "wizard" | "form" | "auto" | "tabs";
|
|
26
33
|
/**
|
|
27
34
|
* Controls whether the shell renders its auto-generated field-error summary box.
|
|
28
35
|
* - `"summary"` (default): Shell renders the labeled error list below the step body.
|
|
@@ -48,6 +55,8 @@ interface Props {
|
|
|
48
55
|
onevent?: (event: any) => void;
|
|
49
56
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
50
57
|
footer?: Snippet<[PathSnapshot<any>, object]>;
|
|
58
|
+
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
59
|
+
completion?: Snippet<[PathSnapshot<any>]>;
|
|
51
60
|
[key: string]: Component<any> | any;
|
|
52
61
|
}
|
|
53
62
|
declare const PathShell: Component<Props, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PathShell.svelte.d.ts","sourceRoot":"","sources":["../src/PathShell.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5G,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8HAA8H;IAC9H,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qNAAqN;IACrN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB
|
|
1
|
+
{"version":3,"file":"PathShell.svelte.d.ts","sourceRoot":"","sources":["../src/PathShell.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5G,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8HAA8H;IAC9H,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qNAAqN;IACrN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAE/B,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,6HAA6H;IAC7H,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;AA0UH,QAAA,MAAM,SAAS;mBA7LQ,QAAQ,IAAI,CAAC;MA6LmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
package/dist/index.css
CHANGED
|
@@ -77,6 +77,31 @@
|
|
|
77
77
|
font-size: 14px;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/* ------------------------------------------------------------------ */
|
|
81
|
+
/* Completion panel */
|
|
82
|
+
/* ------------------------------------------------------------------ */
|
|
83
|
+
.pw-shell__completion {
|
|
84
|
+
text-align: center;
|
|
85
|
+
padding: 40px 16px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.pw-shell__completion-message {
|
|
89
|
+
font-size: 18px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: var(--pw-color-text);
|
|
92
|
+
margin: 0 0 20px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.pw-shell__completion-restart {
|
|
96
|
+
border: 1px solid var(--pw-color-btn-border);
|
|
97
|
+
background: var(--pw-color-btn-bg);
|
|
98
|
+
color: var(--pw-color-text);
|
|
99
|
+
padding: var(--pw-btn-padding);
|
|
100
|
+
border-radius: var(--pw-btn-radius);
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
font-size: 14px;
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
/* ------------------------------------------------------------------ */
|
|
81
106
|
/* Root progress — persistent top-level bar visible during sub-paths */
|
|
82
107
|
/* ------------------------------------------------------------------ */
|
package/dist/index.svelte.d.ts
CHANGED
|
@@ -34,10 +34,14 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
34
34
|
previous: () => Promise<void>;
|
|
35
35
|
/** Cancel the active path (or sub-path). */
|
|
36
36
|
cancel: () => Promise<void>;
|
|
37
|
-
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
|
|
38
|
-
goToStep: (stepId: string
|
|
37
|
+
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. Pass `{ validateOnLeave: true }` to mark the departing step as attempted before navigating. */
|
|
38
|
+
goToStep: (stepId: string, options?: {
|
|
39
|
+
validateOnLeave?: boolean;
|
|
40
|
+
}) => Promise<void>;
|
|
39
41
|
/** 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. */
|
|
40
|
-
goToStepChecked: (stepId: string
|
|
42
|
+
goToStepChecked: (stepId: string, options?: {
|
|
43
|
+
validateOnLeave?: boolean;
|
|
44
|
+
}) => Promise<void>;
|
|
41
45
|
/** 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. */
|
|
42
46
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
43
47
|
/** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
|
|
@@ -115,8 +119,12 @@ export interface PathContext<TData extends PathData = PathData, TServices = unkn
|
|
|
115
119
|
next: () => Promise<void>;
|
|
116
120
|
previous: () => Promise<void>;
|
|
117
121
|
cancel: () => Promise<void>;
|
|
118
|
-
goToStep: (stepId: string
|
|
119
|
-
|
|
122
|
+
goToStep: (stepId: string, options?: {
|
|
123
|
+
validateOnLeave?: boolean;
|
|
124
|
+
}) => Promise<void>;
|
|
125
|
+
goToStepChecked: (stepId: string, options?: {
|
|
126
|
+
validateOnLeave?: boolean;
|
|
127
|
+
}) => Promise<void>;
|
|
120
128
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
121
129
|
resetStep: () => Promise<void>;
|
|
122
130
|
restart: () => Promise<void>;
|
|
@@ -156,6 +164,13 @@ export declare function usePathContext<TData extends PathData = PathData, TServi
|
|
|
156
164
|
* Used by PathShell component.
|
|
157
165
|
*/
|
|
158
166
|
export declare function setPathContext<TData extends PathData = PathData, TServices = unknown>(ctx: PathContext<TData, TServices>): void;
|
|
167
|
+
/**
|
|
168
|
+
* Internal: Get the PathContext from the nearest ancestor PathShell, or
|
|
169
|
+
* `undefined` if no PathShell is present. Used by PathShell itself to access
|
|
170
|
+
* the outer shell's context for `restoreKey` auto-wiring — must be called
|
|
171
|
+
* before `setPathContext()` so it reads the parent rather than self.
|
|
172
|
+
*/
|
|
173
|
+
export declare function getPathContextOrNull<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices> | undefined;
|
|
159
174
|
/**
|
|
160
175
|
* Create a two-way binding helper for form inputs.
|
|
161
176
|
* Returns an object with a reactive `value` property.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,UAAU,EACV,SAAS,EACT,YAAY,EACb,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,YAAY,EACV,QAAQ,EACR,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC9D;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC9C,iCAAiC;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,6MAA6M;IAC7M,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnH,6DAA6D;IAC7D,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qJAAqJ;IACrJ,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,
|
|
1
|
+
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/index.svelte.js
CHANGED
|
@@ -69,7 +69,7 @@ export function usePath(options) {
|
|
|
69
69
|
_snapshot = event.snapshot;
|
|
70
70
|
}
|
|
71
71
|
else if (event.type === "completed" || event.type === "cancelled") {
|
|
72
|
-
_snapshot =
|
|
72
|
+
_snapshot = engine.snapshot();
|
|
73
73
|
}
|
|
74
74
|
options?.onEvent?.(event);
|
|
75
75
|
});
|
|
@@ -80,8 +80,8 @@ export function usePath(options) {
|
|
|
80
80
|
const next = () => engine.next();
|
|
81
81
|
const previous = () => engine.previous();
|
|
82
82
|
const cancel = () => engine.cancel();
|
|
83
|
-
const goToStep = (stepId) => engine.goToStep(stepId);
|
|
84
|
-
const goToStepChecked = (stepId) => engine.goToStepChecked(stepId);
|
|
83
|
+
const goToStep = (stepId, options) => engine.goToStep(stepId, options);
|
|
84
|
+
const goToStepChecked = (stepId, options) => engine.goToStepChecked(stepId, options);
|
|
85
85
|
const setData = ((key, value) => engine.setData(key, value));
|
|
86
86
|
const resetStep = () => engine.resetStep();
|
|
87
87
|
const restart = () => engine.restart();
|
|
@@ -144,6 +144,15 @@ export function usePathContext() {
|
|
|
144
144
|
export function setPathContext(ctx) {
|
|
145
145
|
setContext(PATH_CONTEXT_KEY, ctx);
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Internal: Get the PathContext from the nearest ancestor PathShell, or
|
|
149
|
+
* `undefined` if no PathShell is present. Used by PathShell itself to access
|
|
150
|
+
* the outer shell's context for `restoreKey` auto-wiring — must be called
|
|
151
|
+
* before `setPathContext()` so it reads the parent rather than self.
|
|
152
|
+
*/
|
|
153
|
+
export function getPathContextOrNull() {
|
|
154
|
+
return getContext(PATH_CONTEXT_KEY);
|
|
155
|
+
}
|
|
147
156
|
// ---------------------------------------------------------------------------
|
|
148
157
|
// Helper for binding form inputs
|
|
149
158
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Svelte 5 adapter for @daltonr/pathwrite-core — runes-based reactive bindings and optional PathShell component.",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"svelte": ">=5.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@daltonr/pathwrite-core": "^0.
|
|
55
|
+
"@daltonr/pathwrite-core": "^0.12.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@sveltejs/package": "^2.5.7",
|
package/src/PathShell.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
|
-
import { usePath, setPathContext, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
3
|
+
import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
4
4
|
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
|
|
5
5
|
import type { Snippet, Component } from 'svelte';
|
|
6
6
|
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
path?: PathDefinition<any>;
|
|
10
10
|
engine?: PathEngine;
|
|
11
11
|
initialData?: PathData;
|
|
12
|
+
/**
|
|
13
|
+
* When set, this shell automatically saves its state into the nearest outer `PathShell`'s
|
|
14
|
+
* data under this key on every change, and restores from that stored state on remount.
|
|
15
|
+
* No-op when used on a top-level shell with no outer `PathShell` ancestor.
|
|
16
|
+
*/
|
|
17
|
+
restoreKey?: string;
|
|
12
18
|
autoStart?: boolean;
|
|
13
19
|
backLabel?: string;
|
|
14
20
|
nextLabel?: string;
|
|
@@ -22,12 +28,13 @@
|
|
|
22
28
|
/** When true, calls `validate()` on the engine so all steps show inline errors simultaneously. Useful when this shell is nested inside a step of an outer shell: bind to the outer snapshot's `hasAttemptedNext`. */
|
|
23
29
|
validateWhen?: boolean;
|
|
24
30
|
/**
|
|
25
|
-
*
|
|
31
|
+
* Shell layout mode:
|
|
26
32
|
* - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
|
|
27
|
-
* - "wizard": Back button on left, Cancel and Submit together on right.
|
|
28
|
-
* - "form": Cancel on left, Submit alone on right. Back button never shown.
|
|
33
|
+
* - "wizard": Progress header + Back button on left, Cancel and Submit together on right.
|
|
34
|
+
* - "form": Progress header + Cancel on left, Submit alone on right. Back button never shown.
|
|
35
|
+
* - "tabs": No progress header, no footer. Use for tabbed interfaces with a custom tab bar inside the step body.
|
|
29
36
|
*/
|
|
30
|
-
|
|
37
|
+
layout?: "wizard" | "form" | "auto" | "tabs";
|
|
31
38
|
/**
|
|
32
39
|
* Controls whether the shell renders its auto-generated field-error summary box.
|
|
33
40
|
* - `"summary"` (default): Shell renders the labeled error list below the step body.
|
|
@@ -55,6 +62,8 @@
|
|
|
55
62
|
// Optional override snippets for header and footer
|
|
56
63
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
57
64
|
footer?: Snippet<[PathSnapshot<any>, object]>;
|
|
65
|
+
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
66
|
+
completion?: Snippet<[PathSnapshot<any>]>;
|
|
58
67
|
// All other props treated as step components keyed by step ID
|
|
59
68
|
[key: string]: Component<any> | any;
|
|
60
69
|
}
|
|
@@ -63,6 +72,7 @@
|
|
|
63
72
|
path,
|
|
64
73
|
engine: engineProp,
|
|
65
74
|
initialData = {},
|
|
75
|
+
restoreKey = undefined,
|
|
66
76
|
autoStart = true,
|
|
67
77
|
backLabel = 'Previous',
|
|
68
78
|
nextLabel = 'Next',
|
|
@@ -73,7 +83,7 @@
|
|
|
73
83
|
hideProgress = false,
|
|
74
84
|
hideFooter = false,
|
|
75
85
|
validateWhen = false,
|
|
76
|
-
|
|
86
|
+
layout = 'auto',
|
|
77
87
|
validationDisplay = 'summary',
|
|
78
88
|
progressLayout = 'merged',
|
|
79
89
|
services = null,
|
|
@@ -82,9 +92,14 @@
|
|
|
82
92
|
onevent,
|
|
83
93
|
header,
|
|
84
94
|
footer,
|
|
95
|
+
completion,
|
|
85
96
|
...stepSnippets
|
|
86
97
|
}: Props = $props();
|
|
87
98
|
|
|
99
|
+
// Read outer PathShell context BEFORE setting our own — gives access to
|
|
100
|
+
// parent shell's snapshot and setData for restoreKey auto-wiring.
|
|
101
|
+
const outerCtx = getPathContextOrNull();
|
|
102
|
+
|
|
88
103
|
// Initialize path engine
|
|
89
104
|
const pathReturn = usePath({
|
|
90
105
|
get engine() { return engineProp; },
|
|
@@ -92,6 +107,11 @@
|
|
|
92
107
|
onevent?.(event);
|
|
93
108
|
if (event.type === 'completed') oncomplete?.(event.data);
|
|
94
109
|
if (event.type === 'cancelled') oncancel?.(event.data);
|
|
110
|
+
if (restoreKey && outerCtx && event.type === 'stateChanged') {
|
|
111
|
+
(outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
|
|
112
|
+
restoreKey, event.snapshot
|
|
113
|
+
);
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
});
|
|
97
117
|
|
|
@@ -112,12 +132,38 @@
|
|
|
112
132
|
get services() { return services; },
|
|
113
133
|
});
|
|
114
134
|
|
|
135
|
+
// Dev-mode warning: camelCase callback props are silently ignored in Svelte.
|
|
136
|
+
// Warn if the user passed onComplete/onCancel/onEvent instead of the correct
|
|
137
|
+
// lowercase forms oncomplete/oncancel/onevent.
|
|
138
|
+
if (import.meta.env?.DEV !== false) {
|
|
139
|
+
const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
|
|
140
|
+
for (const name of camelCallbacks) {
|
|
141
|
+
if (name in stepSnippets) {
|
|
142
|
+
console.warn(
|
|
143
|
+
`[PathShell] "${name}" was passed but will be ignored. Svelte uses lowercase callback props — use "${name.toLowerCase()}" instead.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
115
149
|
// Auto-start the path when no external engine is provided
|
|
116
150
|
let started = false;
|
|
117
151
|
onMount(() => {
|
|
118
152
|
if (autoStart && !started && !engineProp) {
|
|
119
153
|
started = true;
|
|
120
|
-
|
|
154
|
+
let startData: PathData = initialData ?? {};
|
|
155
|
+
let restoreStepId: string | undefined;
|
|
156
|
+
if (restoreKey && outerCtx) {
|
|
157
|
+
const stored = outerCtx.snapshot?.data[restoreKey] as PathSnapshot<any> | undefined;
|
|
158
|
+
if (stored != null && typeof stored === 'object' && 'stepId' in stored) {
|
|
159
|
+
startData = stored.data as PathData;
|
|
160
|
+
if (stored.stepIndex > 0) restoreStepId = stored.stepId as string;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const p = start(path, startData);
|
|
164
|
+
if (restoreStepId) {
|
|
165
|
+
p.then(() => goToStep(restoreStepId!));
|
|
166
|
+
}
|
|
121
167
|
}
|
|
122
168
|
});
|
|
123
169
|
|
|
@@ -136,11 +182,14 @@
|
|
|
136
182
|
let snap = $derived(pathReturn.snapshot);
|
|
137
183
|
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
138
184
|
|
|
185
|
+
let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
|
|
186
|
+
let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
|
|
187
|
+
|
|
139
188
|
// Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
140
189
|
let resolvedFooterLayout = $derived(
|
|
141
|
-
|
|
190
|
+
(layout === 'auto' || layout === 'tabs') && snap
|
|
142
191
|
? (snap.stepCount === 1 && snap.nestingLevel === 0 ? 'form' : 'wizard')
|
|
143
|
-
: (
|
|
192
|
+
: (layout === 'auto' || layout === 'tabs' ? 'wizard' : layout)
|
|
144
193
|
);
|
|
145
194
|
|
|
146
195
|
/**
|
|
@@ -167,9 +216,38 @@
|
|
|
167
216
|
</button>
|
|
168
217
|
{/if}
|
|
169
218
|
</div>
|
|
219
|
+
{:else if snap.status === 'completed'}
|
|
220
|
+
<!-- Completion panel: shown after stayOnFinal completion -->
|
|
221
|
+
{#if !effectiveHideProgress && snap.stepCount > 1}
|
|
222
|
+
<div class="pw-shell__header">
|
|
223
|
+
<div class="pw-shell__steps">
|
|
224
|
+
{#each snap.steps as step, i}
|
|
225
|
+
<div class="pw-shell__step pw-shell__step--{step.status}">
|
|
226
|
+
<span class="pw-shell__step-dot">✓</span>
|
|
227
|
+
<span class="pw-shell__step-label">{step.title ?? step.id}</span>
|
|
228
|
+
</div>
|
|
229
|
+
{/each}
|
|
230
|
+
</div>
|
|
231
|
+
<div class="pw-shell__track">
|
|
232
|
+
<div class="pw-shell__track-fill" style="width: 100%"></div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
{/if}
|
|
236
|
+
<div class="pw-shell__body">
|
|
237
|
+
{#if completion}
|
|
238
|
+
{@render completion(snap)}
|
|
239
|
+
{:else}
|
|
240
|
+
<div class="pw-shell__completion">
|
|
241
|
+
<p class="pw-shell__completion-message">All done.</p>
|
|
242
|
+
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(path, initialData)}>
|
|
243
|
+
Start over
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
{/if}
|
|
247
|
+
</div>
|
|
170
248
|
{:else}
|
|
171
249
|
<!-- Root progress: persistent top-level bar visible during sub-paths -->
|
|
172
|
-
{#if !
|
|
250
|
+
{#if !effectiveHideProgress && snap.rootProgress && progressLayout !== 'activeOnly'}
|
|
173
251
|
<div class="pw-shell__root-progress">
|
|
174
252
|
<div class="pw-shell__steps">
|
|
175
253
|
{#each snap.rootProgress.steps as step, i}
|
|
@@ -188,7 +266,7 @@
|
|
|
188
266
|
{/if}
|
|
189
267
|
|
|
190
268
|
<!-- Header: progress indicator (overridable via header snippet) -->
|
|
191
|
-
{#if !
|
|
269
|
+
{#if !effectiveHideProgress && progressLayout !== 'rootOnly'}
|
|
192
270
|
{#if header}
|
|
193
271
|
{@render header(snap)}
|
|
194
272
|
{:else if snap.stepCount > 1 || snap.nestingLevel > 0}
|
|
@@ -283,9 +361,9 @@
|
|
|
283
361
|
</div>
|
|
284
362
|
</div>
|
|
285
363
|
<!-- Footer: navigation buttons (overridable via footer snippet) -->
|
|
286
|
-
{:else if !
|
|
364
|
+
{:else if !effectiveHideFooter && footer}
|
|
287
365
|
{@render footer(snap, actions)}
|
|
288
|
-
{:else if !
|
|
366
|
+
{:else if !effectiveHideFooter}
|
|
289
367
|
<div class="pw-shell__footer">
|
|
290
368
|
<div class="pw-shell__footer-left">
|
|
291
369
|
{#if resolvedFooterLayout === 'form' && !hideCancel}
|
package/src/index.svelte.ts
CHANGED
|
@@ -62,10 +62,10 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
62
62
|
previous: () => Promise<void>;
|
|
63
63
|
/** Cancel the active path (or sub-path). */
|
|
64
64
|
cancel: () => Promise<void>;
|
|
65
|
-
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
|
|
66
|
-
goToStep: (stepId: string) => Promise<void>;
|
|
65
|
+
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. Pass `{ validateOnLeave: true }` to mark the departing step as attempted before navigating. */
|
|
66
|
+
goToStep: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
67
67
|
/** 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. */
|
|
68
|
-
goToStepChecked: (stepId: string) => Promise<void>;
|
|
68
|
+
goToStepChecked: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
69
69
|
/** 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. */
|
|
70
70
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
71
71
|
/** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
|
|
@@ -157,7 +157,7 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
157
157
|
if (event.type === "stateChanged" || event.type === "resumed") {
|
|
158
158
|
_snapshot = event.snapshot as PathSnapshot<TData>;
|
|
159
159
|
} else if (event.type === "completed" || event.type === "cancelled") {
|
|
160
|
-
_snapshot = null;
|
|
160
|
+
_snapshot = engine.snapshot() as PathSnapshot<TData> | null;
|
|
161
161
|
}
|
|
162
162
|
options?.onEvent?.(event);
|
|
163
163
|
});
|
|
@@ -178,8 +178,8 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
178
178
|
const previous = (): Promise<void> => engine.previous();
|
|
179
179
|
const cancel = (): Promise<void> => engine.cancel();
|
|
180
180
|
|
|
181
|
-
const goToStep = (stepId: string): Promise<void> => engine.goToStep(stepId);
|
|
182
|
-
const goToStepChecked = (stepId: string): Promise<void> => engine.goToStepChecked(stepId);
|
|
181
|
+
const goToStep = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> => engine.goToStep(stepId, options);
|
|
182
|
+
const goToStepChecked = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> => engine.goToStepChecked(stepId, options);
|
|
183
183
|
|
|
184
184
|
const setData = (<K extends string & keyof TData>(key: K, value: TData[K]): Promise<void> =>
|
|
185
185
|
engine.setData(key, value as unknown)) as UsePathReturn<TData>["setData"];
|
|
@@ -221,8 +221,8 @@ export interface PathContext<TData extends PathData = PathData, TServices = unkn
|
|
|
221
221
|
next: () => Promise<void>;
|
|
222
222
|
previous: () => Promise<void>;
|
|
223
223
|
cancel: () => Promise<void>;
|
|
224
|
-
goToStep: (stepId: string) => Promise<void>;
|
|
225
|
-
goToStepChecked: (stepId: string) => Promise<void>;
|
|
224
|
+
goToStep: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
225
|
+
goToStepChecked: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
226
226
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
227
227
|
resetStep: () => Promise<void>;
|
|
228
228
|
restart: () => Promise<void>;
|
|
@@ -276,6 +276,16 @@ export function setPathContext<TData extends PathData = PathData, TServices = un
|
|
|
276
276
|
setContext(PATH_CONTEXT_KEY, ctx);
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Internal: Get the PathContext from the nearest ancestor PathShell, or
|
|
281
|
+
* `undefined` if no PathShell is present. Used by PathShell itself to access
|
|
282
|
+
* the outer shell's context for `restoreKey` auto-wiring — must be called
|
|
283
|
+
* before `setPathContext()` so it reads the parent rather than self.
|
|
284
|
+
*/
|
|
285
|
+
export function getPathContextOrNull<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices> | undefined {
|
|
286
|
+
return getContext<PathContext<TData, TServices>>(PATH_CONTEXT_KEY);
|
|
287
|
+
}
|
|
288
|
+
|
|
279
289
|
// ---------------------------------------------------------------------------
|
|
280
290
|
// Helper for binding form inputs
|
|
281
291
|
// ---------------------------------------------------------------------------
|