@daltonr/pathwrite-svelte 0.10.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 +17 -2
- package/dist/PathShell.svelte.d.ts.map +1 -1
- package/dist/index.svelte.d.ts +7 -0
- package/dist/index.svelte.d.ts.map +1 -1
- package/dist/index.svelte.js +9 -0
- package/package.json +2 -2
- package/src/PathShell.svelte +17 -2
- package/src/index.svelte.ts +10 -0
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.
|
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 } from './index.svelte.js';
|
|
3
|
+
import { usePath, setPathContext, 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
|
|
|
@@ -115,6 +115,14 @@
|
|
|
115
115
|
}
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
function warnMissingStep(stepId: string): void {
|
|
119
|
+
const camel = stepIdToCamelCase(stepId);
|
|
120
|
+
const hint = camel !== stepId
|
|
121
|
+
? ` No snippet found for "${stepId}" or its camelCase form "${camel}". If your step ID contains hyphens, pass the snippet as a camelCase prop: ${camel}={YourComponent}.`
|
|
122
|
+
: ` No snippet found for "${stepId}".`;
|
|
123
|
+
console.warn(`[PathShell]${hint}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
let snap = $derived(pathReturn.snapshot);
|
|
119
127
|
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
120
128
|
|
|
@@ -194,7 +202,10 @@
|
|
|
194
202
|
|
|
195
203
|
<!-- Body: current step rendered via named snippet.
|
|
196
204
|
Prefer formId (inner step id of a StepChoice) so consumers can
|
|
197
|
-
register snippets by inner step ids directly.
|
|
205
|
+
register snippets by inner step ids directly.
|
|
206
|
+
Hyphenated step IDs (e.g. "cover-letter") are normalised to camelCase
|
|
207
|
+
("coverLetter") as a fallback, since Svelte props must be valid JS
|
|
208
|
+
identifiers. -->
|
|
198
209
|
<div class="pw-shell__body">
|
|
199
210
|
{#if snap.formId && stepSnippets[snap.formId]}
|
|
200
211
|
{@const StepComponent = stepSnippets[snap.formId]}
|
|
@@ -202,7 +213,11 @@
|
|
|
202
213
|
{:else if stepSnippets[snap.stepId]}
|
|
203
214
|
{@const StepComponent = stepSnippets[snap.stepId]}
|
|
204
215
|
<StepComponent />
|
|
216
|
+
{:else if stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
|
|
217
|
+
{@const StepComponent = stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
|
|
218
|
+
<StepComponent />
|
|
205
219
|
{:else}
|
|
220
|
+
{warnMissingStep(snap.stepId)}
|
|
206
221
|
<p>No content for step "{snap.stepId}"</p>
|
|
207
222
|
{/if}
|
|
208
223
|
</div>
|
|
@@ -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;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C;;;;;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;IAE9C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;
|
|
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;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C;;;;;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;IAE9C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;AA+PH,QAAA,MAAM,SAAS;mBAhKQ,QAAQ,IAAI,CAAC;MAgKmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
package/dist/index.svelte.d.ts
CHANGED
|
@@ -178,5 +178,12 @@ export declare function bindData<TData extends PathData, K extends string & keyo
|
|
|
178
178
|
readonly value: TData[K];
|
|
179
179
|
set: (value: TData[K]) => void;
|
|
180
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* Converts a hyphenated step ID to camelCase.
|
|
183
|
+
* Used internally by PathShell to resolve step snippets when a step ID contains
|
|
184
|
+
* hyphens (e.g. "cover-letter" → "coverLetter"), since Svelte prop names must
|
|
185
|
+
* be valid JavaScript identifiers.
|
|
186
|
+
*/
|
|
187
|
+
export declare function stepIdToCamelCase(id: string): string;
|
|
181
188
|
export { default as PathShell } from "./PathShell.svelte";
|
|
182
189
|
//# sourceMappingURL=index.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
|
|
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
|
@@ -175,5 +175,14 @@ export function bindData(getSnapshot, setData, key) {
|
|
|
175
175
|
}
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Converts a hyphenated step ID to camelCase.
|
|
180
|
+
* Used internally by PathShell to resolve step snippets when a step ID contains
|
|
181
|
+
* hyphens (e.g. "cover-letter" → "coverLetter"), since Svelte prop names must
|
|
182
|
+
* be valid JavaScript identifiers.
|
|
183
|
+
*/
|
|
184
|
+
export function stepIdToCamelCase(id) {
|
|
185
|
+
return id.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
186
|
+
}
|
|
178
187
|
// Export PathShell component
|
|
179
188
|
export { default as PathShell } from "./PathShell.svelte";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-svelte",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
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.10.
|
|
55
|
+
"@daltonr/pathwrite-core": "^0.10.1"
|
|
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 } from './index.svelte.js';
|
|
3
|
+
import { usePath, setPathContext, 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
|
|
|
@@ -115,6 +115,14 @@
|
|
|
115
115
|
}
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
function warnMissingStep(stepId: string): void {
|
|
119
|
+
const camel = stepIdToCamelCase(stepId);
|
|
120
|
+
const hint = camel !== stepId
|
|
121
|
+
? ` No snippet found for "${stepId}" or its camelCase form "${camel}". If your step ID contains hyphens, pass the snippet as a camelCase prop: ${camel}={YourComponent}.`
|
|
122
|
+
: ` No snippet found for "${stepId}".`;
|
|
123
|
+
console.warn(`[PathShell]${hint}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
let snap = $derived(pathReturn.snapshot);
|
|
119
127
|
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
120
128
|
|
|
@@ -194,7 +202,10 @@
|
|
|
194
202
|
|
|
195
203
|
<!-- Body: current step rendered via named snippet.
|
|
196
204
|
Prefer formId (inner step id of a StepChoice) so consumers can
|
|
197
|
-
register snippets by inner step ids directly.
|
|
205
|
+
register snippets by inner step ids directly.
|
|
206
|
+
Hyphenated step IDs (e.g. "cover-letter") are normalised to camelCase
|
|
207
|
+
("coverLetter") as a fallback, since Svelte props must be valid JS
|
|
208
|
+
identifiers. -->
|
|
198
209
|
<div class="pw-shell__body">
|
|
199
210
|
{#if snap.formId && stepSnippets[snap.formId]}
|
|
200
211
|
{@const StepComponent = stepSnippets[snap.formId]}
|
|
@@ -202,7 +213,11 @@
|
|
|
202
213
|
{:else if stepSnippets[snap.stepId]}
|
|
203
214
|
{@const StepComponent = stepSnippets[snap.stepId]}
|
|
204
215
|
<StepComponent />
|
|
216
|
+
{:else if stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
|
|
217
|
+
{@const StepComponent = stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
|
|
218
|
+
<StepComponent />
|
|
205
219
|
{:else}
|
|
220
|
+
{warnMissingStep(snap.stepId)}
|
|
206
221
|
<p>No content for step "{snap.stepId}"</p>
|
|
207
222
|
{/if}
|
|
208
223
|
</div>
|
package/src/index.svelte.ts
CHANGED
|
@@ -310,6 +310,16 @@ export function bindData<TData extends PathData, K extends string & keyof TData>
|
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Converts a hyphenated step ID to camelCase.
|
|
315
|
+
* Used internally by PathShell to resolve step snippets when a step ID contains
|
|
316
|
+
* hyphens (e.g. "cover-letter" → "coverLetter"), since Svelte prop names must
|
|
317
|
+
* be valid JavaScript identifiers.
|
|
318
|
+
*/
|
|
319
|
+
export function stepIdToCamelCase(id: string): string {
|
|
320
|
+
return id.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
321
|
+
}
|
|
322
|
+
|
|
313
323
|
// Export PathShell component
|
|
314
324
|
export { default as PathShell } from "./PathShell.svelte";
|
|
315
325
|
|