@daltonr/pathwrite-react 0.3.0 → 0.5.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/README.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  React hooks over `@daltonr/pathwrite-core`. Exposes path state as reactive React state via `useSyncExternalStore`, with stable action callbacks and an optional context provider.
4
4
 
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @daltonr/pathwrite-core @daltonr/pathwrite-react
9
+ ```
10
+
11
+ ## Exported Types
12
+
13
+ For convenience, this package re-exports core types so you don't need to import from `@daltonr/pathwrite-core`:
14
+
15
+ ```typescript
16
+ import {
17
+ PathShell, // React-specific
18
+ usePath, // React-specific
19
+ usePathContext, // React-specific
20
+ PathProvider, // React-specific
21
+ PathEngine, // Re-exported from core (value + type)
22
+ PathData, // Re-exported from core
23
+ PathDefinition, // Re-exported from core
24
+ PathEvent, // Re-exported from core
25
+ PathSnapshot, // Re-exported from core
26
+ PathStep, // Re-exported from core
27
+ PathStepContext, // Re-exported from core
28
+ SerializedPathState // Re-exported from core
29
+ } from "@daltonr/pathwrite-react";
30
+ ```
31
+
32
+ ---
33
+
5
34
  ## Setup
6
35
 
7
36
  ### Option A — `usePath` hook (component-scoped)
@@ -26,7 +55,7 @@ function MyPathHost() {
26
55
  <p>Step {snapshot.stepIndex + 1} of {snapshot.stepCount}</p>
27
56
  <button onClick={previous} disabled={snapshot.isNavigating}>Back</button>
28
57
  <button onClick={next} disabled={snapshot.isNavigating}>
29
- {snapshot.isLastStep ? "Finish" : "Next"}
58
+ {snapshot.isLastStep ? "Complete" : "Next"}
30
59
  </button>
31
60
  <button onClick={cancel}>Cancel</button>
32
61
  </>
@@ -78,6 +107,7 @@ function NavButtons() {
78
107
 
79
108
  | Option | Type | Description |
80
109
  |--------|------|-------------|
110
+ | `engine` | `PathEngine` | An externally-managed engine (e.g. from `createPersistedEngine()`). When provided, `usePath` subscribes to it instead of creating a new one; snapshot is seeded immediately from the engine's current state. The caller is responsible for the engine's lifecycle. Must be a stable reference. |
81
111
  | `onEvent` | `(event: PathEvent) => void` | Called for every engine event. The callback ref is kept current — changing it does **not** re-subscribe to the engine. |
82
112
 
83
113
  ### Return value
@@ -93,6 +123,7 @@ function NavButtons() {
93
123
  | `goToStep(stepId)` | `function` | Jump directly to a step by ID. Calls `onLeave` / `onEnter` but bypasses guards and `shouldSkip`. |
94
124
  | `goToStepChecked(stepId)` | `function` | Jump to a step by ID, checking `canMoveNext` (forward) or `canMovePrevious` (backward) first. Navigation is blocked if the guard returns false. |
95
125
  | `setData(key, value)` | `function` | Update a single data value; triggers re-render via `stateChanged`. When `TData` is specified, `key` and `value` are type-checked against your data shape. |
126
+ | `restart(definition, data?)` | `function` | Tear down any active path (without firing hooks) and start the given path fresh. Safe to call at any time. Use for "Start over" / retry flows. |
96
127
 
97
128
  All action callbacks are **referentially stable** — safe to pass as props or include in dependency arrays without causing unnecessary re-renders.
98
129
 
@@ -163,15 +194,16 @@ import { PathShell } from "@daltonr/pathwrite-react";
163
194
  | Prop | Type | Default | Description |
164
195
  |------|------|---------|-------------|
165
196
  | `path` | `PathDefinition` | *required* | The path to run. |
197
+ | `engine` | `PathEngine` | — | An externally-managed engine. When provided, `PathShell` skips its own `start()` and drives the UI from this engine. |
166
198
  | `steps` | `Record<string, ReactNode>` | *required* | Map of step ID → content to render. |
167
199
  | `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
168
200
  | `autoStart` | `boolean` | `true` | Start the path automatically on mount. |
169
201
  | `onComplete` | `(data: PathData) => void` | — | Called when the path completes. |
170
202
  | `onCancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
171
203
  | `onEvent` | `(event: PathEvent) => void` | — | Called for every engine event. |
172
- | `backLabel` | `string` | `"Back"` | Back button label. |
204
+ | `backLabel` | `string` | `"Previous"` | Previous button label. |
173
205
  | `nextLabel` | `string` | `"Next"` | Next button label. |
174
- | `finishLabel` | `string` | `"Finish"` | Finish button label (last step). |
206
+ | `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
175
207
  | `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
176
208
  | `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
177
209
  | `hideProgress` | `boolean` | `false` | Hide the progress indicator. |
@@ -194,14 +226,14 @@ Use `renderHeader` and `renderFooter` to replace the built-in progress bar or na
194
226
  <div>
195
227
  <button onClick={actions.previous} disabled={snapshot.isFirstStep}>Back</button>
196
228
  <button onClick={actions.next} disabled={!snapshot.canMoveNext}>
197
- {snapshot.isLastStep ? "Finish" : "Next"}
229
+ {snapshot.isLastStep ? "Complete" : "Next"}
198
230
  </button>
199
231
  </div>
200
232
  )}
201
233
  />
202
234
  ```
203
235
 
204
- `PathShellActions` contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`.
236
+ `PathShellActions` contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`.
205
237
 
206
238
  ### Context sharing
207
239
 
@@ -245,11 +277,259 @@ All visual values are CSS custom properties (`--pw-*`), so you can theme without
245
277
  }
246
278
  ```
247
279
 
280
+ ### Available CSS Custom Properties
281
+
282
+ **Layout:**
283
+ - `--pw-shell-max-width` — Maximum width of the shell (default: `720px`)
284
+ - `--pw-shell-padding` — Internal padding (default: `24px`)
285
+ - `--pw-shell-gap` — Gap between header, body, footer (default: `20px`)
286
+ - `--pw-shell-radius` — Border radius for cards (default: `10px`)
287
+
288
+ **Colors:**
289
+ - `--pw-color-bg` — Background color (default: `#ffffff`)
290
+ - `--pw-color-border` — Border color (default: `#dbe4f0`)
291
+ - `--pw-color-text` — Primary text color (default: `#1f2937`)
292
+ - `--pw-color-muted` — Muted text color (default: `#5b677a`)
293
+ - `--pw-color-primary` — Primary/accent color (default: `#2563eb`)
294
+ - `--pw-color-primary-light` — Light primary for backgrounds (default: `rgba(37, 99, 235, 0.12)`)
295
+ - `--pw-color-btn-bg` — Button background (default: `#f8fbff`)
296
+ - `--pw-color-btn-border` — Button border (default: `#c2d0e5`)
297
+
298
+ **Validation:**
299
+ - `--pw-color-error` — Error text color (default: `#dc2626`)
300
+ - `--pw-color-error-bg` — Error background (default: `#fef2f2`)
301
+ - `--pw-color-error-border` — Error border (default: `#fecaca`)
302
+
303
+ **Progress Indicator:**
304
+ - `--pw-dot-size` — Step dot size (default: `32px`)
305
+ - `--pw-dot-font-size` — Font size inside dots (default: `13px`)
306
+ - `--pw-track-height` — Progress track height (default: `4px`)
307
+
308
+ **Buttons:**
309
+ - `--pw-btn-padding` — Button padding (default: `8px 16px`)
310
+ - `--pw-btn-radius` — Button border radius (default: `6px`)
311
+
248
312
  ---
249
313
 
250
- ## Design notes
314
+ ## Sub-Paths
315
+
316
+ Sub-paths allow you to nest multi-step workflows. Common use cases include:
317
+ - Running a child workflow per collection item (e.g., approve each document)
318
+ - Conditional drill-down flows (e.g., "Add payment method" modal)
319
+ - Reusable wizard components
320
+
321
+ ### Basic Sub-Path Flow
322
+
323
+ When a sub-path is active:
324
+ - The shell switches to show the sub-path's steps
325
+ - The progress bar displays sub-path steps (not main path steps)
326
+ - Pressing Back on the first sub-path step **cancels** the sub-path and returns to the parent
327
+ - `usePathContext()` returns the **sub-path** snapshot, not the parent's
328
+
329
+ ### Complete Example: Approver Collection
330
+
331
+ ```tsx
332
+ import type { PathData, PathDefinition } from "@daltonr/pathwrite-core";
333
+
334
+ // Sub-path data shape
335
+ interface ApproverReviewData extends PathData {
336
+ decision: "approve" | "reject" | "";
337
+ comments: string;
338
+ }
339
+
340
+ // Main path data shape
341
+ interface ApprovalWorkflowData extends PathData {
342
+ documentTitle: string;
343
+ approvers: string[];
344
+ approvals: Array<{ approver: string; decision: string; comments: string }>;
345
+ }
346
+
347
+ // Define the sub-path (approver review wizard)
348
+ const approverReviewPath: PathDefinition<ApproverReviewData> = {
349
+ id: "approver-review",
350
+ steps: [
351
+ { id: "review", title: "Review Document" },
352
+ {
353
+ id: "decision",
354
+ title: "Make Decision",
355
+ canMoveNext: ({ data }) =>
356
+ data.decision === "approve" || data.decision === "reject",
357
+ validationMessages: ({ data }) =>
358
+ !data.decision ? ["Please select Approve or Reject"] : []
359
+ },
360
+ { id: "comments", title: "Add Comments" }
361
+ ]
362
+ };
363
+
364
+ // Define the main path
365
+ const approvalWorkflowPath: PathDefinition<ApprovalWorkflowData> = {
366
+ id: "approval-workflow",
367
+ steps: [
368
+ {
369
+ id: "setup",
370
+ title: "Setup Approval",
371
+ canMoveNext: ({ data }) =>
372
+ (data.documentTitle ?? "").trim().length > 0 &&
373
+ data.approvers.length > 0
374
+ },
375
+ {
376
+ id: "run-approvals",
377
+ title: "Collect Approvals",
378
+ // Block "Next" until all approvers have completed their reviews
379
+ canMoveNext: ({ data }) =>
380
+ data.approvals.length === data.approvers.length,
381
+ validationMessages: ({ data }) => {
382
+ const remaining = data.approvers.length - data.approvals.length;
383
+ return remaining > 0
384
+ ? [`${remaining} approver(s) pending review`]
385
+ : [];
386
+ },
387
+ // When an approver finishes their sub-path, record the result
388
+ onSubPathComplete(subPathId, subPathData, ctx, meta) {
389
+ const approverName = meta?.approverName as string;
390
+ const result = subPathData as ApproverReviewData;
391
+ return {
392
+ approvals: [
393
+ ...ctx.data.approvals,
394
+ {
395
+ approver: approverName,
396
+ decision: result.decision,
397
+ comments: result.comments
398
+ }
399
+ ]
400
+ };
401
+ },
402
+ // If an approver cancels (presses Back on first step), you can track it
403
+ onSubPathCancel(subPathId, subPathData, ctx, meta) {
404
+ console.log(`${meta?.approverName} cancelled their review`);
405
+ // Optionally return data changes, or just log
406
+ }
407
+ },
408
+ { id: "summary", title: "Summary" }
409
+ ]
410
+ };
411
+
412
+ // Component
413
+ function ApprovalWorkflow() {
414
+ const { startSubPath } = usePathContext<ApprovalWorkflowData>();
415
+
416
+ function launchReviewForApprover(approverName: string, index: number) {
417
+ // Pass correlation data via `meta` — it's echoed back to onSubPathComplete
418
+ startSubPath(
419
+ approverReviewPath,
420
+ { decision: "", comments: "" },
421
+ { approverName, approverIndex: index }
422
+ );
423
+ }
424
+
425
+ return (
426
+ <PathShell
427
+ path={approvalWorkflowPath}
428
+ initialData={{ documentTitle: "", approvers: [], approvals: [] }}
429
+ steps={{
430
+ setup: <SetupStep />,
431
+ "run-approvals": <RunApprovalsStep onLaunchReview={launchReviewForApprover} />,
432
+ summary: <SummaryStep />,
433
+ // Sub-path steps (must be co-located in the same steps map)
434
+ review: <ReviewDocumentStep />,
435
+ decision: <MakeDecisionStep />,
436
+ comments: <AddCommentsStep />
437
+ }}
438
+ />
439
+ );
440
+ }
441
+ ```
442
+
443
+ ### Key Notes
444
+
445
+ **1. Sub-path steps must be co-located with main path steps**
446
+ All step content (main path + sub-path steps) lives in the same `steps` prop. When a sub-path is active, the shell renders the sub-path's step content. This means:
447
+ - Parent and sub-path step IDs **must not collide** (e.g., don't use `summary` in both)
448
+ - Sub-path step components can access parent data by referencing the parent path definition, but `usePathContext()` returns the **sub-path** snapshot
449
+
450
+ **2. The `meta` correlation field**
451
+ `startSubPath` accepts an optional third argument (`meta`) that is returned unchanged to `onSubPathComplete` and `onSubPathCancel`. Use it to correlate which collection item triggered the sub-path:
452
+
453
+ ```tsx
454
+ startSubPath(subPath, initialData, { itemIndex: 3, itemId: "abc" });
455
+
456
+ // In the parent step:
457
+ onSubPathComplete(subPathId, subPathData, ctx, meta) {
458
+ const itemIndex = meta?.itemIndex; // 3
459
+ }
460
+ ```
461
+
462
+ **3. Progress bar switches during sub-paths**
463
+ When `snapshot.nestingLevel > 0`, you're in a sub-path. The `steps` array in the snapshot contains the sub-path's steps, not the main path's. The default PathShell progress bar shows sub-path progress. You can check `nestingLevel` to show a breadcrumb or "back to main flow" indicator.
251
464
 
252
- - **`useSyncExternalStore`** the hook subscribes to the core `PathEngine` using React 18's `useSyncExternalStore`, giving tear-free reads with no `useEffect` timing gaps.
253
- - **Ref-based callback** `onEvent` is stored in a ref so that a new closure on every render does not cause a re-subscription.
254
- - **No RxJS** — unlike the Angular adapter, there is no RxJS dependency. The hook is pure React.
465
+ **4. Accessing parent path data from sub-path components**
466
+ There is currently no `useParentPathContext()` hook. If a sub-path step needs parent data (e.g., the document title), pass it via `initialData` when calling `startSubPath`:
467
+
468
+ ```tsx
469
+ startSubPath(approverReviewPath, {
470
+ decision: "",
471
+ comments: "",
472
+ documentTitle: snapshot.data.documentTitle // copy from parent
473
+ });
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Guards and Lifecycle Hooks
479
+
480
+ ### Defensive Guards (Important!)
481
+
482
+ **Guards and `validationMessages` are evaluated *before* `onEnter` runs on first entry.**
483
+
484
+ If you access fields in a guard that `onEnter` is supposed to initialize, the guard will throw a `TypeError` on startup. Write guards defensively using nullish coalescing:
485
+
486
+ ```ts
487
+ // ✗ Unsafe — crashes if data.name is undefined
488
+ canMoveNext: ({ data }) => data.name.trim().length > 0
489
+
490
+ // ✓ Safe — handles undefined gracefully
491
+ canMoveNext: ({ data }) => (data.name ?? "").trim().length > 0
492
+ ```
493
+
494
+ Alternatively, pass `initialData` to `start()` / `<PathShell>` so all fields are present from the first snapshot:
495
+
496
+ ```tsx
497
+ <PathShell path={myPath} initialData={{ name: "", age: 0 }} />
498
+ ```
499
+
500
+ If a guard throws, the engine catches it, logs a warning, and returns `true` (allow navigation) as a safe default.
501
+
502
+ ### Async Guards and Validation Messages
503
+
504
+ Guards and `validationMessages` must be **synchronous** for inclusion in snapshots. Async functions are detected and warned about:
505
+ - Async `canMoveNext` / `canMovePrevious` default to `true` (optimistic)
506
+ - Async `validationMessages` default to `[]`
507
+
508
+ The async version is still enforced during actual navigation (when you call `next()` / `previous()`), but the snapshot won't reflect the pending state. If you need async validation, perform it in the guard and store the result in `data` so the guard can read it synchronously.
509
+
510
+ ### `isFirstEntry` Flag
511
+
512
+ The `PathStepContext` passed to all hooks includes an `isFirstEntry: boolean` flag. It's `true` the first time a step is visited, `false` on re-entry (e.g., after navigating back then forward again).
513
+
514
+ Use it to distinguish initialization from re-entry:
515
+
516
+ ```ts
517
+ {
518
+ id: "details",
519
+ onEnter: ({ isFirstEntry, data }) => {
520
+ if (isFirstEntry) {
521
+ // Only pre-fill on first visit, not when returning via Back
522
+ return { name: "Default Name" };
523
+ }
524
+ }
525
+ }
526
+ ```
527
+
528
+ **Important:** `onEnter` fires every time you enter the step. If you want "initialize once" behavior, either:
529
+ 1. Use `isFirstEntry` to conditionally return data
530
+ 2. Provide `initialData` to `start()` instead of using `onEnter`
531
+
532
+ ---
533
+
534
+ ## Design notes
255
535
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,18 @@
1
1
  import type { PropsWithChildren, ReactElement, ReactNode } from "react";
2
- import { PathData, PathDefinition, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
2
+ import { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
3
3
  export interface UsePathOptions {
4
- /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
4
+ /**
5
+ * An externally-managed `PathEngine` to subscribe to — for example, the engine
6
+ * returned by `createPersistedEngine()` from `@daltonr/pathwrite-store-http`.
7
+ *
8
+ * When provided:
9
+ * - `usePath` will **not** create its own engine.
10
+ * - The snapshot is seeded immediately from the engine's current state.
11
+ * - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
12
+ * - `PathShell` will skip its own `autoStart` call.
13
+ */
14
+ engine?: PathEngine;
15
+ /** Called for every engine event (stateChanged, completed, cancelled, resumed). The callback ref is kept current — changing it does **not** re-subscribe to the engine. */
5
16
  onEvent?: (event: PathEvent) => void;
6
17
  }
7
18
  export interface UsePathReturn<TData extends PathData = PathData> {
@@ -23,6 +34,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
23
34
  goToStepChecked: (stepId: string) => void;
24
35
  /** Update a single data value; triggers a re-render via stateChanged. When `TData` is specified, `key` and `value` are type-checked against your data shape. */
25
36
  setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
37
+ /**
38
+ * Tear down any active path (without firing hooks) and immediately start the
39
+ * given path fresh. Safe to call whether or not a path is currently active.
40
+ * Use for "Start over" / retry flows without remounting the component.
41
+ */
42
+ restart: (path: PathDefinition<any>, initialData?: PathData) => void;
26
43
  }
27
44
  export type PathProviderProps = PropsWithChildren<{
28
45
  /** Forwarded to the internal usePath hook. */
@@ -45,6 +62,12 @@ export declare function usePathContext<TData extends PathData = PathData>(): Use
45
62
  export interface PathShellProps {
46
63
  /** The path definition to drive. */
47
64
  path: PathDefinition<any>;
65
+ /**
66
+ * An externally-managed engine — for example, the engine returned by
67
+ * `createPersistedEngine()`. When supplied, `PathShell` will skip its own
68
+ * `start()` call and drive the UI from the provided engine instead.
69
+ */
70
+ engine?: PathEngine;
48
71
  /** Map of step ID → content. The shell renders `steps[snapshot.stepId]` for the current step. */
49
72
  steps: Record<string, ReactNode>;
50
73
  /** Initial data passed to `engine.start()`. */
@@ -57,12 +80,12 @@ export interface PathShellProps {
57
80
  onCancel?: (data: PathData) => void;
58
81
  /** Called for every engine event. */
59
82
  onEvent?: (event: PathEvent) => void;
60
- /** Label for the Back button. Defaults to `"Back"`. */
83
+ /** Label for the Previous button. Defaults to `"Previous"`. */
61
84
  backLabel?: string;
62
85
  /** Label for the Next button. Defaults to `"Next"`. */
63
86
  nextLabel?: string;
64
- /** Label for the Finish button (shown on the last step). Defaults to `"Finish"`. */
65
- finishLabel?: string;
87
+ /** Label for the Complete button (shown on the last step). Defaults to `"Complete"`. */
88
+ completeLabel?: string;
66
89
  /** Label for the Cancel button. Defaults to `"Cancel"`. */
67
90
  cancelLabel?: string;
68
91
  /** If true, hide the Cancel button. Defaults to `false`. */
@@ -83,6 +106,8 @@ export interface PathShellActions {
83
106
  goToStep: (stepId: string) => void;
84
107
  goToStepChecked: (stepId: string) => void;
85
108
  setData: (key: string, value: unknown) => void;
109
+ /** Restart the shell's current path with its current `initialData`. */
110
+ restart: () => void;
86
111
  }
87
112
  /**
88
113
  * Default UI shell that renders a progress indicator, step content, and navigation
@@ -100,4 +125,6 @@ export interface PathShellActions {
100
125
  * />
101
126
  * ```
102
127
  */
103
- export declare function PathShell({ path: pathDef, steps, initialData, autoStart, onComplete, onCancel, onEvent, backLabel, nextLabel, finishLabel, cancelLabel, hideCancel, hideProgress, className, renderHeader, renderFooter, }: PathShellProps): ReactElement;
128
+ export declare function PathShell({ path: pathDef, engine: externalEngine, steps, initialData, autoStart, onComplete, onCancel, onEvent, backLabel, nextLabel, completeLabel, cancelLabel, hideCancel, hideProgress, className, renderHeader, renderFooter, }: PathShellProps): ReactElement;
129
+ export type { PathData, PathDefinition, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
130
+ export { PathEngine } from "@daltonr/pathwrite-core";
package/dist/index.js CHANGED
@@ -4,17 +4,31 @@ import { PathEngine } from "@daltonr/pathwrite-core";
4
4
  // usePath hook
5
5
  // ---------------------------------------------------------------------------
6
6
  export function usePath(options) {
7
- // Stable engine instance for the lifetime of the hook
7
+ // Use provided engine or create a stable new one for this hook's lifetime.
8
+ // options.engine must be a stable reference (don't recreate on every render).
8
9
  const engineRef = useRef(null);
9
10
  if (engineRef.current === null) {
10
- engineRef.current = new PathEngine();
11
+ engineRef.current = options?.engine ?? new PathEngine();
11
12
  }
12
13
  const engine = engineRef.current;
13
14
  // Keep the onEvent callback current without changing the subscribe identity
14
15
  const onEventRef = useRef(options?.onEvent);
15
16
  onEventRef.current = options?.onEvent;
16
- // Cached snapshot updated only inside the subscribe callback
17
+ // Seed immediately from existing engine state essential when restoring a
18
+ // persisted path (the engine is already started before usePath is called).
19
+ // We track whether we've seeded to avoid calling engine.snapshot() on every
20
+ // re-render (React evaluates useRef's argument each time).
21
+ const seededRef = useRef(false);
17
22
  const snapshotRef = useRef(null);
23
+ if (!seededRef.current) {
24
+ seededRef.current = true;
25
+ try {
26
+ snapshotRef.current = engine.snapshot();
27
+ }
28
+ catch {
29
+ snapshotRef.current = null;
30
+ }
31
+ }
18
32
  const subscribe = useCallback((callback) => engine.subscribe((event) => {
19
33
  if (event.type === "stateChanged" || event.type === "resumed") {
20
34
  snapshotRef.current = event.snapshot;
@@ -36,7 +50,8 @@ export function usePath(options) {
36
50
  const goToStep = useCallback((stepId) => engine.goToStep(stepId), [engine]);
37
51
  const goToStepChecked = useCallback((stepId) => engine.goToStepChecked(stepId), [engine]);
38
52
  const setData = useCallback((key, value) => engine.setData(key, value), [engine]);
39
- return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
53
+ const restart = useCallback((path, initialData = {}) => engine.restart(path, initialData), [engine]);
54
+ return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, restart };
40
55
  }
41
56
  // ---------------------------------------------------------------------------
42
57
  // Context + Provider
@@ -80,8 +95,9 @@ export function usePathContext() {
80
95
  * />
81
96
  * ```
82
97
  */
83
- export function PathShell({ path: pathDef, steps, initialData = {}, autoStart = true, onComplete, onCancel, onEvent, backLabel = "Back", nextLabel = "Next", finishLabel = "Finish", cancelLabel = "Cancel", hideCancel = false, hideProgress = false, className, renderHeader, renderFooter, }) {
98
+ export function PathShell({ path: pathDef, engine: externalEngine, steps, initialData = {}, autoStart = true, onComplete, onCancel, onEvent, backLabel = "Previous", nextLabel = "Next", completeLabel = "Complete", cancelLabel = "Cancel", hideCancel = false, hideProgress = false, className, renderHeader, renderFooter, }) {
84
99
  const pathReturn = usePath({
100
+ engine: externalEngine,
85
101
  onEvent(event) {
86
102
  onEvent?.(event);
87
103
  if (event.type === "completed")
@@ -90,11 +106,12 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
90
106
  onCancel?.(event.data);
91
107
  }
92
108
  });
93
- const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData } = pathReturn;
94
- // Auto-start on mount
109
+ const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
110
+ // Auto-start on mount — skipped when an external engine is provided since
111
+ // the caller is responsible for starting it (e.g. via createPersistedEngine).
95
112
  const startedRef = useRef(false);
96
113
  useEffect(() => {
97
- if (autoStart && !startedRef.current) {
114
+ if (autoStart && !startedRef.current && !externalEngine) {
98
115
  startedRef.current = true;
99
116
  start(pathDef, initialData);
100
117
  }
@@ -109,7 +126,10 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
109
126
  onClick: () => start(pathDef, initialData)
110
127
  }, "Start"))));
111
128
  }
112
- const actions = { next, previous, cancel, goToStep, goToStepChecked, setData };
129
+ const actions = {
130
+ next, previous, cancel, goToStep, goToStepChecked, setData,
131
+ restart: () => restart(pathDef, initialData)
132
+ };
113
133
  return createElement(PathContext.Provider, { value: pathReturn }, createElement("div", { className: cls("pw-shell", className) },
114
134
  // Header — progress indicator
115
135
  !hideProgress && (renderHeader
@@ -123,7 +143,7 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
123
143
  renderFooter
124
144
  ? renderFooter(snapshot, actions)
125
145
  : defaultFooter(snapshot, actions, {
126
- backLabel, nextLabel, finishLabel, cancelLabel, hideCancel
146
+ backLabel, nextLabel, completeLabel, cancelLabel, hideCancel
127
147
  })));
128
148
  }
129
149
  // ---------------------------------------------------------------------------
@@ -154,7 +174,7 @@ function defaultFooter(snapshot, actions, labels) {
154
174
  className: "pw-shell__btn pw-shell__btn--next",
155
175
  disabled: snapshot.isNavigating || !snapshot.canMoveNext,
156
176
  onClick: actions.next
157
- }, snapshot.isLastStep ? labels.finishLabel : labels.nextLabel)));
177
+ }, snapshot.isLastStep ? labels.completeLabel : labels.nextLabel)));
158
178
  }
159
179
  // ---------------------------------------------------------------------------
160
180
  // Helpers
@@ -162,4 +182,5 @@ function defaultFooter(snapshot, actions, labels) {
162
182
  function cls(...parts) {
163
183
  return parts.filter(Boolean).join(" ");
164
184
  }
185
+ export { PathEngine } from "@daltonr/pathwrite-core";
165
186
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,SAAS,EACT,MAAM,EACN,oBAAoB,EACrB,MAAM,OAAO,CAAC;AAEf,OAAO,EAGL,UAAU,EAGX,MAAM,yBAAyB,CAAC;AAqCjC,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,MAAM,UAAU,OAAO,CAAoC,OAAwB;IACjF,sDAAsD;IACtD,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAClD,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC/B,SAAS,CAAC,OAAO,GAAG,IAAI,UAAU,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC;IAEjC,4EAA4E;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,UAAU,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;IAEtC,+DAA+D;IAC/D,MAAM,WAAW,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE7D,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,QAAoB,EAAE,EAAE,CACvB,MAAM,CAAC,SAAS,CAAC,CAAC,KAAgB,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9D,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC,QAA+B,CAAC;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACpE,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC;QACD,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QAC5B,QAAQ,EAAE,CAAC;IACb,CAAC,CAAC,EACJ,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAE/D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAE9D,0BAA0B;IAC1B,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,IAAyB,EAAE,cAAwB,EAAE,EAAE,EAAE,CACxD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,EACjC,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,IAAyB,EAAE,cAAwB,EAAE,EAAE,IAA8B,EAAE,EAAE,CACxF,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAC9C,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAE5D,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAC3C,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,EAClD,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,OAAO,GAAG,WAAW,CACzB,CAAiC,GAAM,EAAE,KAAe,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,KAAgB,CAAC,EAClG,CAAC,MAAM,CAAC,CAC0B,CAAC;IAErC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;AACvG,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,WAAW,GAAG,aAAa,CAAuB,IAAI,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAqB;IACnE,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAClC,OAAO,aAAa,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,GAA2B,CAAC;AACrC,CAAC;AAkDD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,IAAI,EAAE,OAAO,EACb,KAAK,EACL,WAAW,GAAG,EAAE,EAChB,SAAS,GAAG,IAAI,EAChB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,GAAG,MAAM,EAClB,SAAS,GAAG,MAAM,EAClB,WAAW,GAAG,QAAQ,EACtB,WAAW,GAAG,QAAQ,EACtB,UAAU,GAAG,KAAK,EAClB,YAAY,GAAG,KAAK,EACpB,SAAS,EACT,YAAY,EACZ,YAAY,GACG;IACf,MAAM,UAAU,GAAG,OAAO,CAAC;QACzB,OAAO,CAAC,KAAK;YACX,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;gBAAE,UAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;gBAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAEnG,sBAAsB;IACtB,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACrC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAC1B,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC9B,CAAC;QACD,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,0CAA0C;IAC1C,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEvE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,aAAa,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAC9D,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAC5D,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,EACnD,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,iBAAiB,CAAC,EAC3C,CAAC,SAAS,IAAI,aAAa,CAAC,QAAQ,EAAE;YACpC,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,qBAAqB;YAChC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC;SAC3C,EAAE,OAAO,CAAC,CACZ,CACF,CACF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;IAEjG,OAAO,aAAa,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAC9D,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE;IAC5D,8BAA8B;IAC9B,CAAC,YAAY,IAAI,CAAC,YAAY;QAC5B,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC;QACxB,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5B,sBAAsB;IACtB,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,WAAW,CAAC;IAClE,sBAAsB;IACtB,QAAQ,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE,EACjG,GAAG,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAC5C,aAAa,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,2BAA2B,EAAE,EAAE,GAAG,CAAC,CAC7E,CACF;IACD,8BAA8B;IAC9B,YAAY;QACV,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE;YAC/B,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU;SAC3D,CAAC,CACP,CACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,SAAS,aAAa,CAAC,QAAsB;IAC3C,OAAO,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAC3D,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,EACnD,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAChC,aAAa,CAAC,KAAK,EAAE;QACnB,GAAG,EAAE,IAAI,CAAC,EAAE;QACZ,SAAS,EAAE,GAAG,CAAC,gBAAgB,EAAE,mBAAmB,IAAI,CAAC,MAAM,EAAE,CAAC;KACnE,EACC,aAAa,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,oBAAoB,EAAE,EACvD,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAClD,EACD,aAAa,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE,EACzD,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,CACtB,CACF,CACF,CACF,EACD,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,EACnD,aAAa,CAAC,KAAK,EAAE;QACnB,SAAS,EAAE,sBAAsB;QACjC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,QAAQ,GAAG,GAAG,GAAG,EAAE;KAChD,CAAC,CACH,CACF,CAAC;AACJ,CAAC;AAcD,SAAS,aAAa,CACpB,QAAsB,EACtB,OAAyB,EACzB,MAAoB;IAEpB,OAAO,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAC3D,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,uBAAuB,EAAE,EACzD,CAAC,QAAQ,CAAC,WAAW,IAAI,aAAa,CAAC,QAAQ,EAAE;QAC/C,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE,mCAAmC;QAC9C,QAAQ,EAAE,QAAQ,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,eAAe;QAC5D,OAAO,EAAE,OAAO,CAAC,QAAQ;KAC1B,EAAE,MAAM,CAAC,SAAS,CAAC,CACrB,EACD,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,wBAAwB,EAAE,EAC1D,CAAC,MAAM,CAAC,UAAU,IAAI,aAAa,CAAC,QAAQ,EAAE;QAC5C,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE,qCAAqC;QAChD,QAAQ,EAAE,QAAQ,CAAC,YAAY;QAC/B,OAAO,EAAE,OAAO,CAAC,MAAM;KACxB,EAAE,MAAM,CAAC,WAAW,CAAC,EACtB,aAAa,CAAC,QAAQ,EAAE;QACtB,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE,mCAAmC;QAC9C,QAAQ,EAAE,QAAQ,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,WAAW;QACxD,OAAO,EAAE,OAAO,CAAC,IAAI;KACtB,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAChE,CACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAG9E,SAAS,GAAG,CAAC,GAAG,KAA4C;IAC1D,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daltonr/pathwrite-react",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "React adapter for @daltonr/pathwrite-core — hooks, context provider, and optional <PathShell> default UI.",
@@ -45,7 +45,7 @@
45
45
  "react": ">=18.0.0"
46
46
  },
47
47
  "dependencies": {
48
- "@daltonr/pathwrite-core": "^0.3.0"
48
+ "@daltonr/pathwrite-core": "^0.5.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "react": "^18.3.1",
package/src/index.ts CHANGED
@@ -21,7 +21,18 @@ import {
21
21
  // ---------------------------------------------------------------------------
22
22
 
23
23
  export interface UsePathOptions {
24
- /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
24
+ /**
25
+ * An externally-managed `PathEngine` to subscribe to — for example, the engine
26
+ * returned by `createPersistedEngine()` from `@daltonr/pathwrite-store-http`.
27
+ *
28
+ * When provided:
29
+ * - `usePath` will **not** create its own engine.
30
+ * - The snapshot is seeded immediately from the engine's current state.
31
+ * - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
32
+ * - `PathShell` will skip its own `autoStart` call.
33
+ */
34
+ engine?: PathEngine;
35
+ /** Called for every engine event (stateChanged, completed, cancelled, resumed). The callback ref is kept current — changing it does **not** re-subscribe to the engine. */
25
36
  onEvent?: (event: PathEvent) => void;
26
37
  }
27
38
 
@@ -44,6 +55,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
44
55
  goToStepChecked: (stepId: string) => void;
45
56
  /** Update a single data value; triggers a re-render via stateChanged. When `TData` is specified, `key` and `value` are type-checked against your data shape. */
46
57
  setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
58
+ /**
59
+ * Tear down any active path (without firing hooks) and immediately start the
60
+ * given path fresh. Safe to call whether or not a path is currently active.
61
+ * Use for "Start over" / retry flows without remounting the component.
62
+ */
63
+ restart: (path: PathDefinition<any>, initialData?: PathData) => void;
47
64
  }
48
65
 
49
66
  export type PathProviderProps = PropsWithChildren<{
@@ -56,10 +73,11 @@ export type PathProviderProps = PropsWithChildren<{
56
73
  // ---------------------------------------------------------------------------
57
74
 
58
75
  export function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData> {
59
- // Stable engine instance for the lifetime of the hook
76
+ // Use provided engine or create a stable new one for this hook's lifetime.
77
+ // options.engine must be a stable reference (don't recreate on every render).
60
78
  const engineRef = useRef<PathEngine | null>(null);
61
79
  if (engineRef.current === null) {
62
- engineRef.current = new PathEngine();
80
+ engineRef.current = options?.engine ?? new PathEngine();
63
81
  }
64
82
  const engine = engineRef.current;
65
83
 
@@ -67,8 +85,20 @@ export function usePath<TData extends PathData = PathData>(options?: UsePathOpti
67
85
  const onEventRef = useRef(options?.onEvent);
68
86
  onEventRef.current = options?.onEvent;
69
87
 
70
- // Cached snapshot updated only inside the subscribe callback
88
+ // Seed immediately from existing engine state essential when restoring a
89
+ // persisted path (the engine is already started before usePath is called).
90
+ // We track whether we've seeded to avoid calling engine.snapshot() on every
91
+ // re-render (React evaluates useRef's argument each time).
92
+ const seededRef = useRef(false);
71
93
  const snapshotRef = useRef<PathSnapshot<TData> | null>(null);
94
+ if (!seededRef.current) {
95
+ seededRef.current = true;
96
+ try {
97
+ snapshotRef.current = engine.snapshot() as PathSnapshot<TData> | null;
98
+ } catch {
99
+ snapshotRef.current = null;
100
+ }
101
+ }
72
102
 
73
103
  const subscribe = useCallback(
74
104
  (callback: () => void) =>
@@ -120,7 +150,13 @@ export function usePath<TData extends PathData = PathData>(options?: UsePathOpti
120
150
  [engine]
121
151
  ) as UsePathReturn<TData>["setData"];
122
152
 
123
- return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
153
+ const restart = useCallback(
154
+ (path: PathDefinition<any>, initialData: PathData = {}) =>
155
+ engine.restart(path, initialData),
156
+ [engine]
157
+ );
158
+
159
+ return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, restart };
124
160
  }
125
161
 
126
162
  // ---------------------------------------------------------------------------
@@ -160,6 +196,12 @@ export function usePathContext<TData extends PathData = PathData>(): UsePathRetu
160
196
  export interface PathShellProps {
161
197
  /** The path definition to drive. */
162
198
  path: PathDefinition<any>;
199
+ /**
200
+ * An externally-managed engine — for example, the engine returned by
201
+ * `createPersistedEngine()`. When supplied, `PathShell` will skip its own
202
+ * `start()` call and drive the UI from the provided engine instead.
203
+ */
204
+ engine?: PathEngine;
163
205
  /** Map of step ID → content. The shell renders `steps[snapshot.stepId]` for the current step. */
164
206
  steps: Record<string, ReactNode>;
165
207
  /** Initial data passed to `engine.start()`. */
@@ -172,12 +214,12 @@ export interface PathShellProps {
172
214
  onCancel?: (data: PathData) => void;
173
215
  /** Called for every engine event. */
174
216
  onEvent?: (event: PathEvent) => void;
175
- /** Label for the Back button. Defaults to `"Back"`. */
217
+ /** Label for the Previous button. Defaults to `"Previous"`. */
176
218
  backLabel?: string;
177
219
  /** Label for the Next button. Defaults to `"Next"`. */
178
220
  nextLabel?: string;
179
- /** Label for the Finish button (shown on the last step). Defaults to `"Finish"`. */
180
- finishLabel?: string;
221
+ /** Label for the Complete button (shown on the last step). Defaults to `"Complete"`. */
222
+ completeLabel?: string;
181
223
  /** Label for the Cancel button. Defaults to `"Cancel"`. */
182
224
  cancelLabel?: string;
183
225
  /** If true, hide the Cancel button. Defaults to `false`. */
@@ -199,6 +241,8 @@ export interface PathShellActions {
199
241
  goToStep: (stepId: string) => void;
200
242
  goToStepChecked: (stepId: string) => void;
201
243
  setData: (key: string, value: unknown) => void;
244
+ /** Restart the shell's current path with its current `initialData`. */
245
+ restart: () => void;
202
246
  }
203
247
 
204
248
  /**
@@ -219,15 +263,16 @@ export interface PathShellActions {
219
263
  */
220
264
  export function PathShell({
221
265
  path: pathDef,
266
+ engine: externalEngine,
222
267
  steps,
223
268
  initialData = {},
224
269
  autoStart = true,
225
270
  onComplete,
226
271
  onCancel,
227
272
  onEvent,
228
- backLabel = "Back",
273
+ backLabel = "Previous",
229
274
  nextLabel = "Next",
230
- finishLabel = "Finish",
275
+ completeLabel = "Complete",
231
276
  cancelLabel = "Cancel",
232
277
  hideCancel = false,
233
278
  hideProgress = false,
@@ -236,6 +281,7 @@ export function PathShell({
236
281
  renderFooter,
237
282
  }: PathShellProps): ReactElement {
238
283
  const pathReturn = usePath({
284
+ engine: externalEngine,
239
285
  onEvent(event) {
240
286
  onEvent?.(event);
241
287
  if (event.type === "completed") onComplete?.(event.data);
@@ -243,12 +289,13 @@ export function PathShell({
243
289
  }
244
290
  });
245
291
 
246
- const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData } = pathReturn;
292
+ const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
247
293
 
248
- // Auto-start on mount
294
+ // Auto-start on mount — skipped when an external engine is provided since
295
+ // the caller is responsible for starting it (e.g. via createPersistedEngine).
249
296
  const startedRef = useRef(false);
250
297
  useEffect(() => {
251
- if (autoStart && !startedRef.current) {
298
+ if (autoStart && !startedRef.current && !externalEngine) {
252
299
  startedRef.current = true;
253
300
  start(pathDef, initialData);
254
301
  }
@@ -273,7 +320,10 @@ export function PathShell({
273
320
  );
274
321
  }
275
322
 
276
- const actions: PathShellActions = { next, previous, cancel, goToStep, goToStepChecked, setData };
323
+ const actions: PathShellActions = {
324
+ next, previous, cancel, goToStep, goToStepChecked, setData,
325
+ restart: () => restart(pathDef, initialData)
326
+ };
277
327
 
278
328
  return createElement(PathContext.Provider, { value: pathReturn },
279
329
  createElement("div", { className: cls("pw-shell", className) },
@@ -293,7 +343,7 @@ export function PathShell({
293
343
  renderFooter
294
344
  ? renderFooter(snapshot, actions)
295
345
  : defaultFooter(snapshot, actions, {
296
- backLabel, nextLabel, finishLabel, cancelLabel, hideCancel
346
+ backLabel, nextLabel, completeLabel, cancelLabel, hideCancel
297
347
  })
298
348
  )
299
349
  );
@@ -336,7 +386,7 @@ function defaultHeader(snapshot: PathSnapshot): ReactElement {
336
386
  interface FooterLabels {
337
387
  backLabel: string;
338
388
  nextLabel: string;
339
- finishLabel: string;
389
+ completeLabel: string;
340
390
  cancelLabel: string;
341
391
  hideCancel: boolean;
342
392
  }
@@ -367,7 +417,7 @@ function defaultFooter(
367
417
  className: "pw-shell__btn pw-shell__btn--next",
368
418
  disabled: snapshot.isNavigating || !snapshot.canMoveNext,
369
419
  onClick: actions.next
370
- }, snapshot.isLastStep ? labels.finishLabel : labels.nextLabel)
420
+ }, snapshot.isLastStep ? labels.completeLabel : labels.nextLabel)
371
421
  )
372
422
  );
373
423
  }
@@ -376,7 +426,23 @@ function defaultFooter(
376
426
  // Helpers
377
427
  // ---------------------------------------------------------------------------
378
428
 
379
-
380
429
  function cls(...parts: (string | undefined | false | null)[]): string {
381
430
  return parts.filter(Boolean).join(" ");
382
431
  }
432
+
433
+ // ---------------------------------------------------------------------------
434
+ // Re-export core types for convenience
435
+ // ---------------------------------------------------------------------------
436
+
437
+ export type {
438
+ PathData,
439
+ PathDefinition,
440
+ PathEvent,
441
+ PathSnapshot,
442
+ PathStep,
443
+ PathStepContext,
444
+ SerializedPathState
445
+ } from "@daltonr/pathwrite-core";
446
+
447
+ export { PathEngine } from "@daltonr/pathwrite-core";
448
+