@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.
@@ -1,13 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
- import { usePath, setPathContext } 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
 
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
8
  interface Props {
13
9
  path?: PathDefinition<any>;
@@ -17,6 +13,7 @@
17
13
  backLabel?: string;
18
14
  nextLabel?: string;
19
15
  completeLabel?: string;
16
+ loadingLabel?: string;
20
17
  cancelLabel?: string;
21
18
  hideCancel?: boolean;
22
19
  hideProgress?: boolean;
@@ -42,6 +39,11 @@
42
39
  * - "activeOnly": Only the active (sub-path) bar — root bar hidden.
43
40
  */
44
41
  progressLayout?: ProgressLayout;
42
+ /**
43
+ * Services object passed through context to all step components.
44
+ * Step components access it via `usePathContext<TData, TServices>()`.
45
+ */
46
+ services?: unknown;
45
47
  // Callback props replace event dispatching in Svelte 5
46
48
  oncomplete?: (data: PathData) => void;
47
49
  oncancel?: (data: PathData) => void;
@@ -61,12 +63,14 @@
61
63
  backLabel = 'Previous',
62
64
  nextLabel = 'Next',
63
65
  completeLabel = 'Complete',
66
+ loadingLabel = undefined,
64
67
  cancelLabel = 'Cancel',
65
68
  hideCancel = false,
66
69
  hideProgress = false,
67
70
  footerLayout = 'auto',
68
71
  validationDisplay = 'summary',
69
72
  progressLayout = 'merged',
73
+ services = null,
70
74
  oncomplete,
71
75
  oncancel,
72
76
  onevent,
@@ -77,7 +81,7 @@
77
81
 
78
82
  // Initialize path engine
79
83
  const pathReturn = usePath({
80
- engine: engineProp,
84
+ get engine() { return engineProp; },
81
85
  onEvent: (event) => {
82
86
  onevent?.(event);
83
87
  if (event.type === 'completed') oncomplete?.(event.data);
@@ -85,7 +89,7 @@
85
89
  }
86
90
  });
87
91
 
88
- const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn } = pathReturn;
92
+ const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn, retry, suspend } = pathReturn;
89
93
 
90
94
  // Provide context for child step components
91
95
  setPathContext({
@@ -96,7 +100,10 @@
96
100
  goToStep,
97
101
  goToStepChecked,
98
102
  setData,
99
- restart: () => restartFn(path, initialData)
103
+ restart: () => restartFn(path, initialData),
104
+ retry,
105
+ suspend,
106
+ get services() { return services; },
100
107
  });
101
108
 
102
109
  // Auto-start the path when no external engine is provided
@@ -108,8 +115,16 @@
108
115
  }
109
116
  });
110
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
+
111
126
  let snap = $derived(pathReturn.snapshot);
112
- let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData) });
127
+ let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
113
128
 
114
129
  // Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
115
130
  let resolvedFooterLayout = $derived(
@@ -187,13 +202,22 @@
187
202
 
188
203
  <!-- Body: current step rendered via named snippet.
189
204
  Prefer formId (inner step id of a StepChoice) so consumers can
190
- 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. -->
191
209
  <div class="pw-shell__body">
192
210
  {#if snap.formId && stepSnippets[snap.formId]}
193
- <svelte:component this={stepSnippets[snap.formId]} />
211
+ {@const StepComponent = stepSnippets[snap.formId]}
212
+ <StepComponent />
194
213
  {:else if stepSnippets[snap.stepId]}
195
- <svelte:component this={stepSnippets[snap.stepId]} />
214
+ {@const StepComponent = stepSnippets[snap.stepId]}
215
+ <StepComponent />
216
+ {:else if stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
217
+ {@const StepComponent = stepSnippets[stepIdToCamelCase(snap.formId ?? snap.stepId)]}
218
+ <StepComponent />
196
219
  {:else}
220
+ {warnMissingStep(snap.stepId)}
197
221
  <p>No content for step "{snap.stepId}"</p>
198
222
  {/if}
199
223
  </div>
@@ -220,8 +244,36 @@
220
244
  </ul>
221
245
  {/if}
222
246
 
247
+ <!-- Blocking error — guard returned { allowed: false, reason } -->
248
+ {#if validationDisplay !== 'inline' && snap.hasAttemptedNext && snap.blockingError}
249
+ <p class="pw-shell__blocking-error">{snap.blockingError}</p>
250
+ {/if}
251
+
252
+ <!-- Error panel: replaces footer when an async operation has failed -->
253
+ {#if snap.status === "error" && snap.error}
254
+ {@const err = snap.error}
255
+ {@const escalated = err.retryCount >= 2}
256
+ <div class="pw-shell__error">
257
+ <div class="pw-shell__error-title">{escalated ? "Still having trouble." : "Something went wrong."}</div>
258
+ <div class="pw-shell__error-message">{errorPhaseMessage(err.phase)}{err.message ? ` ${err.message}` : ""}</div>
259
+ <div class="pw-shell__error-actions">
260
+ {#if !escalated}
261
+ <button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
262
+ {/if}
263
+ {#if snap.hasPersistence}
264
+ <button
265
+ type="button"
266
+ class="pw-shell__btn {escalated ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend'}"
267
+ onclick={suspend}
268
+ >Save and come back later</button>
269
+ {/if}
270
+ {#if escalated && !snap.hasPersistence}
271
+ <button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
272
+ {/if}
273
+ </div>
274
+ </div>
223
275
  <!-- Footer: navigation buttons (overridable via footer snippet) -->
224
- {#if footer}
276
+ {:else if footer}
225
277
  {@render footer(snap, actions)}
226
278
  {:else}
227
279
  <div class="pw-shell__footer">
@@ -231,7 +283,7 @@
231
283
  <button
232
284
  type="button"
233
285
  class="pw-shell__btn pw-shell__btn--cancel"
234
- disabled={snap.isNavigating}
286
+ disabled={snap.status !== "idle"}
235
287
  onclick={cancel}
236
288
  >
237
289
  {cancelLabel}
@@ -241,7 +293,7 @@
241
293
  <button
242
294
  type="button"
243
295
  class="pw-shell__btn pw-shell__btn--back"
244
- disabled={snap.isNavigating || !snap.canMovePrevious}
296
+ disabled={snap.status !== "idle" || !snap.canMovePrevious}
245
297
  onclick={previous}
246
298
  >
247
299
  {backLabel}
@@ -254,7 +306,7 @@
254
306
  <button
255
307
  type="button"
256
308
  class="pw-shell__btn pw-shell__btn--cancel"
257
- disabled={snap.isNavigating}
309
+ disabled={snap.status !== "idle"}
258
310
  onclick={cancel}
259
311
  >
260
312
  {cancelLabel}
@@ -264,10 +316,11 @@
264
316
  <button
265
317
  type="button"
266
318
  class="pw-shell__btn pw-shell__btn--next"
267
- disabled={snap.isNavigating}
319
+ class:pw-shell__btn--loading={snap.status !== "idle"}
320
+ disabled={snap.status !== "idle"}
268
321
  onclick={next}
269
322
  >
270
- {snap.isLastStep ? completeLabel : nextLabel}
323
+ {snap.status !== 'idle' && loadingLabel ? loadingLabel : snap.isLastStep ? completeLabel : nextLabel}
271
324
  </button>
272
325
  </div>
273
326
  </div>
@@ -8,6 +8,7 @@ interface Props {
8
8
  backLabel?: string;
9
9
  nextLabel?: string;
10
10
  completeLabel?: string;
11
+ loadingLabel?: string;
11
12
  cancelLabel?: string;
12
13
  hideCancel?: boolean;
13
14
  hideProgress?: boolean;
@@ -33,6 +34,11 @@ interface Props {
33
34
  * - "activeOnly": Only the active (sub-path) bar — root bar hidden.
34
35
  */
35
36
  progressLayout?: ProgressLayout;
37
+ /**
38
+ * Services object passed through context to all step components.
39
+ * Step components access it via `usePathContext<TData, TServices>()`.
40
+ */
41
+ services?: unknown;
36
42
  oncomplete?: (data: PathData) => void;
37
43
  oncancel?: (data: PathData) => void;
38
44
  onevent?: (event: any) => void;
@@ -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;AAG/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,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;IAEhC,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;AAyNH,QAAA,MAAM,SAAS;mBAlIQ,QAAQ,IAAI,CAAC;MAkImB,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,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.css CHANGED
@@ -285,6 +285,16 @@
285
285
  content: ":";
286
286
  }
287
287
 
288
+ /* ------------------------------------------------------------------ */
289
+ /* Blocking error (guard-level message, not field-attached) */
290
+ /* ------------------------------------------------------------------ */
291
+ .pw-shell__blocking-error {
292
+ margin: 0;
293
+ padding: 8px 16px;
294
+ font-size: 13px;
295
+ color: var(--pw-color-error);
296
+ }
297
+
288
298
  /* ------------------------------------------------------------------ */
289
299
  /* Warning messages */
290
300
  /* ------------------------------------------------------------------ */
@@ -361,6 +371,7 @@
361
371
  background: var(--pw-color-primary);
362
372
  border-color: var(--pw-color-primary);
363
373
  color: #fff;
374
+ position: relative;
364
375
  }
365
376
 
366
377
  .pw-shell__btn--next:hover:not(:disabled) {
@@ -368,6 +379,29 @@
368
379
  border-color: #1d4ed8;
369
380
  }
370
381
 
382
+ @keyframes pw-spin {
383
+ to { transform: rotate(360deg); }
384
+ }
385
+
386
+ .pw-shell__btn--next.pw-shell__btn--loading {
387
+ color: transparent;
388
+ pointer-events: none;
389
+ }
390
+
391
+ .pw-shell__btn--next.pw-shell__btn--loading::after {
392
+ content: "";
393
+ position: absolute;
394
+ top: 50%;
395
+ left: 50%;
396
+ width: 14px;
397
+ height: 14px;
398
+ margin: -7px 0 0 -7px;
399
+ border: 2px solid rgba(255, 255, 255, 0.35);
400
+ border-top-color: #fff;
401
+ border-radius: 50%;
402
+ animation: pw-spin 0.6s linear infinite;
403
+ }
404
+
371
405
  .pw-shell__btn--back {
372
406
  background: transparent;
373
407
  border-color: var(--pw-color-primary);
@@ -388,3 +422,55 @@
388
422
  background: var(--pw-color-primary-light);
389
423
  }
390
424
 
425
+ /* ------------------------------------------------------------------ */
426
+ /* Error panel — replaces footer when status === "error" */
427
+ /* ------------------------------------------------------------------ */
428
+ .pw-shell__error {
429
+ background: var(--pw-color-error-bg);
430
+ border: 1px solid var(--pw-color-error-border);
431
+ border-radius: var(--pw-shell-radius);
432
+ padding: 16px 20px;
433
+ display: flex;
434
+ flex-direction: column;
435
+ gap: 10px;
436
+ }
437
+
438
+ .pw-shell__error-title {
439
+ font-size: 14px;
440
+ font-weight: 600;
441
+ color: var(--pw-color-error);
442
+ }
443
+
444
+ .pw-shell__error-message {
445
+ font-size: 13px;
446
+ color: var(--pw-color-muted);
447
+ }
448
+
449
+ .pw-shell__error-actions {
450
+ display: flex;
451
+ gap: 8px;
452
+ align-items: center;
453
+ margin-top: 2px;
454
+ }
455
+
456
+ .pw-shell__btn--retry {
457
+ background: var(--pw-color-error);
458
+ border-color: var(--pw-color-error);
459
+ color: #fff;
460
+ }
461
+
462
+ .pw-shell__btn--retry:hover:not(:disabled) {
463
+ opacity: 0.9;
464
+ }
465
+
466
+ .pw-shell__btn--suspend {
467
+ color: var(--pw-color-muted);
468
+ border-color: transparent;
469
+ background: transparent;
470
+ font-size: 13px;
471
+ }
472
+
473
+ .pw-shell__btn--suspend:hover:not(:disabled) {
474
+ background: var(--pw-color-primary-light);
475
+ }
476
+
@@ -1,4 +1,5 @@
1
1
  import type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
2
+ export { formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
2
3
  export type { PathData, FieldErrors, PathDefinition, PathEngine, PathEvent, PathSnapshot, PathStep, PathStepContext, ProgressLayout, RootProgress, SerializedPathState } from "@daltonr/pathwrite-core";
3
4
  export interface UsePathOptions {
4
5
  /**
@@ -47,6 +48,10 @@ export interface UsePathReturn<TData extends PathData = PathData> {
47
48
  * Use for "Start over" / retry flows without remounting the component.
48
49
  */
49
50
  restart: () => Promise<void>;
51
+ /** Re-runs the operation that set `snapshot.error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. */
52
+ retry: () => Promise<void>;
53
+ /** Pauses the path with intent to return. Emits `suspended`. All state is preserved. */
54
+ suspend: () => Promise<void>;
50
55
  }
51
56
  /**
52
57
  * Create a Pathwrite engine with Svelte 5 runes-based reactivity.
@@ -103,7 +108,7 @@ export interface UsePathReturn<TData extends PathData = PathData> {
103
108
  * ```
104
109
  */
105
110
  export declare function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData>;
106
- export interface PathContext<TData extends PathData = PathData> {
111
+ export interface PathContext<TData extends PathData = PathData, TServices = unknown> {
107
112
  readonly snapshot: PathSnapshot<TData>;
108
113
  next: () => Promise<void>;
109
114
  previous: () => Promise<void>;
@@ -113,17 +118,29 @@ export interface PathContext<TData extends PathData = PathData> {
113
118
  setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
114
119
  resetStep: () => Promise<void>;
115
120
  restart: () => Promise<void>;
121
+ /** Re-run the operation that set `snapshot.error`. */
122
+ retry: () => Promise<void>;
123
+ /** Pause with intent to return, preserving all state. Emits `suspended`. */
124
+ suspend: () => Promise<void>;
125
+ /**
126
+ * Services object passed through context from `PathShell`.
127
+ * Typed as `TServices` when `usePathContext<TData, TServices>()` is used.
128
+ */
129
+ services: TServices;
116
130
  }
117
131
  /**
118
- * Get the PathContext from a parent PathShell component.
132
+ * Access the nearest `PathShell`'s path instance and optional services object.
119
133
  * Use this inside step components to access the path engine.
120
134
  *
135
+ * - `TData` narrows `ctx.snapshot?.data`
136
+ * - `TServices` types the `services` value — must match what was passed to `PathShell`
137
+ *
121
138
  * @example
122
139
  * ```svelte
123
140
  * <script lang="ts">
124
- * import { getPathContext } from '@daltonr/pathwrite-svelte';
141
+ * import { usePathContext } from '@daltonr/pathwrite-svelte';
125
142
  *
126
- * const ctx = getPathContext();
143
+ * const ctx = usePathContext();
127
144
  * </script>
128
145
  *
129
146
  * <input value={ctx.snapshot?.data.name}
@@ -131,12 +148,12 @@ export interface PathContext<TData extends PathData = PathData> {
131
148
  * <button onclick={ctx.next}>Next</button>
132
149
  * ```
133
150
  */
134
- export declare function getPathContext<TData extends PathData = PathData>(): PathContext<TData>;
151
+ export declare function usePathContext<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices>;
135
152
  /**
136
153
  * Internal: Set the PathContext for child components.
137
154
  * Used by PathShell component.
138
155
  */
139
- export declare function setPathContext<TData extends PathData = PathData>(ctx: PathContext<TData>): void;
156
+ export declare function setPathContext<TData extends PathData = PathData, TServices = unknown>(ctx: PathContext<TData, TServices>): void;
140
157
  /**
141
158
  * Create a two-way binding helper for form inputs.
142
159
  * Returns an object with a reactive `value` property.
@@ -161,5 +178,12 @@ export declare function bindData<TData extends PathData, K extends string & keyo
161
178
  readonly value: TData[K];
162
179
  set: (value: TData[K]) => void;
163
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;
164
188
  export { default as PathShell } from "./PathShell.svelte";
165
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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"}
@@ -1,5 +1,7 @@
1
1
  import { onDestroy, getContext, setContext } from "svelte";
2
2
  import { PathEngine as PathEngineClass } from "@daltonr/pathwrite-core";
3
+ // Re-export core utilities and types for convenience
4
+ export { formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
3
5
  // ---------------------------------------------------------------------------
4
6
  // usePath - Runes-based API for Svelte 5
5
7
  // ---------------------------------------------------------------------------
@@ -83,6 +85,8 @@ export function usePath(options) {
83
85
  const setData = ((key, value) => engine.setData(key, value));
84
86
  const resetStep = () => engine.resetStep();
85
87
  const restart = () => engine.restart();
88
+ const retry = () => engine.retry();
89
+ const suspend = () => engine.suspend();
86
90
  return {
87
91
  get snapshot() { return _snapshot; },
88
92
  start,
@@ -94,7 +98,9 @@ export function usePath(options) {
94
98
  goToStepChecked,
95
99
  setData,
96
100
  resetStep,
97
- restart
101
+ restart,
102
+ retry,
103
+ suspend
98
104
  };
99
105
  }
100
106
  // ---------------------------------------------------------------------------
@@ -102,15 +108,18 @@ export function usePath(options) {
102
108
  // ---------------------------------------------------------------------------
103
109
  const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
104
110
  /**
105
- * Get the PathContext from a parent PathShell component.
111
+ * Access the nearest `PathShell`'s path instance and optional services object.
106
112
  * Use this inside step components to access the path engine.
107
113
  *
114
+ * - `TData` narrows `ctx.snapshot?.data`
115
+ * - `TServices` types the `services` value — must match what was passed to `PathShell`
116
+ *
108
117
  * @example
109
118
  * ```svelte
110
119
  * <script lang="ts">
111
- * import { getPathContext } from '@daltonr/pathwrite-svelte';
120
+ * import { usePathContext } from '@daltonr/pathwrite-svelte';
112
121
  *
113
- * const ctx = getPathContext();
122
+ * const ctx = usePathContext();
114
123
  * </script>
115
124
  *
116
125
  * <input value={ctx.snapshot?.data.name}
@@ -118,10 +127,10 @@ const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
118
127
  * <button onclick={ctx.next}>Next</button>
119
128
  * ```
120
129
  */
121
- export function getPathContext() {
130
+ export function usePathContext() {
122
131
  const ctx = getContext(PATH_CONTEXT_KEY);
123
132
  if (!ctx) {
124
- throw new Error("getPathContext() must be called from a component inside a <PathShell>. " +
133
+ throw new Error("usePathContext() must be called from a component inside a <PathShell>. " +
125
134
  "Ensure the PathShell component is a parent in the component tree.");
126
135
  }
127
136
  return ctx;
@@ -166,5 +175,14 @@ export function bindData(getSnapshot, setData, key) {
166
175
  }
167
176
  };
168
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
+ }
169
187
  // Export PathShell component
170
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.9.0",
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.9.0"
55
+ "@daltonr/pathwrite-core": "^0.10.1"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@sveltejs/package": "^2.5.7",