@daltonr/pathwrite-svelte 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PathShell.svelte +55 -17
- 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 +23 -6
- package/dist/index.svelte.d.ts.map +1 -1
- package/dist/index.svelte.js +15 -6
- package/package.json +2 -2
- package/src/PathShell.svelte +55 -17
- package/src/index.svelte.ts +31 -10
package/dist/PathShell.svelte
CHANGED
|
@@ -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 } 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
|
|
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
|
|
@@ -109,7 +116,7 @@
|
|
|
109
116
|
});
|
|
110
117
|
|
|
111
118
|
let snap = $derived(pathReturn.snapshot);
|
|
112
|
-
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData) });
|
|
119
|
+
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
113
120
|
|
|
114
121
|
// Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
115
122
|
let resolvedFooterLayout = $derived(
|
|
@@ -190,9 +197,11 @@
|
|
|
190
197
|
register snippets by inner step ids directly. -->
|
|
191
198
|
<div class="pw-shell__body">
|
|
192
199
|
{#if snap.formId && stepSnippets[snap.formId]}
|
|
193
|
-
|
|
200
|
+
{@const StepComponent = stepSnippets[snap.formId]}
|
|
201
|
+
<StepComponent />
|
|
194
202
|
{:else if stepSnippets[snap.stepId]}
|
|
195
|
-
|
|
203
|
+
{@const StepComponent = stepSnippets[snap.stepId]}
|
|
204
|
+
<StepComponent />
|
|
196
205
|
{:else}
|
|
197
206
|
<p>No content for step "{snap.stepId}"</p>
|
|
198
207
|
{/if}
|
|
@@ -220,8 +229,36 @@
|
|
|
220
229
|
</ul>
|
|
221
230
|
{/if}
|
|
222
231
|
|
|
232
|
+
<!-- Blocking error — guard returned { allowed: false, reason } -->
|
|
233
|
+
{#if validationDisplay !== 'inline' && snap.hasAttemptedNext && snap.blockingError}
|
|
234
|
+
<p class="pw-shell__blocking-error">{snap.blockingError}</p>
|
|
235
|
+
{/if}
|
|
236
|
+
|
|
237
|
+
<!-- Error panel: replaces footer when an async operation has failed -->
|
|
238
|
+
{#if snap.status === "error" && snap.error}
|
|
239
|
+
{@const err = snap.error}
|
|
240
|
+
{@const escalated = err.retryCount >= 2}
|
|
241
|
+
<div class="pw-shell__error">
|
|
242
|
+
<div class="pw-shell__error-title">{escalated ? "Still having trouble." : "Something went wrong."}</div>
|
|
243
|
+
<div class="pw-shell__error-message">{errorPhaseMessage(err.phase)}{err.message ? ` ${err.message}` : ""}</div>
|
|
244
|
+
<div class="pw-shell__error-actions">
|
|
245
|
+
{#if !escalated}
|
|
246
|
+
<button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
|
|
247
|
+
{/if}
|
|
248
|
+
{#if snap.hasPersistence}
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
class="pw-shell__btn {escalated ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend'}"
|
|
252
|
+
onclick={suspend}
|
|
253
|
+
>Save and come back later</button>
|
|
254
|
+
{/if}
|
|
255
|
+
{#if escalated && !snap.hasPersistence}
|
|
256
|
+
<button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
|
|
257
|
+
{/if}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
223
260
|
<!-- Footer: navigation buttons (overridable via footer snippet) -->
|
|
224
|
-
{
|
|
261
|
+
{:else if footer}
|
|
225
262
|
{@render footer(snap, actions)}
|
|
226
263
|
{:else}
|
|
227
264
|
<div class="pw-shell__footer">
|
|
@@ -231,7 +268,7 @@
|
|
|
231
268
|
<button
|
|
232
269
|
type="button"
|
|
233
270
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
234
|
-
disabled={snap.
|
|
271
|
+
disabled={snap.status !== "idle"}
|
|
235
272
|
onclick={cancel}
|
|
236
273
|
>
|
|
237
274
|
{cancelLabel}
|
|
@@ -241,7 +278,7 @@
|
|
|
241
278
|
<button
|
|
242
279
|
type="button"
|
|
243
280
|
class="pw-shell__btn pw-shell__btn--back"
|
|
244
|
-
disabled={snap.
|
|
281
|
+
disabled={snap.status !== "idle" || !snap.canMovePrevious}
|
|
245
282
|
onclick={previous}
|
|
246
283
|
>
|
|
247
284
|
{backLabel}
|
|
@@ -254,7 +291,7 @@
|
|
|
254
291
|
<button
|
|
255
292
|
type="button"
|
|
256
293
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
257
|
-
disabled={snap.
|
|
294
|
+
disabled={snap.status !== "idle"}
|
|
258
295
|
onclick={cancel}
|
|
259
296
|
>
|
|
260
297
|
{cancelLabel}
|
|
@@ -264,10 +301,11 @@
|
|
|
264
301
|
<button
|
|
265
302
|
type="button"
|
|
266
303
|
class="pw-shell__btn pw-shell__btn--next"
|
|
267
|
-
|
|
304
|
+
class:pw-shell__btn--loading={snap.status !== "idle"}
|
|
305
|
+
disabled={snap.status !== "idle"}
|
|
268
306
|
onclick={next}
|
|
269
307
|
>
|
|
270
|
-
{snap.isLastStep ? completeLabel : nextLabel}
|
|
308
|
+
{snap.status !== 'idle' && loadingLabel ? loadingLabel : snap.isLastStep ? completeLabel : nextLabel}
|
|
271
309
|
</button>
|
|
272
310
|
</div>
|
|
273
311
|
</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;
|
|
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;AAmPH,QAAA,MAAM,SAAS;mBA5JQ,QAAQ,IAAI,CAAC;MA4JmB,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
|
+
|
package/dist/index.svelte.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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 {
|
|
141
|
+
* import { usePathContext } from '@daltonr/pathwrite-svelte';
|
|
125
142
|
*
|
|
126
|
-
* const ctx =
|
|
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
|
|
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.
|
|
@@ -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,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC9D;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC9C,iCAAiC;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,6MAA6M;IAC7M,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnH,6DAA6D;IAC7D,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qJAAqJ;IACrJ,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,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,qHAAqH;IACrH,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,OAAO,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACvD,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,KAAK,CAAC,
|
|
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
|
@@ -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
|
-
*
|
|
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 {
|
|
120
|
+
* import { usePathContext } from '@daltonr/pathwrite-svelte';
|
|
112
121
|
*
|
|
113
|
-
* const ctx =
|
|
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
|
|
130
|
+
export function usePathContext() {
|
|
122
131
|
const ctx = getContext(PATH_CONTEXT_KEY);
|
|
123
132
|
if (!ctx) {
|
|
124
|
-
throw new Error("
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Svelte 5 adapter for @daltonr/pathwrite-core — runes-based reactive bindings and optional PathShell component.",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"svelte": ">=5.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@daltonr/pathwrite-core": "^0.
|
|
55
|
+
"@daltonr/pathwrite-core": "^0.10.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@sveltejs/package": "^2.5.7",
|
package/src/PathShell.svelte
CHANGED
|
@@ -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 } 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
|
|
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
|
|
@@ -109,7 +116,7 @@
|
|
|
109
116
|
});
|
|
110
117
|
|
|
111
118
|
let snap = $derived(pathReturn.snapshot);
|
|
112
|
-
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData) });
|
|
119
|
+
let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
|
|
113
120
|
|
|
114
121
|
// Auto-detect footer layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
115
122
|
let resolvedFooterLayout = $derived(
|
|
@@ -190,9 +197,11 @@
|
|
|
190
197
|
register snippets by inner step ids directly. -->
|
|
191
198
|
<div class="pw-shell__body">
|
|
192
199
|
{#if snap.formId && stepSnippets[snap.formId]}
|
|
193
|
-
|
|
200
|
+
{@const StepComponent = stepSnippets[snap.formId]}
|
|
201
|
+
<StepComponent />
|
|
194
202
|
{:else if stepSnippets[snap.stepId]}
|
|
195
|
-
|
|
203
|
+
{@const StepComponent = stepSnippets[snap.stepId]}
|
|
204
|
+
<StepComponent />
|
|
196
205
|
{:else}
|
|
197
206
|
<p>No content for step "{snap.stepId}"</p>
|
|
198
207
|
{/if}
|
|
@@ -220,8 +229,36 @@
|
|
|
220
229
|
</ul>
|
|
221
230
|
{/if}
|
|
222
231
|
|
|
232
|
+
<!-- Blocking error — guard returned { allowed: false, reason } -->
|
|
233
|
+
{#if validationDisplay !== 'inline' && snap.hasAttemptedNext && snap.blockingError}
|
|
234
|
+
<p class="pw-shell__blocking-error">{snap.blockingError}</p>
|
|
235
|
+
{/if}
|
|
236
|
+
|
|
237
|
+
<!-- Error panel: replaces footer when an async operation has failed -->
|
|
238
|
+
{#if snap.status === "error" && snap.error}
|
|
239
|
+
{@const err = snap.error}
|
|
240
|
+
{@const escalated = err.retryCount >= 2}
|
|
241
|
+
<div class="pw-shell__error">
|
|
242
|
+
<div class="pw-shell__error-title">{escalated ? "Still having trouble." : "Something went wrong."}</div>
|
|
243
|
+
<div class="pw-shell__error-message">{errorPhaseMessage(err.phase)}{err.message ? ` ${err.message}` : ""}</div>
|
|
244
|
+
<div class="pw-shell__error-actions">
|
|
245
|
+
{#if !escalated}
|
|
246
|
+
<button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
|
|
247
|
+
{/if}
|
|
248
|
+
{#if snap.hasPersistence}
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
class="pw-shell__btn {escalated ? 'pw-shell__btn--retry' : 'pw-shell__btn--suspend'}"
|
|
252
|
+
onclick={suspend}
|
|
253
|
+
>Save and come back later</button>
|
|
254
|
+
{/if}
|
|
255
|
+
{#if escalated && !snap.hasPersistence}
|
|
256
|
+
<button type="button" class="pw-shell__btn pw-shell__btn--retry" onclick={retry}>Try again</button>
|
|
257
|
+
{/if}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
223
260
|
<!-- Footer: navigation buttons (overridable via footer snippet) -->
|
|
224
|
-
{
|
|
261
|
+
{:else if footer}
|
|
225
262
|
{@render footer(snap, actions)}
|
|
226
263
|
{:else}
|
|
227
264
|
<div class="pw-shell__footer">
|
|
@@ -231,7 +268,7 @@
|
|
|
231
268
|
<button
|
|
232
269
|
type="button"
|
|
233
270
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
234
|
-
disabled={snap.
|
|
271
|
+
disabled={snap.status !== "idle"}
|
|
235
272
|
onclick={cancel}
|
|
236
273
|
>
|
|
237
274
|
{cancelLabel}
|
|
@@ -241,7 +278,7 @@
|
|
|
241
278
|
<button
|
|
242
279
|
type="button"
|
|
243
280
|
class="pw-shell__btn pw-shell__btn--back"
|
|
244
|
-
disabled={snap.
|
|
281
|
+
disabled={snap.status !== "idle" || !snap.canMovePrevious}
|
|
245
282
|
onclick={previous}
|
|
246
283
|
>
|
|
247
284
|
{backLabel}
|
|
@@ -254,7 +291,7 @@
|
|
|
254
291
|
<button
|
|
255
292
|
type="button"
|
|
256
293
|
class="pw-shell__btn pw-shell__btn--cancel"
|
|
257
|
-
disabled={snap.
|
|
294
|
+
disabled={snap.status !== "idle"}
|
|
258
295
|
onclick={cancel}
|
|
259
296
|
>
|
|
260
297
|
{cancelLabel}
|
|
@@ -264,10 +301,11 @@
|
|
|
264
301
|
<button
|
|
265
302
|
type="button"
|
|
266
303
|
class="pw-shell__btn pw-shell__btn--next"
|
|
267
|
-
|
|
304
|
+
class:pw-shell__btn--loading={snap.status !== "idle"}
|
|
305
|
+
disabled={snap.status !== "idle"}
|
|
268
306
|
onclick={next}
|
|
269
307
|
>
|
|
270
|
-
{snap.isLastStep ? completeLabel : nextLabel}
|
|
308
|
+
{snap.status !== 'idle' && loadingLabel ? loadingLabel : snap.isLastStep ? completeLabel : nextLabel}
|
|
271
309
|
</button>
|
|
272
310
|
</div>
|
|
273
311
|
</div>
|
package/src/index.svelte.ts
CHANGED
|
@@ -8,7 +8,8 @@ import type {
|
|
|
8
8
|
} from "@daltonr/pathwrite-core";
|
|
9
9
|
import { PathEngine as PathEngineClass } from "@daltonr/pathwrite-core";
|
|
10
10
|
|
|
11
|
-
// Re-export core types for convenience
|
|
11
|
+
// Re-export core utilities and types for convenience
|
|
12
|
+
export { formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
|
|
12
13
|
export type {
|
|
13
14
|
PathData,
|
|
14
15
|
FieldErrors,
|
|
@@ -75,6 +76,10 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
75
76
|
* Use for "Start over" / retry flows without remounting the component.
|
|
76
77
|
*/
|
|
77
78
|
restart: () => Promise<void>;
|
|
79
|
+
/** Re-runs the operation that set `snapshot.error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. */
|
|
80
|
+
retry: () => Promise<void>;
|
|
81
|
+
/** Pauses the path with intent to return. Emits `suspended`. All state is preserved. */
|
|
82
|
+
suspend: () => Promise<void>;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// ---------------------------------------------------------------------------
|
|
@@ -180,6 +185,8 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
180
185
|
const resetStep = (): Promise<void> => engine.resetStep();
|
|
181
186
|
|
|
182
187
|
const restart = (): Promise<void> => engine.restart();
|
|
188
|
+
const retry = (): Promise<void> => engine.retry();
|
|
189
|
+
const suspend = (): Promise<void> => engine.suspend();
|
|
183
190
|
|
|
184
191
|
return {
|
|
185
192
|
get snapshot() { return _snapshot; },
|
|
@@ -192,7 +199,9 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
192
199
|
goToStepChecked,
|
|
193
200
|
setData,
|
|
194
201
|
resetStep,
|
|
195
|
-
restart
|
|
202
|
+
restart,
|
|
203
|
+
retry,
|
|
204
|
+
suspend
|
|
196
205
|
};
|
|
197
206
|
}
|
|
198
207
|
|
|
@@ -202,7 +211,7 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
202
211
|
|
|
203
212
|
const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
|
|
204
213
|
|
|
205
|
-
export interface PathContext<TData extends PathData = PathData> {
|
|
214
|
+
export interface PathContext<TData extends PathData = PathData, TServices = unknown> {
|
|
206
215
|
readonly snapshot: PathSnapshot<TData>;
|
|
207
216
|
next: () => Promise<void>;
|
|
208
217
|
previous: () => Promise<void>;
|
|
@@ -212,18 +221,30 @@ export interface PathContext<TData extends PathData = PathData> {
|
|
|
212
221
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
213
222
|
resetStep: () => Promise<void>;
|
|
214
223
|
restart: () => Promise<void>;
|
|
224
|
+
/** Re-run the operation that set `snapshot.error`. */
|
|
225
|
+
retry: () => Promise<void>;
|
|
226
|
+
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
227
|
+
suspend: () => Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Services object passed through context from `PathShell`.
|
|
230
|
+
* Typed as `TServices` when `usePathContext<TData, TServices>()` is used.
|
|
231
|
+
*/
|
|
232
|
+
services: TServices;
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
/**
|
|
218
|
-
*
|
|
236
|
+
* Access the nearest `PathShell`'s path instance and optional services object.
|
|
219
237
|
* Use this inside step components to access the path engine.
|
|
220
238
|
*
|
|
239
|
+
* - `TData` narrows `ctx.snapshot?.data`
|
|
240
|
+
* - `TServices` types the `services` value — must match what was passed to `PathShell`
|
|
241
|
+
*
|
|
221
242
|
* @example
|
|
222
243
|
* ```svelte
|
|
223
244
|
* <script lang="ts">
|
|
224
|
-
* import {
|
|
245
|
+
* import { usePathContext } from '@daltonr/pathwrite-svelte';
|
|
225
246
|
*
|
|
226
|
-
* const ctx =
|
|
247
|
+
* const ctx = usePathContext();
|
|
227
248
|
* </script>
|
|
228
249
|
*
|
|
229
250
|
* <input value={ctx.snapshot?.data.name}
|
|
@@ -231,11 +252,11 @@ export interface PathContext<TData extends PathData = PathData> {
|
|
|
231
252
|
* <button onclick={ctx.next}>Next</button>
|
|
232
253
|
* ```
|
|
233
254
|
*/
|
|
234
|
-
export function
|
|
235
|
-
const ctx = getContext<PathContext<TData>>(PATH_CONTEXT_KEY);
|
|
255
|
+
export function usePathContext<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices> {
|
|
256
|
+
const ctx = getContext<PathContext<TData, TServices>>(PATH_CONTEXT_KEY);
|
|
236
257
|
if (!ctx) {
|
|
237
258
|
throw new Error(
|
|
238
|
-
"
|
|
259
|
+
"usePathContext() must be called from a component inside a <PathShell>. " +
|
|
239
260
|
"Ensure the PathShell component is a parent in the component tree."
|
|
240
261
|
);
|
|
241
262
|
}
|
|
@@ -246,7 +267,7 @@ export function getPathContext<TData extends PathData = PathData>(): PathContext
|
|
|
246
267
|
* Internal: Set the PathContext for child components.
|
|
247
268
|
* Used by PathShell component.
|
|
248
269
|
*/
|
|
249
|
-
export function setPathContext<TData extends PathData = PathData>(ctx: PathContext<TData>): void {
|
|
270
|
+
export function setPathContext<TData extends PathData = PathData, TServices = unknown>(ctx: PathContext<TData, TServices>): void {
|
|
250
271
|
setContext(PATH_CONTEXT_KEY, ctx);
|
|
251
272
|
}
|
|
252
273
|
|