@daltonr/pathwrite-svelte 0.5.0 → 0.6.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 CHANGED
@@ -105,6 +105,39 @@ npm install @daltonr/pathwrite-svelte
105
105
 
106
106
  Each step is a **Svelte 5 snippet** whose name matches the step ID. PathShell collects them automatically and renders the active one.
107
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
+
108
141
  ---
109
142
 
110
143
  ## Simple vs Persisted
@@ -252,7 +285,8 @@ Default UI shell with progress indicator and navigation buttons.
252
285
  | `completeLabel` | `string` | `"Complete"` | Complete button label |
253
286
  | `cancelLabel` | `string` | `"Cancel"` | Cancel button label |
254
287
  | `hideCancel` | `boolean` | `false` | Hide cancel button |
255
- | `hideProgress` | `boolean` | `false` | Hide progress indicator |
288
+ | `hideProgress` | `boolean` | `false` | Hide progress indicator. Also hidden automatically for single-step top-level paths. |
289
+ | `footerLayout` | `"wizard" \| "form" \| "auto"` | `"auto"` | Footer button layout. `"auto"` uses `"form"` for single-step top-level paths, `"wizard"` otherwise. `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back button. |
256
290
 
257
291
  > **`path` vs `engine`:** Pass `path` for simple wizards where PathShell manages the engine. Pass `engine` when you create the engine yourself (e.g., via `restoreOrStart()` for persistence). These are mutually exclusive — don't pass both.
258
292
 
@@ -302,6 +336,46 @@ You can also override the header and footer:
302
336
  </PathShell>
303
337
  ```
304
338
 
339
+ #### Resetting the path
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
+
305
379
  ### `getPathContext<TData>()`
306
380
 
307
381
  Get the path context from a parent `<PathShell>`. Use this inside step components.
@@ -4,6 +4,11 @@
4
4
  import type { PathDefinition, PathData, PathEngine, PathSnapshot } from './index.svelte.js';
5
5
  import type { Snippet } from 'svelte';
6
6
 
7
+ /** Converts a camelCase or lowercase field key to a display label. */
8
+ function formatFieldKey(key: string): string {
9
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()).trim();
10
+ }
11
+
7
12
  interface Props {
8
13
  path?: PathDefinition<any>;
9
14
  engine?: PathEngine;
@@ -15,6 +20,13 @@
15
20
  cancelLabel?: string;
16
21
  hideCancel?: boolean;
17
22
  hideProgress?: boolean;
23
+ /**
24
+ * Footer layout mode:
25
+ * - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
26
+ * - "wizard": Back button on left, Cancel and Submit together on right.
27
+ * - "form": Cancel on left, Submit alone on right. Back button never shown.
28
+ */
29
+ footerLayout?: "wizard" | "form" | "auto";
18
30
  // Callback props replace event dispatching in Svelte 5
19
31
  oncomplete?: (data: PathData) => void;
20
32
  oncancel?: (data: PathData) => void;
@@ -37,6 +49,7 @@
37
49
  cancelLabel = 'Cancel',
38
50
  hideCancel = false,
39
51
  hideProgress = false,
52
+ footerLayout = 'auto',
40
53
  oncomplete,
41
54
  oncancel,
42
55
  onevent,
@@ -80,6 +93,26 @@
80
93
 
81
94
  let snap = $derived(pathReturn.snapshot);
82
95
  let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restart(path, initialData) });
96
+
97
+ // Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
98
+ let resolvedFooterLayout = $derived(
99
+ footerLayout === 'auto' && snap
100
+ ? (snap.stepCount === 1 && snap.nestingLevel === 0 ? 'form' : 'wizard')
101
+ : (footerLayout === 'auto' ? 'wizard' : footerLayout)
102
+ );
103
+
104
+ /**
105
+ * Restart the active path from step 1 with the original `initialData`,
106
+ * without unmounting the shell. Use with `bind:this`:
107
+ *
108
+ * ```svelte
109
+ * <PathShell bind:this={shellRef} path={myPath} />
110
+ * <button onclick={() => shellRef.restart()}>Try Again</button>
111
+ * ```
112
+ */
113
+ export function restart(): Promise<void> {
114
+ return pathReturn.restart(path, initialData);
115
+ }
83
116
  </script>
84
117
 
85
118
  <div class="pw-shell">
@@ -97,7 +130,7 @@
97
130
  {#if !hideProgress}
98
131
  {#if header}
99
132
  {@render header(snap)}
100
- {:else}
133
+ {:else if snap.stepCount > 1 || snap.nestingLevel > 0}
101
134
  <div class="pw-shell__header">
102
135
  <div class="pw-shell__steps">
103
136
  {#each snap.steps as step, i}
@@ -125,11 +158,13 @@
125
158
  {/if}
126
159
  </div>
127
160
 
128
- <!-- Validation messages -->
129
- {#if snap.validationMessages.length > 0}
161
+ <!-- Validation messages — labeled by field name -->
162
+ {#if snap.hasAttemptedNext && Object.keys(snap.fieldMessages).length > 0}
130
163
  <ul class="pw-shell__validation">
131
- {#each snap.validationMessages as msg}
132
- <li class="pw-shell__validation-item">{msg}</li>
164
+ {#each Object.entries(snap.fieldMessages) as [key, msg]}
165
+ <li class="pw-shell__validation-item">
166
+ {#if key !== '_'}<span class="pw-shell__validation-label">{formatFieldKey(key)}</span>{/if}{msg}
167
+ </li>
133
168
  {/each}
134
169
  </ul>
135
170
  {/if}
@@ -140,7 +175,18 @@
140
175
  {:else}
141
176
  <div class="pw-shell__footer">
142
177
  <div class="pw-shell__footer-left">
143
- {#if !snap.isFirstStep}
178
+ {#if resolvedFooterLayout === 'form' && !hideCancel}
179
+ <!-- Form mode: Cancel on the left -->
180
+ <button
181
+ type="button"
182
+ class="pw-shell__btn pw-shell__btn--cancel"
183
+ disabled={snap.isNavigating}
184
+ onclick={cancel}
185
+ >
186
+ {cancelLabel}
187
+ </button>
188
+ {:else if resolvedFooterLayout === 'wizard' && !snap.isFirstStep}
189
+ <!-- Wizard mode: Back on the left -->
144
190
  <button
145
191
  type="button"
146
192
  class="pw-shell__btn pw-shell__btn--back"
@@ -152,7 +198,8 @@
152
198
  {/if}
153
199
  </div>
154
200
  <div class="pw-shell__footer-right">
155
- {#if !hideCancel}
201
+ {#if resolvedFooterLayout === 'wizard' && !hideCancel}
202
+ <!-- Wizard mode: Cancel on the right -->
156
203
  <button
157
204
  type="button"
158
205
  class="pw-shell__btn pw-shell__btn--cancel"
@@ -162,10 +209,11 @@
162
209
  {cancelLabel}
163
210
  </button>
164
211
  {/if}
212
+ <!-- Both modes: Submit on the right -->
165
213
  <button
166
214
  type="button"
167
215
  class="pw-shell__btn pw-shell__btn--next"
168
- disabled={snap.isNavigating || !snap.canMoveNext}
216
+ disabled={snap.isNavigating}
169
217
  onclick={next}
170
218
  >
171
219
  {snap.isLastStep ? completeLabel : nextLabel}
@@ -11,6 +11,13 @@ interface Props {
11
11
  cancelLabel?: string;
12
12
  hideCancel?: boolean;
13
13
  hideProgress?: boolean;
14
+ /**
15
+ * Footer layout mode:
16
+ * - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
17
+ * - "wizard": Back button on left, Cancel and Submit together on right.
18
+ * - "form": Cancel on left, Submit alone on right. Back button never shown.
19
+ */
20
+ footerLayout?: "wizard" | "form" | "auto";
14
21
  oncomplete?: (data: PathData) => void;
15
22
  oncancel?: (data: PathData) => void;
16
23
  onevent?: (event: any) => void;
@@ -18,7 +25,9 @@ interface Props {
18
25
  footer?: Snippet<[PathSnapshot<any>, object]>;
19
26
  [key: string]: Snippet | any;
20
27
  }
21
- declare const PathShell: import("svelte").Component<Props, {}, "">;
28
+ declare const PathShell: import("svelte").Component<Props, {
29
+ restart: () => Promise<void>;
30
+ }, "">;
22
31
  type PathShell = ReturnType<typeof PathShell>;
23
32
  export default PathShell;
24
33
  //# sourceMappingURL=PathShell.svelte.d.ts.map
@@ -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,MAAM,mBAAmB,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,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,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,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,OAAO,GAAG,GAAG,CAAC;CAC9B;AAmJH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
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,MAAM,mBAAmB,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,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,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;IAE1C,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,OAAO,GAAG,GAAG,CAAC;CAC9B;AAuLH,QAAA,MAAM,SAAS;mBAlGQ,OAAO,CAAC,IAAI,CAAC;MAkGmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
package/dist/index.css CHANGED
@@ -204,6 +204,15 @@
204
204
  left: 4px;
205
205
  }
206
206
 
207
+ .pw-shell__validation-label {
208
+ font-weight: 600;
209
+ margin-right: 3px;
210
+ }
211
+
212
+ .pw-shell__validation-label::after {
213
+ content: ":";
214
+ }
215
+
207
216
  /* ------------------------------------------------------------------ */
208
217
  /* Footer — navigation buttons */
209
218
  /* ------------------------------------------------------------------ */
@@ -1,5 +1,5 @@
1
1
  import type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
2
- export type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
2
+ export type { PathData, FieldErrors, PathDefinition, PathEngine, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
3
3
  export interface UsePathOptions {
4
4
  /**
5
5
  * An externally-managed `PathEngine` to subscribe to — for example, the engine
@@ -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,YAAY,EACV,QAAQ,EACR,cAAc,EACd,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,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,sFAAsF;IACtF,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,iGAAiG;IACjG,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,oLAAoL;IACpL,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,gKAAgK;IAChK,OAAO,EAAE,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,OAAO,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACvD,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,KAAK,CAAC,CAuDtB;AAQD,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC5D,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC9C,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO,EAAE,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,KAAK,WAAW,CAAC,KAAK,CAAC,CAStF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAE/F;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,QAAQ,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,KAAK,EAC7E,WAAW,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,EAC7C,OAAO,EAAE,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EACzF,GAAG,EAAE,CAAC,GACL;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAAE,CAS9D;AAGD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daltonr/pathwrite-svelte",
3
- "version": "0.5.0",
3
+ "version": "0.6.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.",
@@ -31,7 +31,8 @@
31
31
  "svelte": "./dist/PathShell.svelte",
32
32
  "import": "./dist/PathShell.svelte"
33
33
  },
34
- "./styles.css": "./dist/index.css"
34
+ "./styles.css": "./dist/index.css",
35
+ "./dist/index.css": "./dist/index.css"
35
36
  },
36
37
  "svelte": "./dist/index.svelte.js",
37
38
  "main": "dist/index.svelte.js",
@@ -51,7 +52,7 @@
51
52
  "svelte": ">=5.0.0"
52
53
  },
53
54
  "dependencies": {
54
- "@daltonr/pathwrite-core": "^0.5.0"
55
+ "@daltonr/pathwrite-core": "^0.6.1"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@sveltejs/package": "^2.5.7",
@@ -4,6 +4,11 @@
4
4
  import type { PathDefinition, PathData, PathEngine, PathSnapshot } from './index.svelte.js';
5
5
  import type { Snippet } from 'svelte';
6
6
 
7
+ /** Converts a camelCase or lowercase field key to a display label. */
8
+ function formatFieldKey(key: string): string {
9
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()).trim();
10
+ }
11
+
7
12
  interface Props {
8
13
  path?: PathDefinition<any>;
9
14
  engine?: PathEngine;
@@ -15,6 +20,13 @@
15
20
  cancelLabel?: string;
16
21
  hideCancel?: boolean;
17
22
  hideProgress?: boolean;
23
+ /**
24
+ * Footer layout mode:
25
+ * - "auto" (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
26
+ * - "wizard": Back button on left, Cancel and Submit together on right.
27
+ * - "form": Cancel on left, Submit alone on right. Back button never shown.
28
+ */
29
+ footerLayout?: "wizard" | "form" | "auto";
18
30
  // Callback props replace event dispatching in Svelte 5
19
31
  oncomplete?: (data: PathData) => void;
20
32
  oncancel?: (data: PathData) => void;
@@ -37,6 +49,7 @@
37
49
  cancelLabel = 'Cancel',
38
50
  hideCancel = false,
39
51
  hideProgress = false,
52
+ footerLayout = 'auto',
40
53
  oncomplete,
41
54
  oncancel,
42
55
  onevent,
@@ -80,6 +93,26 @@
80
93
 
81
94
  let snap = $derived(pathReturn.snapshot);
82
95
  let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restart(path, initialData) });
96
+
97
+ // Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
98
+ let resolvedFooterLayout = $derived(
99
+ footerLayout === 'auto' && snap
100
+ ? (snap.stepCount === 1 && snap.nestingLevel === 0 ? 'form' : 'wizard')
101
+ : (footerLayout === 'auto' ? 'wizard' : footerLayout)
102
+ );
103
+
104
+ /**
105
+ * Restart the active path from step 1 with the original `initialData`,
106
+ * without unmounting the shell. Use with `bind:this`:
107
+ *
108
+ * ```svelte
109
+ * <PathShell bind:this={shellRef} path={myPath} />
110
+ * <button onclick={() => shellRef.restart()}>Try Again</button>
111
+ * ```
112
+ */
113
+ export function restart(): Promise<void> {
114
+ return pathReturn.restart(path, initialData);
115
+ }
83
116
  </script>
84
117
 
85
118
  <div class="pw-shell">
@@ -97,7 +130,7 @@
97
130
  {#if !hideProgress}
98
131
  {#if header}
99
132
  {@render header(snap)}
100
- {:else}
133
+ {:else if snap.stepCount > 1 || snap.nestingLevel > 0}
101
134
  <div class="pw-shell__header">
102
135
  <div class="pw-shell__steps">
103
136
  {#each snap.steps as step, i}
@@ -125,11 +158,13 @@
125
158
  {/if}
126
159
  </div>
127
160
 
128
- <!-- Validation messages -->
129
- {#if snap.validationMessages.length > 0}
161
+ <!-- Validation messages — labeled by field name -->
162
+ {#if snap.hasAttemptedNext && Object.keys(snap.fieldMessages).length > 0}
130
163
  <ul class="pw-shell__validation">
131
- {#each snap.validationMessages as msg}
132
- <li class="pw-shell__validation-item">{msg}</li>
164
+ {#each Object.entries(snap.fieldMessages) as [key, msg]}
165
+ <li class="pw-shell__validation-item">
166
+ {#if key !== '_'}<span class="pw-shell__validation-label">{formatFieldKey(key)}</span>{/if}{msg}
167
+ </li>
133
168
  {/each}
134
169
  </ul>
135
170
  {/if}
@@ -140,7 +175,18 @@
140
175
  {:else}
141
176
  <div class="pw-shell__footer">
142
177
  <div class="pw-shell__footer-left">
143
- {#if !snap.isFirstStep}
178
+ {#if resolvedFooterLayout === 'form' && !hideCancel}
179
+ <!-- Form mode: Cancel on the left -->
180
+ <button
181
+ type="button"
182
+ class="pw-shell__btn pw-shell__btn--cancel"
183
+ disabled={snap.isNavigating}
184
+ onclick={cancel}
185
+ >
186
+ {cancelLabel}
187
+ </button>
188
+ {:else if resolvedFooterLayout === 'wizard' && !snap.isFirstStep}
189
+ <!-- Wizard mode: Back on the left -->
144
190
  <button
145
191
  type="button"
146
192
  class="pw-shell__btn pw-shell__btn--back"
@@ -152,7 +198,8 @@
152
198
  {/if}
153
199
  </div>
154
200
  <div class="pw-shell__footer-right">
155
- {#if !hideCancel}
201
+ {#if resolvedFooterLayout === 'wizard' && !hideCancel}
202
+ <!-- Wizard mode: Cancel on the right -->
156
203
  <button
157
204
  type="button"
158
205
  class="pw-shell__btn pw-shell__btn--cancel"
@@ -162,10 +209,11 @@
162
209
  {cancelLabel}
163
210
  </button>
164
211
  {/if}
212
+ <!-- Both modes: Submit on the right -->
165
213
  <button
166
214
  type="button"
167
215
  class="pw-shell__btn pw-shell__btn--next"
168
- disabled={snap.isNavigating || !snap.canMoveNext}
216
+ disabled={snap.isNavigating}
169
217
  onclick={next}
170
218
  >
171
219
  {snap.isLastStep ? completeLabel : nextLabel}
@@ -11,6 +11,7 @@ import { PathEngine as PathEngineClass } from "@daltonr/pathwrite-core";
11
11
  // Re-export core types for convenience
12
12
  export type {
13
13
  PathData,
14
+ FieldErrors,
14
15
  PathDefinition,
15
16
  PathEngine,
16
17
  PathEvent,