@daltonr/pathwrite-svelte 0.9.0 → 0.10.1
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 +81 -470
- package/dist/PathShell.svelte +71 -18
- package/dist/PathShell.svelte.d.ts +6 -0
- package/dist/PathShell.svelte.d.ts.map +1 -1
- package/dist/index.css +86 -0
- package/dist/index.svelte.d.ts +30 -6
- package/dist/index.svelte.d.ts.map +1 -1
- package/dist/index.svelte.js +24 -6
- package/package.json +2 -2
- package/src/PathShell.svelte +71 -18
- package/src/index.svelte.ts +41 -10
package/README.md
CHANGED
|
@@ -1,547 +1,158 @@
|
|
|
1
1
|
# @daltonr/pathwrite-svelte
|
|
2
2
|
|
|
3
|
-
Svelte 5 adapter for
|
|
3
|
+
Svelte 5 adapter for `@daltonr/pathwrite-core` — runes-based reactive state with an optional `<PathShell>` UI component.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @daltonr/pathwrite-svelte
|
|
8
|
+
npm install @daltonr/pathwrite-core @daltonr/pathwrite-svelte
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Peer dependencies: Svelte 5+.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
> Uses Svelte 5 runes (`$state`, `$derived`, `$props`) and snippets (`{#snippet}`, `{@render}`). Not compatible with Svelte 4.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Quick start
|
|
16
16
|
|
|
17
17
|
```svelte
|
|
18
|
+
<!-- JobApplicationFlow.svelte -->
|
|
18
19
|
<script lang="ts">
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const myPath = {
|
|
25
|
-
id: 'signup',
|
|
26
|
-
steps: [
|
|
27
|
-
{ id: 'details', title: 'Your Details' },
|
|
28
|
-
{ id: 'review', title: 'Review' }
|
|
29
|
-
]
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
onMount(() => {
|
|
33
|
-
start(myPath, { name: '', email: '' });
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
let snap = $derived($snapshot);
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
{#if snap}
|
|
40
|
-
<h2>{snap.steps[snap.stepIndex].title}</h2>
|
|
41
|
-
|
|
42
|
-
{#if snap.stepId === 'details'}
|
|
43
|
-
<input
|
|
44
|
-
type="text"
|
|
45
|
-
value={snap.data.name || ''}
|
|
46
|
-
oninput={(e) => setData('name', e.currentTarget.value)}
|
|
47
|
-
placeholder="Name"
|
|
48
|
-
/>
|
|
49
|
-
<input
|
|
50
|
-
type="email"
|
|
51
|
-
value={snap.data.email || ''}
|
|
52
|
-
oninput={(e) => setData('email', e.currentTarget.value)}
|
|
53
|
-
placeholder="Email"
|
|
54
|
-
/>
|
|
55
|
-
{:else if snap.stepId === 'review'}
|
|
56
|
-
<p>Name: {snap.data.name}</p>
|
|
57
|
-
<p>Email: {snap.data.email}</p>
|
|
58
|
-
{/if}
|
|
59
|
-
|
|
60
|
-
<button onclick={previous} disabled={snap.isFirstStep || snap.isNavigating}>
|
|
61
|
-
Previous
|
|
62
|
-
</button>
|
|
63
|
-
<button onclick={next} disabled={!snap.canMoveNext || snap.isNavigating}>
|
|
64
|
-
{snap.isLastStep ? 'Complete' : 'Next'}
|
|
65
|
-
</button>
|
|
66
|
-
{/if}
|
|
67
|
-
```
|
|
20
|
+
import { PathShell } from "@daltonr/pathwrite-svelte";
|
|
21
|
+
import "@daltonr/pathwrite-svelte/styles.css";
|
|
22
|
+
import { applicationPath } from "./application-path";
|
|
23
|
+
import DetailsStep from "./DetailsStep.svelte";
|
|
24
|
+
import CoverNoteStep from "./CoverNoteStep.svelte";
|
|
68
25
|
|
|
69
|
-
### Option 2: Use `<PathShell>` with snippets
|
|
70
|
-
|
|
71
|
-
```svelte
|
|
72
|
-
<script lang="ts">
|
|
73
|
-
import { PathShell } from '@daltonr/pathwrite-svelte';
|
|
74
|
-
import '@daltonr/pathwrite-svelte/styles.css';
|
|
75
|
-
|
|
76
|
-
import DetailsForm from './DetailsForm.svelte';
|
|
77
|
-
import ReviewPanel from './ReviewPanel.svelte';
|
|
78
|
-
|
|
79
|
-
const signupPath = {
|
|
80
|
-
id: 'signup',
|
|
81
|
-
steps: [
|
|
82
|
-
{ id: 'details', title: 'Your Details' },
|
|
83
|
-
{ id: 'review', title: 'Review' }
|
|
84
|
-
]
|
|
85
|
-
};
|
|
86
|
-
|
|
87
26
|
function handleComplete(data) {
|
|
88
|
-
console.log(
|
|
27
|
+
console.log("Submitted:", data);
|
|
89
28
|
}
|
|
90
29
|
</script>
|
|
91
30
|
|
|
92
31
|
<PathShell
|
|
93
|
-
path={
|
|
94
|
-
initialData={{ name:
|
|
32
|
+
path={applicationPath}
|
|
33
|
+
initialData={{ name: "", email: "", coverNote: "" }}
|
|
95
34
|
oncomplete={handleComplete}
|
|
96
35
|
>
|
|
97
36
|
{#snippet details()}
|
|
98
|
-
<
|
|
99
|
-
{/snippet}
|
|
100
|
-
{#snippet review()}
|
|
101
|
-
<ReviewPanel />
|
|
37
|
+
<DetailsStep />
|
|
102
38
|
{/snippet}
|
|
103
|
-
</PathShell>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Each step is a **Svelte 5 snippet** whose name matches the step ID. PathShell collects them automatically and renders the active one.
|
|
107
|
-
|
|
108
|
-
> **⚠️ Important: Snippet Names Must Match Step IDs**
|
|
109
|
-
>
|
|
110
|
-
> When passing step content to `<PathShell>`, each snippet's name **must exactly match** the corresponding step's `id`:
|
|
111
|
-
>
|
|
112
|
-
> ```typescript
|
|
113
|
-
> const myPath = {
|
|
114
|
-
> id: 'signup',
|
|
115
|
-
> steps: [
|
|
116
|
-
> { id: 'details' }, // ← Step ID
|
|
117
|
-
> { id: 'review' } // ← Step ID
|
|
118
|
-
> ]
|
|
119
|
-
> };
|
|
120
|
-
> ```
|
|
121
|
-
>
|
|
122
|
-
> ```svelte
|
|
123
|
-
> <PathShell path={myPath}>
|
|
124
|
-
> {#snippet details()} <!-- ✅ Matches "details" step -->
|
|
125
|
-
> <DetailsForm />
|
|
126
|
-
> {/snippet}
|
|
127
|
-
> {#snippet review()} <!-- ✅ Matches "review" step -->
|
|
128
|
-
> <ReviewPanel />
|
|
129
|
-
> {/snippet}
|
|
130
|
-
> {#snippet foo()} <!-- ❌ No step with id "foo" -->
|
|
131
|
-
> <FooPanel />
|
|
132
|
-
> {/snippet}
|
|
133
|
-
> </PathShell>
|
|
134
|
-
> ```
|
|
135
|
-
>
|
|
136
|
-
> If a snippet name doesn't match any step ID, PathShell will render:
|
|
137
|
-
> **`No content for step "foo"`**
|
|
138
|
-
>
|
|
139
|
-
> **💡 Tip:** Use your IDE's "Go to Definition" on the step ID in your path definition, then copy-paste the exact string when creating the snippet. This ensures perfect matching and avoids typos.
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Simple vs Persisted
|
|
144
|
-
|
|
145
|
-
PathShell supports two modes. Pick the one that fits your use case:
|
|
146
|
-
|
|
147
|
-
### Simple — PathShell manages the engine
|
|
148
|
-
|
|
149
|
-
Pass `path` and `initialData`. PathShell creates and starts the engine for you:
|
|
150
39
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
initialData={{ name: '' }}
|
|
155
|
-
oncomplete={handleComplete}
|
|
156
|
-
>
|
|
157
|
-
{#snippet details()}
|
|
158
|
-
<DetailsForm />
|
|
40
|
+
<!-- Step ID is "cover-note"; PathShell resolves the camelCase snippet automatically -->
|
|
41
|
+
{#snippet coverNote()}
|
|
42
|
+
<CoverNoteStep />
|
|
159
43
|
{/snippet}
|
|
160
44
|
</PathShell>
|
|
161
45
|
```
|
|
162
46
|
|
|
163
|
-
**Use this when:** you don't need persistence, restoration, or custom observers. Quick prototypes, simple forms, one-off wizards.
|
|
164
|
-
|
|
165
|
-
### Persisted — you create the engine
|
|
166
|
-
|
|
167
|
-
Create the engine yourself with `restoreOrStart()` and pass it via `engine`. PathShell subscribes to it but does not start or own it:
|
|
168
|
-
|
|
169
|
-
```svelte
|
|
170
|
-
<script>
|
|
171
|
-
import { HttpStore, restoreOrStart, persistence } from '@daltonr/pathwrite-store';
|
|
172
|
-
|
|
173
|
-
let engine = $state(null);
|
|
174
|
-
|
|
175
|
-
onMount(async () => {
|
|
176
|
-
const result = await restoreOrStart({
|
|
177
|
-
store: new HttpStore({ baseUrl: '/api/wizard' }),
|
|
178
|
-
key: 'user:onboarding',
|
|
179
|
-
path: signupPath,
|
|
180
|
-
initialData: { name: '' },
|
|
181
|
-
observers: [
|
|
182
|
-
persistence({ store, key: 'user:onboarding', strategy: 'onNext' })
|
|
183
|
-
]
|
|
184
|
-
});
|
|
185
|
-
engine = result.engine;
|
|
186
|
-
});
|
|
187
|
-
</script>
|
|
188
|
-
|
|
189
|
-
{#if engine}
|
|
190
|
-
<PathShell {engine} oncomplete={handleComplete}>
|
|
191
|
-
{#snippet details()}
|
|
192
|
-
<DetailsForm />
|
|
193
|
-
{/snippet}
|
|
194
|
-
</PathShell>
|
|
195
|
-
{/if}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Use this when:** you need auto-persistence, restore-on-reload, or custom observers. Production apps, checkout flows, anything where losing progress matters.
|
|
199
|
-
|
|
200
|
-
> **Don't pass both.** `path` and `engine` are mutually exclusive. If you pass `engine`, PathShell will not call `start()` — it assumes the engine is already running.
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## ⚠️ Step Components Must Use `getPathContext()`
|
|
205
|
-
|
|
206
|
-
Step components rendered inside `<PathShell>` access the path engine via `getPathContext()` — **not** Svelte's raw `getContext()`.
|
|
207
|
-
|
|
208
47
|
```svelte
|
|
48
|
+
<!-- DetailsStep.svelte — step component uses usePathContext -->
|
|
209
49
|
<script lang="ts">
|
|
210
|
-
|
|
211
|
-
import { getPathContext } from '@daltonr/pathwrite-svelte';
|
|
212
|
-
const { snapshot, setData } = getPathContext();
|
|
50
|
+
import { usePathContext } from "@daltonr/pathwrite-svelte";
|
|
213
51
|
|
|
214
|
-
|
|
215
|
-
// import { getContext } from 'svelte';
|
|
216
|
-
// const { snapshot, setData } = getContext('pathContext');
|
|
52
|
+
const ctx = usePathContext();
|
|
217
53
|
</script>
|
|
218
54
|
|
|
219
|
-
{#if
|
|
55
|
+
{#if ctx.snapshot}
|
|
220
56
|
<input
|
|
221
|
-
value={
|
|
222
|
-
oninput={(e) => setData(
|
|
57
|
+
value={ctx.snapshot.data.name ?? ""}
|
|
58
|
+
oninput={(e) => ctx.setData("name", e.currentTarget.value)}
|
|
59
|
+
placeholder="Name"
|
|
223
60
|
/>
|
|
61
|
+
<button onclick={ctx.next}>Next</button>
|
|
224
62
|
{/if}
|
|
225
63
|
```
|
|
226
64
|
|
|
227
|
-
|
|
65
|
+
## usePath
|
|
228
66
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
## API
|
|
67
|
+
`usePath<TData>(options?)` creates an isolated path engine instance with runes-based reactive state. The engine is unsubscribed automatically when the component is destroyed.
|
|
232
68
|
|
|
233
|
-
|
|
69
|
+
> Do not destructure `snapshot` — it is a reactive getter backed by `$state`. Destructuring captures the value once and loses reactivity. Access it as `path.snapshot` throughout the template.
|
|
234
70
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
```
|
|
71
|
+
| Return value | Type | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `snapshot` | `PathSnapshot \| null` | Reactive getter. `null` when no path is active. |
|
|
74
|
+
| `start(definition, data?)` | `Promise<void>` | Start or restart a path. |
|
|
75
|
+
| `restart(definition, data?)` | `Promise<void>` | Tear down any active path and start fresh. |
|
|
76
|
+
| `next()` | `Promise<void>` | Advance one step. Completes on the last step. |
|
|
77
|
+
| `previous()` | `Promise<void>` | Go back one step. No-op on the first step of a top-level path. |
|
|
78
|
+
| `cancel()` | `Promise<void>` | Cancel the active path (or sub-path). |
|
|
79
|
+
| `goToStep(stepId)` | `Promise<void>` | Jump to a step by ID. Calls `onLeave`/`onEnter`; bypasses guards. |
|
|
80
|
+
| `goToStepChecked(stepId)` | `Promise<void>` | Jump to a step by ID, checking the current step's guard first. |
|
|
81
|
+
| `setData(key, value)` | `Promise<void>` | Update a single data field. Type-safe when `TData` is specified. |
|
|
82
|
+
| `startSubPath(definition, data?, meta?)` | `Promise<void>` | Push a sub-path. `meta` is returned to `onSubPathComplete`/`onSubPathCancel`. |
|
|
248
83
|
|
|
249
|
-
|
|
84
|
+
**Options:**
|
|
250
85
|
|
|
251
86
|
| Option | Type | Description |
|
|
252
|
-
|
|
253
|
-
| `engine` | `PathEngine` |
|
|
254
|
-
| `onEvent` | `(event) => void` | Called for every engine event |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `engine` | `PathEngine` | Externally-managed engine (e.g. from `restoreOrStart()`). `usePath` subscribes to it; the caller owns the lifecycle. |
|
|
89
|
+
| `onEvent` | `(event: PathEvent) => void` | Called for every engine event. |
|
|
255
90
|
|
|
256
|
-
|
|
91
|
+
## PathShell props
|
|
257
92
|
|
|
258
|
-
|
|
259
|
-
|----------|------|-------------|
|
|
260
|
-
| `snapshot` | `Readable<PathSnapshot \| null>` | Current path state (reactive store) |
|
|
261
|
-
| `start` | `function` | Start a path |
|
|
262
|
-
| `startSubPath` | `function` | Launch a sub-path |
|
|
263
|
-
| `next` | `function` | Go to next step |
|
|
264
|
-
| `previous` | `function` | Go to previous step |
|
|
265
|
-
| `cancel` | `function` | Cancel the path |
|
|
266
|
-
| `goToStep` | `function` | Jump to step by ID |
|
|
267
|
-
| `goToStepChecked` | `function` | Jump with guard checks |
|
|
268
|
-
| `setData` | `function` | Update data |
|
|
269
|
-
| `restart` | `function` | Restart the path |
|
|
270
|
-
|
|
271
|
-
### `<PathShell>`
|
|
272
|
-
|
|
273
|
-
Default UI shell with progress indicator and navigation buttons.
|
|
274
|
-
|
|
275
|
-
#### Props
|
|
93
|
+
Step content is supplied as Svelte 5 snippets whose names match each step's `id`. For hyphenated step IDs (e.g. `"cover-letter"`), pass the snippet as the camelCase prop (`coverLetter={...}`) — PathShell resolves it automatically. A `console.warn` fires in development if no snippet is found under either the exact ID or the camelCase form.
|
|
276
94
|
|
|
277
95
|
| Prop | Type | Default | Description |
|
|
278
|
-
|
|
279
|
-
| `path` | `PathDefinition` | — | Path
|
|
280
|
-
| `engine` | `PathEngine` | — |
|
|
281
|
-
| `initialData` | `PathData` | `{}` | Initial data |
|
|
282
|
-
| `autoStart` | `boolean` | `true` |
|
|
283
|
-
| `
|
|
284
|
-
| `
|
|
285
|
-
| `
|
|
286
|
-
| `
|
|
287
|
-
| `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
| Callback | Type | Description |
|
|
296
|
-
|----------|------|-------------|
|
|
297
|
-
| `oncomplete` | `(data) => void` | Called when path completes |
|
|
298
|
-
| `oncancel` | `(data) => void` | Called when path is cancelled |
|
|
299
|
-
| `onevent` | `(event) => void` | Called for every event |
|
|
300
|
-
|
|
301
|
-
#### Snippets
|
|
302
|
-
|
|
303
|
-
Step content is provided as Svelte 5 snippets. The snippet name must match the step ID:
|
|
304
|
-
|
|
305
|
-
```svelte
|
|
306
|
-
<PathShell path={myPath}>
|
|
307
|
-
{#snippet details()}
|
|
308
|
-
<DetailsStep />
|
|
309
|
-
{/snippet}
|
|
310
|
-
{#snippet review()}
|
|
311
|
-
<ReviewStep />
|
|
312
|
-
{/snippet}
|
|
313
|
-
</PathShell>
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
You can also override the header and footer:
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `path` | `PathDefinition` | — | Path to run. Mutually exclusive with `engine`. |
|
|
98
|
+
| `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. |
|
|
99
|
+
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
|
|
100
|
+
| `autoStart` | `boolean` | `true` | Start on mount. Ignored when `engine` is provided. |
|
|
101
|
+
| `footerLayout` | `"wizard" \| "form" \| "auto"` | `"auto"` | `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back. `"auto"` picks `"form"` for single-step paths. |
|
|
102
|
+
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. Also hidden automatically for single-step paths. |
|
|
103
|
+
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
104
|
+
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
105
|
+
| `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
|
|
106
|
+
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
107
|
+
| `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
|
|
108
|
+
| `oncomplete` | `(data: PathData) => void` | — | Called when the path finishes naturally. |
|
|
109
|
+
| `oncancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
|
|
110
|
+
| `onevent` | `(event: PathEvent) => void` | — | Called for every engine event. |
|
|
111
|
+
|
|
112
|
+
You can also replace the built-in header and footer with custom snippets:
|
|
317
113
|
|
|
318
114
|
```svelte
|
|
319
115
|
<PathShell path={myPath}>
|
|
320
116
|
{#snippet header(snap)}
|
|
321
|
-
<
|
|
117
|
+
<p>Step {snap.stepIndex + 1} of {snap.stepCount}</p>
|
|
322
118
|
{/snippet}
|
|
323
119
|
|
|
324
|
-
{#snippet details()}
|
|
325
|
-
<DetailsStep />
|
|
326
|
-
{/snippet}
|
|
120
|
+
{#snippet details()}<DetailsStep />{/snippet}
|
|
327
121
|
|
|
328
122
|
{#snippet footer(snap, actions)}
|
|
329
|
-
<button onclick={actions.previous} disabled={snap.isFirstStep}>
|
|
330
|
-
← Back
|
|
331
|
-
</button>
|
|
123
|
+
<button onclick={actions.previous} disabled={snap.isFirstStep}>Back</button>
|
|
332
124
|
<button onclick={actions.next} disabled={!snap.canMoveNext}>
|
|
333
|
-
{snap.isLastStep ?
|
|
125
|
+
{snap.isLastStep ? "Submit" : "Continue"}
|
|
334
126
|
</button>
|
|
335
127
|
{/snippet}
|
|
336
128
|
</PathShell>
|
|
337
129
|
```
|
|
338
130
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
There are two ways to reset `<PathShell>` to step 1.
|
|
342
|
-
|
|
343
|
-
**Option 1 — Toggle mount** (simplest, always correct)
|
|
344
|
-
|
|
345
|
-
Toggle a `$state` rune to destroy and recreate the shell:
|
|
346
|
-
|
|
347
|
-
```svelte
|
|
348
|
-
<script>
|
|
349
|
-
let isActive = $state(true);
|
|
350
|
-
</script>
|
|
351
|
-
|
|
352
|
-
{#if isActive}
|
|
353
|
-
<PathShell path={myPath} oncomplete={() => (isActive = false)}>
|
|
354
|
-
{#snippet details()}<DetailsStep />{/snippet}
|
|
355
|
-
</PathShell>
|
|
356
|
-
{:else}
|
|
357
|
-
<button onclick={() => (isActive = true)}>Try Again</button>
|
|
358
|
-
{/if}
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**Option 2 — Call `restart()` on the shell ref** (in-place, no unmount)
|
|
362
|
-
|
|
363
|
-
Use `bind:this` to get a reference to the shell instance, then call `restart()`:
|
|
364
|
-
|
|
365
|
-
```svelte
|
|
366
|
-
<script>
|
|
367
|
-
let shellRef;
|
|
368
|
-
</script>
|
|
369
|
-
|
|
370
|
-
<PathShell bind:this={shellRef} path={myPath} oncomplete={onDone}>
|
|
371
|
-
{#snippet details()}<DetailsStep />{/snippet}
|
|
372
|
-
</PathShell>
|
|
373
|
-
|
|
374
|
-
<button onclick={() => shellRef.restart()}>Try Again</button>
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
`restart()` resets the path engine to step 1 with the original `initialData` without unmounting the component. Use this when you need to keep the shell mounted — for example, to preserve scroll position or drive a CSS transition.
|
|
378
|
-
|
|
379
|
-
### `getPathContext<TData>()`
|
|
131
|
+
## usePathContext
|
|
380
132
|
|
|
381
|
-
|
|
133
|
+
`usePathContext<TData>()` is the preferred way for step components rendered inside `<PathShell>` to access the path engine. `<PathShell>` calls `setContext()` internally with a private `Symbol` key; `usePathContext()` calls the matching `getContext()` and returns the same interface as `usePath`. It throws a clear error if called outside a `<PathShell>` — do not use Svelte's raw `getContext()` directly, as the key is a private `Symbol` and will silently return `undefined`.
|
|
382
134
|
|
|
383
135
|
```svelte
|
|
384
136
|
<script lang="ts">
|
|
385
|
-
import {
|
|
386
|
-
|
|
387
|
-
const
|
|
137
|
+
import { usePathContext } from "@daltonr/pathwrite-svelte";
|
|
138
|
+
|
|
139
|
+
const ctx = usePathContext<ApplicationData>();
|
|
388
140
|
</script>
|
|
389
141
|
|
|
390
|
-
{#if
|
|
142
|
+
{#if ctx.snapshot}
|
|
391
143
|
<input
|
|
392
|
-
value={
|
|
393
|
-
oninput={(e) => setData(
|
|
144
|
+
value={ctx.snapshot.data.name ?? ""}
|
|
145
|
+
oninput={(e) => ctx.setData("name", e.currentTarget.value)}
|
|
394
146
|
/>
|
|
395
|
-
<button onclick={next}>Next</button>
|
|
396
147
|
{/if}
|
|
397
148
|
```
|
|
398
149
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
Helper to create a two-way binding store.
|
|
402
|
-
|
|
403
|
-
```svelte
|
|
404
|
-
<script lang="ts">
|
|
405
|
-
import { usePath, bindData } from '@daltonr/pathwrite-svelte';
|
|
406
|
-
|
|
407
|
-
const { snapshot, setData } = usePath();
|
|
408
|
-
const name = bindData(snapshot, setData, 'name');
|
|
409
|
-
</script>
|
|
410
|
-
|
|
411
|
-
<input bind:value={$name} />
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## PathSnapshot
|
|
415
|
-
|
|
416
|
-
The `snapshot` store contains the current path state:
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
interface PathSnapshot {
|
|
420
|
-
pathId: string;
|
|
421
|
-
stepId: string;
|
|
422
|
-
stepIndex: number;
|
|
423
|
-
stepCount: number;
|
|
424
|
-
data: PathData;
|
|
425
|
-
nestingLevel: number;
|
|
426
|
-
isFirstStep: boolean;
|
|
427
|
-
isLastStep: boolean;
|
|
428
|
-
canMoveNext: boolean;
|
|
429
|
-
canMovePrevious: boolean;
|
|
430
|
-
isNavigating: boolean;
|
|
431
|
-
progress: number; // 0-1
|
|
432
|
-
steps: Array<{ id: string; title?: string; status: 'completed' | 'current' | 'upcoming' }>;
|
|
433
|
-
validationMessages: string[];
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
Access it via the store: `$snapshot`
|
|
438
|
-
|
|
439
|
-
## Guards and Hooks
|
|
440
|
-
|
|
441
|
-
Define validation and lifecycle logic in your path definition:
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
const myPath = {
|
|
445
|
-
id: 'signup',
|
|
446
|
-
steps: [
|
|
447
|
-
{
|
|
448
|
-
id: 'details',
|
|
449
|
-
canMoveNext: (ctx) => ctx.data.name && ctx.data.email,
|
|
450
|
-
validationMessages: (ctx) => {
|
|
451
|
-
const errors = [];
|
|
452
|
-
if (!ctx.data.name) errors.push('Name is required');
|
|
453
|
-
if (!ctx.data.email) errors.push('Email is required');
|
|
454
|
-
return errors;
|
|
455
|
-
},
|
|
456
|
-
onEnter: (ctx) => {
|
|
457
|
-
console.log('Entered details step');
|
|
458
|
-
},
|
|
459
|
-
onLeave: (ctx) => {
|
|
460
|
-
console.log('Leaving details step');
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
{ id: 'review' }
|
|
464
|
-
]
|
|
465
|
-
};
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
## Persistence
|
|
469
|
-
|
|
470
|
-
Use with [@daltonr/pathwrite-store](../store) for automatic state persistence:
|
|
471
|
-
|
|
472
|
-
```svelte
|
|
473
|
-
<script lang="ts">
|
|
474
|
-
import { onMount } from 'svelte';
|
|
475
|
-
import { PathShell } from '@daltonr/pathwrite-svelte';
|
|
476
|
-
import { HttpStore, restoreOrStart, persistence } from '@daltonr/pathwrite-store';
|
|
477
|
-
import DetailsForm from './DetailsForm.svelte';
|
|
478
|
-
import ReviewPanel from './ReviewPanel.svelte';
|
|
479
|
-
|
|
480
|
-
const store = new HttpStore({ baseUrl: '/api/wizard' });
|
|
481
|
-
const key = 'user:123:signup';
|
|
482
|
-
|
|
483
|
-
let engine = $state(null);
|
|
484
|
-
let restored = $state(false);
|
|
485
|
-
|
|
486
|
-
onMount(async () => {
|
|
487
|
-
const result = await restoreOrStart({
|
|
488
|
-
store,
|
|
489
|
-
key,
|
|
490
|
-
path: signupPath,
|
|
491
|
-
initialData: { name: '', email: '' },
|
|
492
|
-
observers: [
|
|
493
|
-
persistence({ store, key, strategy: 'onNext' })
|
|
494
|
-
]
|
|
495
|
-
});
|
|
496
|
-
engine = result.engine;
|
|
497
|
-
restored = result.restored;
|
|
498
|
-
});
|
|
499
|
-
</script>
|
|
500
|
-
|
|
501
|
-
{#if engine}
|
|
502
|
-
<PathShell {engine} oncomplete={(data) => console.log('Done!', data)}>
|
|
503
|
-
{#snippet details()}
|
|
504
|
-
<DetailsForm />
|
|
505
|
-
{/snippet}
|
|
506
|
-
{#snippet review()}
|
|
507
|
-
<ReviewPanel />
|
|
508
|
-
{/snippet}
|
|
509
|
-
</PathShell>
|
|
510
|
-
{/if}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
## TypeScript
|
|
514
|
-
|
|
515
|
-
Type your path data for full type safety:
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
interface SignupData {
|
|
519
|
-
name: string;
|
|
520
|
-
email: string;
|
|
521
|
-
age: number;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const { snapshot, setData } = usePath<SignupData>();
|
|
525
|
-
|
|
526
|
-
// ✅ Type-checked
|
|
527
|
-
setData('name', 'John');
|
|
528
|
-
|
|
529
|
-
// ❌ Type error
|
|
530
|
-
setData('invalid', 'value');
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
## License
|
|
534
|
-
|
|
535
|
-
MIT — © 2026 Devjoy Ltd.
|
|
536
|
-
|
|
537
|
-
## See Also
|
|
538
|
-
|
|
539
|
-
- [@daltonr/pathwrite-core](../core) - Core engine
|
|
540
|
-
- [@daltonr/pathwrite-store](../store) - HTTP persistence
|
|
541
|
-
- [Documentation](../../docs/guides/DEVELOPER_GUIDE.md)
|
|
150
|
+
## Further reading
|
|
542
151
|
|
|
152
|
+
- [Svelte getting started guide](../../docs/getting-started/frameworks/svelte.md)
|
|
153
|
+
- [Navigation & guards](../../docs/guides/navigation.md)
|
|
154
|
+
- [Full documentation](../../docs/README.md)
|
|
543
155
|
|
|
544
156
|
---
|
|
545
157
|
|
|
546
|
-
© 2026 Devjoy Ltd.
|
|
547
|
-
|
|
158
|
+
MIT — © 2026 Devjoy Ltd.
|