@daltonr/pathwrite-svelte 0.12.0 → 0.13.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 +32 -8
- package/dist/PathShell.svelte +49 -14
- package/dist/PathShell.svelte.d.ts +2 -2
- package/dist/PathShell.svelte.d.ts.map +1 -1
- package/dist/index.css +17 -0
- package/dist/index.svelte.d.ts +24 -10
- package/dist/index.svelte.d.ts.map +1 -1
- package/dist/index.svelte.js +20 -5
- package/package.json +8 -3
- package/src/PathShell.svelte +49 -14
- package/src/index.svelte.ts +51 -21
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ Peer dependencies: Svelte 5+.
|
|
|
22
22
|
import { applicationPath } from "./application-path";
|
|
23
23
|
import DetailsStep from "./DetailsStep.svelte";
|
|
24
24
|
import CoverNoteStep from "./CoverNoteStep.svelte";
|
|
25
|
+
import type { PathData } from "@daltonr/pathwrite-core";
|
|
25
26
|
|
|
26
|
-
function handleComplete(data) {
|
|
27
|
+
function handleComplete(data: PathData) {
|
|
27
28
|
console.log("Submitted:", data);
|
|
28
29
|
}
|
|
29
30
|
</script>
|
|
@@ -72,13 +73,16 @@ Peer dependencies: Svelte 5+.
|
|
|
72
73
|
|---|---|---|
|
|
73
74
|
| `snapshot` | `PathSnapshot \| null` | Reactive getter. `null` when no path is active or when `completionBehaviour: "dismiss"` is used. With the default `"stayOnFinal"`, a non-null snapshot with `status === "completed"` is returned after the path finishes. |
|
|
74
75
|
| `start(definition, data?)` | `Promise<void>` | Start or restart a path. |
|
|
75
|
-
| `restart(
|
|
76
|
+
| `restart()` | `Promise<void>` | Tear down any active path (without firing hooks) and restart the root path with the `initialData` from the original `start()`. Takes no arguments; rejects if nothing has been started. |
|
|
76
77
|
| `next()` | `Promise<void>` | Advance one step. Completes on the last step. |
|
|
77
78
|
| `previous()` | `Promise<void>` | Go back one step. No-op on the first step of a top-level path. |
|
|
78
79
|
| `cancel()` | `Promise<void>` | Cancel the active path (or sub-path). |
|
|
79
80
|
| `goToStep(stepId)` | `Promise<void>` | Jump to a step by ID. Calls `onLeave`/`onEnter`; bypasses guards. |
|
|
80
81
|
| `goToStepChecked(stepId)` | `Promise<void>` | Jump to a step by ID, checking the current step's guard first. |
|
|
81
82
|
| `setData(key, value)` | `Promise<void>` | Update a single data field. Type-safe when `TData` is specified. |
|
|
83
|
+
| `resetStep()` | `Promise<void>` | Restore the current step's data to what it was when the step was entered. Emits `stateChanged` with cause `"resetStep"`; no hooks run. |
|
|
84
|
+
| `retry()` | `Promise<void>` | Re-run the operation that set `snapshot.error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. |
|
|
85
|
+
| `suspend()` | `Promise<void>` | Pause the path with intent to return. Emits `suspended`; all state and data are preserved. |
|
|
82
86
|
| `startSubPath(definition, data?, meta?)` | `Promise<void>` | Push a sub-path. `meta` is returned to `onSubPathComplete`/`onSubPathCancel`. |
|
|
83
87
|
| `validate()` | `void` | Set `snapshot.hasValidated` without navigating. Triggers all inline field errors simultaneously. Used to validate all tabs in a nested shell at once. |
|
|
84
88
|
|
|
@@ -96,24 +100,32 @@ Step content is supplied as Svelte 5 snippets whose names match each step's `id`
|
|
|
96
100
|
| Prop | Type | Default | Description |
|
|
97
101
|
|---|---|---|---|
|
|
98
102
|
| `path` | `PathDefinition` | — | Path to run. Mutually exclusive with `engine`. |
|
|
99
|
-
| `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. |
|
|
100
|
-
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
|
|
103
|
+
| `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. May be provided after mount (e.g. once an async `restoreOrStart()` resolves): the shell adopts it, re-subscribing and re-seeding from the new engine. Set `autoStart` to `false` if the shell should not start its own path in the meantime. |
|
|
104
|
+
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. Overridden by the stored snapshot when `restoreKey` is set. |
|
|
101
105
|
| `autoStart` | `boolean` | `true` | Start on mount. Ignored when `engine` is provided. |
|
|
102
106
|
| `layout` | `"wizard" \| "form" \| "auto" \| "tabs"` | `"auto"` | `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back. `"tabs"`: No progress header or footer — for tabbed interfaces. `"auto"` picks `"form"` for single-step paths. |
|
|
107
|
+
| `validationDisplay` | `"summary" \| "inline" \| "both"` | `"summary"` | Where `fieldErrors` are rendered. Use `"inline"` so step components render their own errors. |
|
|
108
|
+
| `progressLayout` | `"merged" \| "split" \| "rootOnly" \| "activeOnly"` | `"merged"` | How the root and sub-path progress bars are arranged while a sub-path is active. |
|
|
103
109
|
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. Also hidden automatically for single-step paths. |
|
|
110
|
+
| `hideFooter` | `boolean` | `false` | Hide the footer (navigation buttons). The error panel is still shown on async failure. |
|
|
104
111
|
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
105
112
|
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
106
113
|
| `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
|
|
114
|
+
| `loadingLabel` | `string` | `undefined` | Label for the Next/Complete button while an async operation is in progress. When unset, the button keeps its label and shows a CSS spinner. |
|
|
107
115
|
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
108
116
|
| `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
|
|
109
|
-
| `validateWhen` | `boolean` | `false` | When
|
|
110
|
-
| `restoreKey` | `string` | — | When set, the shell automatically saves its full state (data + active step) into the nearest outer `PathShell`'s data under this key on every change, and restores from it on remount. No-op on a top-level shell. |
|
|
117
|
+
| `validateWhen` | `boolean` | `false` | When `true` (including already at mount), calls `validate()` on the engine so all steps show inline errors at once. Bind to the outer snapshot's `hasAttemptedNext` when this shell is nested inside a step of an outer shell. |
|
|
118
|
+
| `restoreKey` | `string` | — | When set, the shell automatically saves its full state (data + active step) into the nearest outer `PathShell`'s data under this key on every change, and restores from it on remount. No-op on a top-level shell. The stored value also carries the inner engine's serialized state, so a remount restores in place: no `onEnter` / `onLeave` re-run, attempted / visited state kept. |
|
|
111
119
|
| `services` | `unknown` | `null` | Arbitrary services object available to step components via `usePathContext<TData, TServices>().services`. |
|
|
112
120
|
| `oncomplete` | `(data: PathData) => void` | — | Called when the path finishes naturally. |
|
|
113
121
|
| `oncancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
|
|
114
122
|
| `onevent` | `(event: PathEvent) => void` | — | Called for every engine event. |
|
|
123
|
+
| `header` | `Snippet<[PathSnapshot]>` | — | Replaces the default progress header. A custom header is shown even for single-step paths, and hidden under `hideProgress` or `layout="tabs"`. |
|
|
124
|
+
| `footer` | `Snippet<[PathSnapshot, PathShellActions]>` | — | Replaces the default navigation footer. `actions` contains `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`, `retry`, `suspend`. |
|
|
115
125
|
| `completion` | `Snippet<[PathSnapshot<any>]>` | — | Custom snippet rendered when `snapshot.status === "completed"` (`completionBehaviour: "stayOnFinal"`). Receives the completed snapshot. If omitted, a default "All done." panel is shown. |
|
|
116
126
|
|
|
127
|
+
The component instance also exposes `restart()` for `bind:this` refs, which restarts the path with its original `initialData` without remounting.
|
|
128
|
+
|
|
117
129
|
> **Note:** Svelte requires event/callback props to be lowercase. Unlike React/Vue/Angular, passing `onComplete`, `onCancel`, or `onEvent` (camelCase) will be silently ignored. PathShell emits a `console.warn` in development if it detects one of these common mistakes.
|
|
118
130
|
|
|
119
131
|
You can also replace the built-in header and footer with custom snippets:
|
|
@@ -137,7 +149,7 @@ You can also replace the built-in header and footer with custom snippets:
|
|
|
137
149
|
|
|
138
150
|
## usePathContext
|
|
139
151
|
|
|
140
|
-
`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
|
|
152
|
+
`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 `snapshot` (typed `PathSnapshot | null` — narrow with `{#if ctx.snapshot}`), the navigation actions (`next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `resetStep`, `restart`, `retry`, `suspend`) and `services`; it does not expose `start`, `startSubPath` or `validate`. 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`. The context is the full `usePath()` return type (`PathContext` extends `UsePathReturn`), so `start`, `startSubPath` and `validate` are available to step components too — for example to launch a sub-path from a button inside a step.
|
|
141
153
|
|
|
142
154
|
```svelte
|
|
143
155
|
<script lang="ts">
|
|
@@ -154,10 +166,22 @@ You can also replace the built-in header and footer with custom snippets:
|
|
|
154
166
|
{/if}
|
|
155
167
|
```
|
|
156
168
|
|
|
169
|
+
## Other exports
|
|
170
|
+
|
|
171
|
+
| Export | Description |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `bindData(getSnapshot, setData, key)` | Two-way binding helper for inputs. Returns an object with a reactive `value` getter (reads `getSnapshot()?.data[key]`) and a `set(value)` method that calls `setData(key, value)`. Example: `const name = bindData(() => path.snapshot, path.setData, "name")`, then `<input value={name.value} oninput={(e) => name.set(e.currentTarget.value)} />`. |
|
|
174
|
+
| `stepIdToCamelCase(id)` | Converts a hyphenated step ID to camelCase (`"cover-letter"` → `"coverLetter"`) — the conversion `<PathShell>` uses to resolve snippets for hyphenated step IDs. |
|
|
175
|
+
| `setPathContext(ctx)` | Sets the `PathContext` that `usePathContext()` reads, under the adapter's private `Symbol` key. Used internally by `<PathShell>`; only needed when building your own shell component. |
|
|
176
|
+
| `getPathContextOrNull()` | Reads the nearest ancestor `PathContext`, or `undefined` when there is none. Used internally by `<PathShell>` to reach the outer shell for `restoreKey`; call it before `setPathContext()` so it reads the parent rather than self. |
|
|
177
|
+
| `formatFieldKey`, `errorPhaseMessage` | Re-exported from `@daltonr/pathwrite-core` for building custom summaries and error panels. |
|
|
178
|
+
|
|
179
|
+
`PathEngine` is re-exported as a **type only**. To construct an engine, import the class from `@daltonr/pathwrite-core`.
|
|
180
|
+
|
|
157
181
|
## Further reading
|
|
158
182
|
|
|
159
183
|
- [Svelte getting started guide](../../docs/getting-started/frameworks/svelte.md)
|
|
160
|
-
- [Navigation & guards](../../docs/
|
|
184
|
+
- [Navigation & guards](../../docs/developer-guide/04-navigation.md)
|
|
161
185
|
- [Full documentation](../../docs/README.md)
|
|
162
186
|
|
|
163
187
|
---
|
package/dist/PathShell.svelte
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
4
|
-
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
|
|
4
|
+
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
|
|
5
|
+
import { PathEngine as PathEngineClass } from '@daltonr/pathwrite-core';
|
|
6
|
+
import type { SerializedPathState } from '@daltonr/pathwrite-core';
|
|
5
7
|
import type { Snippet, Component } from 'svelte';
|
|
6
8
|
|
|
7
9
|
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
onevent?: (event: any) => void;
|
|
62
64
|
// Optional override snippets for header and footer
|
|
63
65
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
64
|
-
footer?: Snippet<[PathSnapshot<any>,
|
|
66
|
+
footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
|
|
65
67
|
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
66
68
|
completion?: Snippet<[PathSnapshot<any>]>;
|
|
67
69
|
// All other props treated as step components keyed by step ID
|
|
@@ -100,33 +102,56 @@
|
|
|
100
102
|
// parent shell's snapshot and setData for restoreKey auto-wiring.
|
|
101
103
|
const outerCtx = getPathContextOrNull();
|
|
102
104
|
|
|
105
|
+
// When remounting under restoreKey, rebuild the inner engine from the state the
|
|
106
|
+
// previous instance exported, instead of starting the path and jumping to the
|
|
107
|
+
// step (which re-ran onEnter/onLeave and lost attempted / visited state).
|
|
108
|
+
// svelte-ignore state_referenced_locally — read once at init on purpose: restore happens at mount only
|
|
109
|
+
const restoredEngine: PathEngine | null = (() => {
|
|
110
|
+
if (engineProp || !restoreKey || !outerCtx || !path) return null;
|
|
111
|
+
const stored = outerCtx.snapshot?.data[restoreKey] as { serializedState?: SerializedPathState } | undefined;
|
|
112
|
+
if (!stored || typeof stored !== 'object' || !stored.serializedState) return null;
|
|
113
|
+
try {
|
|
114
|
+
return PathEngineClass.fromState(stored.serializedState, { [path.id]: path });
|
|
115
|
+
} catch {
|
|
116
|
+
return null; // unusable state (e.g. the path definition changed): start fresh below
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
// The shell's own engine is created once; an `engine` prop — present at
|
|
120
|
+
// mount or arriving later — always takes precedence and is adopted by usePath.
|
|
121
|
+
const ownEngine: PathEngine = restoredEngine ?? new PathEngineClass();
|
|
122
|
+
const currentEngine = (): PathEngine => engineProp ?? ownEngine;
|
|
123
|
+
|
|
103
124
|
// Initialize path engine
|
|
104
125
|
const pathReturn = usePath({
|
|
105
|
-
get engine() { return
|
|
126
|
+
get engine() { return currentEngine(); },
|
|
106
127
|
onEvent: (event) => {
|
|
107
128
|
onevent?.(event);
|
|
108
129
|
if (event.type === 'completed') oncomplete?.(event.data);
|
|
109
130
|
if (event.type === 'cancelled') oncancel?.(event.data);
|
|
110
131
|
if (restoreKey && outerCtx && event.type === 'stateChanged') {
|
|
111
132
|
(outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
|
|
112
|
-
restoreKey, event.snapshot
|
|
133
|
+
restoreKey, { ...event.snapshot, serializedState: currentEngine().exportState() }
|
|
113
134
|
);
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
});
|
|
117
138
|
|
|
118
|
-
const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn, retry, suspend } = pathReturn;
|
|
139
|
+
const { start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, resetStep, restart: restartFn, retry, suspend, validate } = pathReturn;
|
|
119
140
|
|
|
120
141
|
// Provide context for child step components
|
|
121
142
|
setPathContext({
|
|
122
143
|
get snapshot() { return pathReturn.snapshot; },
|
|
144
|
+
start,
|
|
145
|
+
startSubPath,
|
|
146
|
+
validate,
|
|
123
147
|
next,
|
|
124
148
|
previous,
|
|
125
149
|
cancel,
|
|
126
150
|
goToStep,
|
|
127
151
|
goToStepChecked,
|
|
128
152
|
setData,
|
|
129
|
-
|
|
153
|
+
resetStep,
|
|
154
|
+
restart: () => restartFn(),
|
|
130
155
|
retry,
|
|
131
156
|
suspend,
|
|
132
157
|
get services() { return services; },
|
|
@@ -134,8 +159,13 @@
|
|
|
134
159
|
|
|
135
160
|
// Dev-mode warning: camelCase callback props are silently ignored in Svelte.
|
|
136
161
|
// Warn if the user passed onComplete/onCancel/onEvent instead of the correct
|
|
137
|
-
// lowercase forms oncomplete/oncancel/onevent.
|
|
138
|
-
|
|
162
|
+
// lowercase forms oncomplete/oncancel/onevent. Runs once, on mount (a closure
|
|
163
|
+
// — reading the props at the top level would only capture their initial value).
|
|
164
|
+
// `import.meta.env` is a bundler convention (Vite); the cast keeps this
|
|
165
|
+
// package free of Vite's ambient types.
|
|
166
|
+
const isDev = (import.meta as { env?: { DEV?: boolean } }).env?.DEV !== false;
|
|
167
|
+
onMount(() => {
|
|
168
|
+
if (!isDev) return;
|
|
139
169
|
const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
|
|
140
170
|
for (const name of camelCallbacks) {
|
|
141
171
|
if (name in stepSnippets) {
|
|
@@ -144,12 +174,13 @@
|
|
|
144
174
|
);
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
|
-
}
|
|
177
|
+
});
|
|
148
178
|
|
|
149
179
|
// Auto-start the path when no external engine is provided
|
|
150
180
|
let started = false;
|
|
151
181
|
onMount(() => {
|
|
152
|
-
if (autoStart && !started && !engineProp) {
|
|
182
|
+
if (autoStart && !started && !engineProp && !restoredEngine) {
|
|
183
|
+
if (!path) throw new Error('[PathShell] "path" is required when no "engine" is provided');
|
|
153
184
|
started = true;
|
|
154
185
|
let startData: PathData = initialData ?? {};
|
|
155
186
|
let restoreStepId: string | undefined;
|
|
@@ -180,7 +211,11 @@
|
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
let snap = $derived(pathReturn.snapshot);
|
|
183
|
-
let actions = $derived({
|
|
214
|
+
let actions: PathShellActions = $derived({
|
|
215
|
+
next, previous, cancel, goToStep, goToStepChecked,
|
|
216
|
+
setData: (key, value) => setData(key as never, value as never),
|
|
217
|
+
restart: () => restartFn(), retry, suspend
|
|
218
|
+
});
|
|
184
219
|
|
|
185
220
|
let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
|
|
186
221
|
let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
|
|
@@ -202,7 +237,7 @@
|
|
|
202
237
|
* ```
|
|
203
238
|
*/
|
|
204
239
|
export function restart(): Promise<void> {
|
|
205
|
-
return pathReturn.restart(
|
|
240
|
+
return pathReturn.restart();
|
|
206
241
|
}
|
|
207
242
|
</script>
|
|
208
243
|
|
|
@@ -211,7 +246,7 @@
|
|
|
211
246
|
<div class="pw-shell__empty">
|
|
212
247
|
<p>No active path.</p>
|
|
213
248
|
{#if !autoStart}
|
|
214
|
-
<button type="button" class="pw-shell__start-btn" onclick={() => start(path, initialData)}>
|
|
249
|
+
<button type="button" class="pw-shell__start-btn" onclick={() => path && start(path, initialData)}>
|
|
215
250
|
Start
|
|
216
251
|
</button>
|
|
217
252
|
{/if}
|
|
@@ -239,7 +274,7 @@
|
|
|
239
274
|
{:else}
|
|
240
275
|
<div class="pw-shell__completion">
|
|
241
276
|
<p class="pw-shell__completion-message">All done.</p>
|
|
242
|
-
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(
|
|
277
|
+
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn()}>
|
|
243
278
|
Start over
|
|
244
279
|
</button>
|
|
245
280
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
|
|
1
|
+
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
|
|
2
2
|
import type { Snippet, Component } from 'svelte';
|
|
3
3
|
interface Props {
|
|
4
4
|
path?: PathDefinition<any>;
|
|
@@ -54,7 +54,7 @@ interface Props {
|
|
|
54
54
|
oncancel?: (data: PathData) => void;
|
|
55
55
|
onevent?: (event: any) => void;
|
|
56
56
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
57
|
-
footer?: Snippet<[PathSnapshot<any>,
|
|
57
|
+
footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
|
|
58
58
|
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
59
59
|
completion?: Snippet<[PathSnapshot<any>]>;
|
|
60
60
|
[key: string]: Component<any> | any;
|
|
@@ -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;
|
|
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,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG9H,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;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,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,8HAA8H;IAC9H,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qNAAqN;IACrN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C;;;;;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,gBAAgB,CAAC,CAAC,CAAC;IACxD,6HAA6H;IAC7H,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;AA6WH,QAAA,MAAM,SAAS;mBA7LQ,QAAQ,IAAI,CAAC;MA6LmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
package/dist/index.css
CHANGED
|
@@ -357,6 +357,23 @@
|
|
|
357
357
|
content: ":";
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/* ------------------------------------------------------------------ */
|
|
361
|
+
/* Inline field-level errors and warnings (<FieldError /> component) */
|
|
362
|
+
/* ------------------------------------------------------------------ */
|
|
363
|
+
.pw-field-error {
|
|
364
|
+
display: block;
|
|
365
|
+
font-size: 12px;
|
|
366
|
+
color: var(--pw-color-error);
|
|
367
|
+
margin-top: 4px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.pw-field-warning {
|
|
371
|
+
display: block;
|
|
372
|
+
font-size: 12px;
|
|
373
|
+
color: var(--pw-color-warning);
|
|
374
|
+
margin-top: 4px;
|
|
375
|
+
}
|
|
376
|
+
|
|
360
377
|
/* ------------------------------------------------------------------ */
|
|
361
378
|
/* Footer — navigation buttons */
|
|
362
379
|
/* ------------------------------------------------------------------ */
|
package/dist/index.svelte.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export interface UsePathOptions {
|
|
|
12
12
|
* - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
|
|
13
13
|
* - `PathShell` will skip its own `autoStart` call.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* An externally managed engine. Pass it as a getter (`get engine() { … }`)
|
|
17
|
+
* over a reactive prop to have the hook track it: an engine that arrives
|
|
18
|
+
* later (e.g. from an async `restoreOrStart()`) or is swapped is adopted —
|
|
19
|
+
* the hook re-subscribes and re-seeds its snapshot from the new engine.
|
|
20
|
+
*/
|
|
15
21
|
engine?: PathEngine;
|
|
16
22
|
/** Called for every engine event (stateChanged, completed, cancelled, resumed). */
|
|
17
23
|
onEvent?: (event: PathEvent) => void;
|
|
@@ -47,8 +53,9 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
47
53
|
/** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
|
|
48
54
|
resetStep: () => Promise<void>;
|
|
49
55
|
/**
|
|
50
|
-
* Tear down any active path (without firing hooks) and immediately
|
|
51
|
-
*
|
|
56
|
+
* Tear down any active path (without firing hooks) and immediately restart
|
|
57
|
+
* the root path with the `initialData` from the original `start()` call.
|
|
58
|
+
* Takes no arguments; rejects if the engine has never been started.
|
|
52
59
|
* Use for "Start over" / retry flows without remounting the component.
|
|
53
60
|
*/
|
|
54
61
|
restart: () => Promise<void>;
|
|
@@ -114,8 +121,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
114
121
|
* ```
|
|
115
122
|
*/
|
|
116
123
|
export declare function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData>;
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Navigation actions handed to a custom `footer` snippet of `<PathShell>`
|
|
126
|
+
* (`{#snippet footer(snap, actions)}`). Same shape as the other adapters'
|
|
127
|
+
* `PathShellActions`.
|
|
128
|
+
*/
|
|
129
|
+
export interface PathShellActions {
|
|
119
130
|
next: () => Promise<void>;
|
|
120
131
|
previous: () => Promise<void>;
|
|
121
132
|
cancel: () => Promise<void>;
|
|
@@ -125,17 +136,20 @@ export interface PathContext<TData extends PathData = PathData, TServices = unkn
|
|
|
125
136
|
goToStepChecked: (stepId: string, options?: {
|
|
126
137
|
validateOnLeave?: boolean;
|
|
127
138
|
}) => Promise<void>;
|
|
128
|
-
setData:
|
|
129
|
-
|
|
139
|
+
setData: (key: string, value: unknown) => Promise<void>;
|
|
140
|
+
/** Restart the shell's current path with its current `initialData`. */
|
|
130
141
|
restart: () => Promise<void>;
|
|
131
142
|
/** Re-run the operation that set `snapshot.error`. */
|
|
132
143
|
retry: () => Promise<void>;
|
|
133
144
|
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
134
145
|
suspend: () => Promise<void>;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* What step components receive from `usePathContext()`: everything `usePath()`
|
|
149
|
+
* returns (derived from `UsePathReturn`, so the two cannot drift apart) plus
|
|
150
|
+
* the `services` object given to `<PathShell>`.
|
|
151
|
+
*/
|
|
152
|
+
export interface PathContext<TData extends PathData = PathData, TServices = unknown> extends UsePathReturn<TData> {
|
|
139
153
|
services: TServices;
|
|
140
154
|
}
|
|
141
155
|
/**
|
|
@@ -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,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,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,6LAA6L;IAC7L,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,oLAAoL;IACpL,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,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
|
|
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
|
@@ -60,11 +60,11 @@ export { formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
|
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
62
|
export function usePath(options) {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
let ownEngine = null;
|
|
64
|
+
const resolveEngine = () => options?.engine ?? (ownEngine ??= new PathEngineClass());
|
|
65
|
+
let engine = resolveEngine();
|
|
65
66
|
let _snapshot = $state(engine.snapshot());
|
|
66
|
-
|
|
67
|
-
const unsubscribe = engine.subscribe((event) => {
|
|
67
|
+
const onEngineEvent = (event) => {
|
|
68
68
|
if (event.type === "stateChanged" || event.type === "resumed") {
|
|
69
69
|
_snapshot = event.snapshot;
|
|
70
70
|
}
|
|
@@ -72,9 +72,24 @@ export function usePath(options) {
|
|
|
72
72
|
_snapshot = engine.snapshot();
|
|
73
73
|
}
|
|
74
74
|
options?.onEvent?.(event);
|
|
75
|
+
};
|
|
76
|
+
let unsubscribe = engine.subscribe(onEngineEvent);
|
|
77
|
+
// Adopt a late or swapped engine (the `engine` option is a getter over a
|
|
78
|
+
// reactive prop): re-subscribe and re-seed the snapshot. Created in its own
|
|
79
|
+
// effect root so usePath() also works outside a component.
|
|
80
|
+
const stopWatching = $effect.root(() => {
|
|
81
|
+
$effect(() => {
|
|
82
|
+
const next = resolveEngine();
|
|
83
|
+
if (next === engine)
|
|
84
|
+
return;
|
|
85
|
+
unsubscribe();
|
|
86
|
+
engine = next;
|
|
87
|
+
_snapshot = engine.snapshot();
|
|
88
|
+
unsubscribe = engine.subscribe(onEngineEvent);
|
|
89
|
+
});
|
|
75
90
|
});
|
|
76
91
|
// Auto-cleanup when component is destroyed
|
|
77
|
-
onDestroy(unsubscribe);
|
|
92
|
+
onDestroy(() => { unsubscribe(); stopWatching(); });
|
|
78
93
|
const start = (path, initialData = {}) => engine.start(path, initialData);
|
|
79
94
|
const startSubPath = (path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta);
|
|
80
95
|
const next = () => engine.next();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daltonr/pathwrite-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"import": "./dist/index.svelte.js"
|
|
29
29
|
},
|
|
30
30
|
"./PathShell.svelte": {
|
|
31
|
+
"types": "./dist/PathShell.svelte.d.ts",
|
|
31
32
|
"svelte": "./dist/PathShell.svelte",
|
|
32
33
|
"import": "./dist/PathShell.svelte"
|
|
33
34
|
},
|
|
@@ -44,7 +45,8 @@
|
|
|
44
45
|
"LICENSE"
|
|
45
46
|
],
|
|
46
47
|
"scripts": {
|
|
47
|
-
"
|
|
48
|
+
"check": "svelte-check --tsconfig ./tsconfig.check.json --fail-on-warnings",
|
|
49
|
+
"build": "npm run check && svelte-package -i src -o dist && cp ../shell.css dist/index.css",
|
|
48
50
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
49
51
|
"prepublishOnly": "test -d dist && echo 'dist already built, skipping' || (npm run clean && npm run build)"
|
|
50
52
|
},
|
|
@@ -52,12 +54,15 @@
|
|
|
52
54
|
"svelte": ">=5.0.0"
|
|
53
55
|
},
|
|
54
56
|
"dependencies": {
|
|
55
|
-
"@daltonr/pathwrite-core": "^0.
|
|
57
|
+
"@daltonr/pathwrite-core": "^0.13.1"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
58
60
|
"@sveltejs/package": "^2.5.7",
|
|
59
61
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
60
62
|
"@testing-library/svelte": "^5.0.0",
|
|
61
63
|
"svelte": "^5.0.0"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
62
67
|
}
|
|
63
68
|
}
|
package/src/PathShell.svelte
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
|
|
4
|
-
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
|
|
4
|
+
import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
|
|
5
|
+
import { PathEngine as PathEngineClass } from '@daltonr/pathwrite-core';
|
|
6
|
+
import type { SerializedPathState } from '@daltonr/pathwrite-core';
|
|
5
7
|
import type { Snippet, Component } from 'svelte';
|
|
6
8
|
|
|
7
9
|
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
onevent?: (event: any) => void;
|
|
62
64
|
// Optional override snippets for header and footer
|
|
63
65
|
header?: Snippet<[PathSnapshot<any>]>;
|
|
64
|
-
footer?: Snippet<[PathSnapshot<any>,
|
|
66
|
+
footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
|
|
65
67
|
/** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
|
|
66
68
|
completion?: Snippet<[PathSnapshot<any>]>;
|
|
67
69
|
// All other props treated as step components keyed by step ID
|
|
@@ -100,33 +102,56 @@
|
|
|
100
102
|
// parent shell's snapshot and setData for restoreKey auto-wiring.
|
|
101
103
|
const outerCtx = getPathContextOrNull();
|
|
102
104
|
|
|
105
|
+
// When remounting under restoreKey, rebuild the inner engine from the state the
|
|
106
|
+
// previous instance exported, instead of starting the path and jumping to the
|
|
107
|
+
// step (which re-ran onEnter/onLeave and lost attempted / visited state).
|
|
108
|
+
// svelte-ignore state_referenced_locally — read once at init on purpose: restore happens at mount only
|
|
109
|
+
const restoredEngine: PathEngine | null = (() => {
|
|
110
|
+
if (engineProp || !restoreKey || !outerCtx || !path) return null;
|
|
111
|
+
const stored = outerCtx.snapshot?.data[restoreKey] as { serializedState?: SerializedPathState } | undefined;
|
|
112
|
+
if (!stored || typeof stored !== 'object' || !stored.serializedState) return null;
|
|
113
|
+
try {
|
|
114
|
+
return PathEngineClass.fromState(stored.serializedState, { [path.id]: path });
|
|
115
|
+
} catch {
|
|
116
|
+
return null; // unusable state (e.g. the path definition changed): start fresh below
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
// The shell's own engine is created once; an `engine` prop — present at
|
|
120
|
+
// mount or arriving later — always takes precedence and is adopted by usePath.
|
|
121
|
+
const ownEngine: PathEngine = restoredEngine ?? new PathEngineClass();
|
|
122
|
+
const currentEngine = (): PathEngine => engineProp ?? ownEngine;
|
|
123
|
+
|
|
103
124
|
// Initialize path engine
|
|
104
125
|
const pathReturn = usePath({
|
|
105
|
-
get engine() { return
|
|
126
|
+
get engine() { return currentEngine(); },
|
|
106
127
|
onEvent: (event) => {
|
|
107
128
|
onevent?.(event);
|
|
108
129
|
if (event.type === 'completed') oncomplete?.(event.data);
|
|
109
130
|
if (event.type === 'cancelled') oncancel?.(event.data);
|
|
110
131
|
if (restoreKey && outerCtx && event.type === 'stateChanged') {
|
|
111
132
|
(outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
|
|
112
|
-
restoreKey, event.snapshot
|
|
133
|
+
restoreKey, { ...event.snapshot, serializedState: currentEngine().exportState() }
|
|
113
134
|
);
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
});
|
|
117
138
|
|
|
118
|
-
const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn, retry, suspend } = pathReturn;
|
|
139
|
+
const { start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, resetStep, restart: restartFn, retry, suspend, validate } = pathReturn;
|
|
119
140
|
|
|
120
141
|
// Provide context for child step components
|
|
121
142
|
setPathContext({
|
|
122
143
|
get snapshot() { return pathReturn.snapshot; },
|
|
144
|
+
start,
|
|
145
|
+
startSubPath,
|
|
146
|
+
validate,
|
|
123
147
|
next,
|
|
124
148
|
previous,
|
|
125
149
|
cancel,
|
|
126
150
|
goToStep,
|
|
127
151
|
goToStepChecked,
|
|
128
152
|
setData,
|
|
129
|
-
|
|
153
|
+
resetStep,
|
|
154
|
+
restart: () => restartFn(),
|
|
130
155
|
retry,
|
|
131
156
|
suspend,
|
|
132
157
|
get services() { return services; },
|
|
@@ -134,8 +159,13 @@
|
|
|
134
159
|
|
|
135
160
|
// Dev-mode warning: camelCase callback props are silently ignored in Svelte.
|
|
136
161
|
// Warn if the user passed onComplete/onCancel/onEvent instead of the correct
|
|
137
|
-
// lowercase forms oncomplete/oncancel/onevent.
|
|
138
|
-
|
|
162
|
+
// lowercase forms oncomplete/oncancel/onevent. Runs once, on mount (a closure
|
|
163
|
+
// — reading the props at the top level would only capture their initial value).
|
|
164
|
+
// `import.meta.env` is a bundler convention (Vite); the cast keeps this
|
|
165
|
+
// package free of Vite's ambient types.
|
|
166
|
+
const isDev = (import.meta as { env?: { DEV?: boolean } }).env?.DEV !== false;
|
|
167
|
+
onMount(() => {
|
|
168
|
+
if (!isDev) return;
|
|
139
169
|
const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
|
|
140
170
|
for (const name of camelCallbacks) {
|
|
141
171
|
if (name in stepSnippets) {
|
|
@@ -144,12 +174,13 @@
|
|
|
144
174
|
);
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
|
-
}
|
|
177
|
+
});
|
|
148
178
|
|
|
149
179
|
// Auto-start the path when no external engine is provided
|
|
150
180
|
let started = false;
|
|
151
181
|
onMount(() => {
|
|
152
|
-
if (autoStart && !started && !engineProp) {
|
|
182
|
+
if (autoStart && !started && !engineProp && !restoredEngine) {
|
|
183
|
+
if (!path) throw new Error('[PathShell] "path" is required when no "engine" is provided');
|
|
153
184
|
started = true;
|
|
154
185
|
let startData: PathData = initialData ?? {};
|
|
155
186
|
let restoreStepId: string | undefined;
|
|
@@ -180,7 +211,11 @@
|
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
let snap = $derived(pathReturn.snapshot);
|
|
183
|
-
let actions = $derived({
|
|
214
|
+
let actions: PathShellActions = $derived({
|
|
215
|
+
next, previous, cancel, goToStep, goToStepChecked,
|
|
216
|
+
setData: (key, value) => setData(key as never, value as never),
|
|
217
|
+
restart: () => restartFn(), retry, suspend
|
|
218
|
+
});
|
|
184
219
|
|
|
185
220
|
let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
|
|
186
221
|
let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
|
|
@@ -202,7 +237,7 @@
|
|
|
202
237
|
* ```
|
|
203
238
|
*/
|
|
204
239
|
export function restart(): Promise<void> {
|
|
205
|
-
return pathReturn.restart(
|
|
240
|
+
return pathReturn.restart();
|
|
206
241
|
}
|
|
207
242
|
</script>
|
|
208
243
|
|
|
@@ -211,7 +246,7 @@
|
|
|
211
246
|
<div class="pw-shell__empty">
|
|
212
247
|
<p>No active path.</p>
|
|
213
248
|
{#if !autoStart}
|
|
214
|
-
<button type="button" class="pw-shell__start-btn" onclick={() => start(path, initialData)}>
|
|
249
|
+
<button type="button" class="pw-shell__start-btn" onclick={() => path && start(path, initialData)}>
|
|
215
250
|
Start
|
|
216
251
|
</button>
|
|
217
252
|
{/if}
|
|
@@ -239,7 +274,7 @@
|
|
|
239
274
|
{:else}
|
|
240
275
|
<div class="pw-shell__completion">
|
|
241
276
|
<p class="pw-shell__completion-message">All done.</p>
|
|
242
|
-
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(
|
|
277
|
+
<button type="button" class="pw-shell__completion-restart" onclick={() => restartFn()}>
|
|
243
278
|
Start over
|
|
244
279
|
</button>
|
|
245
280
|
</div>
|
package/src/index.svelte.ts
CHANGED
|
@@ -39,6 +39,12 @@ export interface UsePathOptions {
|
|
|
39
39
|
* - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
|
|
40
40
|
* - `PathShell` will skip its own `autoStart` call.
|
|
41
41
|
*/
|
|
42
|
+
/**
|
|
43
|
+
* An externally managed engine. Pass it as a getter (`get engine() { … }`)
|
|
44
|
+
* over a reactive prop to have the hook track it: an engine that arrives
|
|
45
|
+
* later (e.g. from an async `restoreOrStart()`) or is swapped is adopted —
|
|
46
|
+
* the hook re-subscribes and re-seeds its snapshot from the new engine.
|
|
47
|
+
*/
|
|
42
48
|
engine?: PathEngine;
|
|
43
49
|
/** Called for every engine event (stateChanged, completed, cancelled, resumed). */
|
|
44
50
|
onEvent?: (event: PathEvent) => void;
|
|
@@ -71,8 +77,9 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
71
77
|
/** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
|
|
72
78
|
resetStep: () => Promise<void>;
|
|
73
79
|
/**
|
|
74
|
-
* Tear down any active path (without firing hooks) and immediately
|
|
75
|
-
*
|
|
80
|
+
* Tear down any active path (without firing hooks) and immediately restart
|
|
81
|
+
* the root path with the `initialData` from the original `start()` call.
|
|
82
|
+
* Takes no arguments; rejects if the engine has never been started.
|
|
76
83
|
* Use for "Start over" / retry flows without remounting the component.
|
|
77
84
|
*/
|
|
78
85
|
restart: () => Promise<void>;
|
|
@@ -145,25 +152,40 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
145
152
|
export function usePath<TData extends PathData = PathData>(
|
|
146
153
|
options?: UsePathOptions
|
|
147
154
|
): UsePathReturn<TData> {
|
|
148
|
-
|
|
155
|
+
let ownEngine: PathEngine | null = null;
|
|
156
|
+
const resolveEngine = (): PathEngine => options?.engine ?? (ownEngine ??= new PathEngineClass());
|
|
157
|
+
let engine = resolveEngine();
|
|
149
158
|
|
|
150
|
-
// Reactive snapshot via $state rune
|
|
151
159
|
let _snapshot: PathSnapshot<TData> | null = $state(
|
|
152
160
|
engine.snapshot() as PathSnapshot<TData> | null
|
|
153
161
|
);
|
|
154
162
|
|
|
155
|
-
|
|
156
|
-
const unsubscribe = engine.subscribe((event: PathEvent) => {
|
|
163
|
+
const onEngineEvent = (event: PathEvent): void => {
|
|
157
164
|
if (event.type === "stateChanged" || event.type === "resumed") {
|
|
158
165
|
_snapshot = event.snapshot as PathSnapshot<TData>;
|
|
159
166
|
} else if (event.type === "completed" || event.type === "cancelled") {
|
|
160
167
|
_snapshot = engine.snapshot() as PathSnapshot<TData> | null;
|
|
161
168
|
}
|
|
162
169
|
options?.onEvent?.(event);
|
|
170
|
+
};
|
|
171
|
+
let unsubscribe = engine.subscribe(onEngineEvent);
|
|
172
|
+
|
|
173
|
+
// Adopt a late or swapped engine (the `engine` option is a getter over a
|
|
174
|
+
// reactive prop): re-subscribe and re-seed the snapshot. Created in its own
|
|
175
|
+
// effect root so usePath() also works outside a component.
|
|
176
|
+
const stopWatching = $effect.root(() => {
|
|
177
|
+
$effect(() => {
|
|
178
|
+
const next = resolveEngine();
|
|
179
|
+
if (next === engine) return;
|
|
180
|
+
unsubscribe();
|
|
181
|
+
engine = next;
|
|
182
|
+
_snapshot = engine.snapshot() as PathSnapshot<TData> | null;
|
|
183
|
+
unsubscribe = engine.subscribe(onEngineEvent);
|
|
184
|
+
});
|
|
163
185
|
});
|
|
164
186
|
|
|
165
187
|
// Auto-cleanup when component is destroyed
|
|
166
|
-
onDestroy(unsubscribe);
|
|
188
|
+
onDestroy(() => { unsubscribe(); stopWatching(); });
|
|
167
189
|
|
|
168
190
|
const start = (path: PathDefinition<any>, initialData: PathData = {}): Promise<void> =>
|
|
169
191
|
engine.start(path, initialData);
|
|
@@ -210,30 +232,38 @@ export function usePath<TData extends PathData = PathData>(
|
|
|
210
232
|
};
|
|
211
233
|
}
|
|
212
234
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
export interface PathContext<TData extends PathData = PathData, TServices = unknown> {
|
|
220
|
-
readonly snapshot: PathSnapshot<TData>;
|
|
235
|
+
/**
|
|
236
|
+
* Navigation actions handed to a custom `footer` snippet of `<PathShell>`
|
|
237
|
+
* (`{#snippet footer(snap, actions)}`). Same shape as the other adapters'
|
|
238
|
+
* `PathShellActions`.
|
|
239
|
+
*/
|
|
240
|
+
export interface PathShellActions {
|
|
221
241
|
next: () => Promise<void>;
|
|
222
242
|
previous: () => Promise<void>;
|
|
223
243
|
cancel: () => Promise<void>;
|
|
224
244
|
goToStep: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
225
245
|
goToStepChecked: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
|
|
226
|
-
setData:
|
|
227
|
-
|
|
246
|
+
setData: (key: string, value: unknown) => Promise<void>;
|
|
247
|
+
/** Restart the shell's current path with its current `initialData`. */
|
|
228
248
|
restart: () => Promise<void>;
|
|
229
249
|
/** Re-run the operation that set `snapshot.error`. */
|
|
230
250
|
retry: () => Promise<void>;
|
|
231
251
|
/** Pause with intent to return, preserving all state. Emits `suspended`. */
|
|
232
252
|
suspend: () => Promise<void>;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Context API for PathShell
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* What step components receive from `usePathContext()`: everything `usePath()`
|
|
263
|
+
* returns (derived from `UsePathReturn`, so the two cannot drift apart) plus
|
|
264
|
+
* the `services` object given to `<PathShell>`.
|
|
265
|
+
*/
|
|
266
|
+
export interface PathContext<TData extends PathData = PathData, TServices = unknown> extends UsePathReturn<TData> {
|
|
237
267
|
services: TServices;
|
|
238
268
|
}
|
|
239
269
|
|