@daltonr/pathwrite-react 0.4.0 → 0.6.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 +72 -5
- package/dist/index.d.ts +33 -8
- package/dist/index.js +58 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +98 -23
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
usePath, // React-specific
|
|
19
19
|
usePathContext, // React-specific
|
|
20
20
|
PathProvider, // React-specific
|
|
21
|
+
PathEngine, // Re-exported from core (value + type)
|
|
21
22
|
PathData, // Re-exported from core
|
|
22
23
|
PathDefinition, // Re-exported from core
|
|
23
24
|
PathEvent, // Re-exported from core
|
|
@@ -54,7 +55,7 @@ function MyPathHost() {
|
|
|
54
55
|
<p>Step {snapshot.stepIndex + 1} of {snapshot.stepCount}</p>
|
|
55
56
|
<button onClick={previous} disabled={snapshot.isNavigating}>Back</button>
|
|
56
57
|
<button onClick={next} disabled={snapshot.isNavigating}>
|
|
57
|
-
{snapshot.isLastStep ? "
|
|
58
|
+
{snapshot.isLastStep ? "Complete" : "Next"}
|
|
58
59
|
</button>
|
|
59
60
|
<button onClick={cancel}>Cancel</button>
|
|
60
61
|
</>
|
|
@@ -106,6 +107,7 @@ function NavButtons() {
|
|
|
106
107
|
|
|
107
108
|
| Option | Type | Description |
|
|
108
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. |
|
|
109
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. |
|
|
110
112
|
|
|
111
113
|
### Return value
|
|
@@ -187,23 +189,55 @@ import { PathShell } from "@daltonr/pathwrite-react";
|
|
|
187
189
|
/>
|
|
188
190
|
```
|
|
189
191
|
|
|
192
|
+
> **⚠️ Important: `steps` Keys Must Match Step IDs**
|
|
193
|
+
>
|
|
194
|
+
> The keys in the `steps` object **must exactly match** the step IDs from your path definition:
|
|
195
|
+
>
|
|
196
|
+
> ```typescript
|
|
197
|
+
> const myPath: PathDefinition = {
|
|
198
|
+
> id: 'signup',
|
|
199
|
+
> steps: [
|
|
200
|
+
> { id: 'details' }, // ← Step ID
|
|
201
|
+
> { id: 'review' } // ← Step ID
|
|
202
|
+
> ]
|
|
203
|
+
> };
|
|
204
|
+
> ```
|
|
205
|
+
>
|
|
206
|
+
> ```tsx
|
|
207
|
+
> <PathShell
|
|
208
|
+
> path={myPath}
|
|
209
|
+
> steps={{
|
|
210
|
+
> details: <DetailsForm />, // ✅ Matches "details" step
|
|
211
|
+
> review: <ReviewPanel />, // ✅ Matches "review" step
|
|
212
|
+
> foo: <FooPanel /> // ❌ No step with id "foo"
|
|
213
|
+
> }}
|
|
214
|
+
> />
|
|
215
|
+
> ```
|
|
216
|
+
>
|
|
217
|
+
> If a key doesn't match any step ID, PathShell will render:
|
|
218
|
+
> **`No content for step "foo"`**
|
|
219
|
+
>
|
|
220
|
+
> **💡 Tip:** Use your IDE's "Go to Definition" on the step ID in your path definition, then copy-paste the exact string when creating the `steps` object. This ensures perfect matching and avoids typos.
|
|
221
|
+
|
|
190
222
|
### Props
|
|
191
223
|
|
|
192
224
|
| Prop | Type | Default | Description |
|
|
193
225
|
|------|------|---------|-------------|
|
|
194
226
|
| `path` | `PathDefinition` | *required* | The path to run. |
|
|
227
|
+
| `engine` | `PathEngine` | — | An externally-managed engine. When provided, `PathShell` skips its own `start()` and drives the UI from this engine. |
|
|
195
228
|
| `steps` | `Record<string, ReactNode>` | *required* | Map of step ID → content to render. |
|
|
196
229
|
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
|
|
197
230
|
| `autoStart` | `boolean` | `true` | Start the path automatically on mount. |
|
|
198
231
|
| `onComplete` | `(data: PathData) => void` | — | Called when the path completes. |
|
|
199
232
|
| `onCancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
|
|
200
233
|
| `onEvent` | `(event: PathEvent) => void` | — | Called for every engine event. |
|
|
201
|
-
| `backLabel` | `string` | `"
|
|
234
|
+
| `backLabel` | `string` | `"Previous"` | Previous button label. |
|
|
202
235
|
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
203
|
-
| `
|
|
236
|
+
| `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
|
|
204
237
|
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
205
238
|
| `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
|
|
206
|
-
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. |
|
|
239
|
+
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. Also hidden automatically for single-step top-level paths. |
|
|
240
|
+
| `footerLayout` | `"wizard" \| "form" \| "auto"` | `"auto"` | Footer button layout. `"auto"` uses `"form"` for single-step top-level paths, `"wizard"` otherwise. `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back button. |
|
|
207
241
|
| `className` | `string` | — | Extra CSS class on the root element. |
|
|
208
242
|
| `renderHeader` | `(snapshot) => ReactNode` | — | Render prop to replace the progress header. |
|
|
209
243
|
| `renderFooter` | `(snapshot, actions) => ReactNode` | — | Render prop to replace the navigation footer. |
|
|
@@ -223,7 +257,7 @@ Use `renderHeader` and `renderFooter` to replace the built-in progress bar or na
|
|
|
223
257
|
<div>
|
|
224
258
|
<button onClick={actions.previous} disabled={snapshot.isFirstStep}>Back</button>
|
|
225
259
|
<button onClick={actions.next} disabled={!snapshot.canMoveNext}>
|
|
226
|
-
{snapshot.isLastStep ? "
|
|
260
|
+
{snapshot.isLastStep ? "Complete" : "Next"}
|
|
227
261
|
</button>
|
|
228
262
|
</div>
|
|
229
263
|
)}
|
|
@@ -232,6 +266,39 @@ Use `renderHeader` and `renderFooter` to replace the built-in progress bar or na
|
|
|
232
266
|
|
|
233
267
|
`PathShellActions` contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`.
|
|
234
268
|
|
|
269
|
+
### Resetting the path
|
|
270
|
+
|
|
271
|
+
Use the `key` prop to reset `<PathShell>` to step 1. Changing `key` forces React to discard the old component and mount a fresh one — this is idiomatic React and requires no new API:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
const [formKey, setFormKey] = useState(0);
|
|
275
|
+
|
|
276
|
+
<PathShell
|
|
277
|
+
key={formKey}
|
|
278
|
+
path={myPath}
|
|
279
|
+
initialData={{ name: "" }}
|
|
280
|
+
onComplete={handleDone}
|
|
281
|
+
steps={{ details: <DetailsForm /> }}
|
|
282
|
+
/>
|
|
283
|
+
|
|
284
|
+
<button onClick={() => setFormKey(k => k + 1)}>Try Again</button>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Incrementing `formKey` discards the old shell and mounts a completely fresh one — path engine, child component state, and DOM are all reset.
|
|
288
|
+
|
|
289
|
+
If your "Try Again" button is inside the success/cancelled panel you conditionally render after completion, the pattern is even simpler:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
const [isActive, setIsActive] = useState(true);
|
|
293
|
+
|
|
294
|
+
{isActive
|
|
295
|
+
? <PathShell path={myPath} onComplete={() => setIsActive(false)} steps={...} />
|
|
296
|
+
: <SuccessPanel onRetry={() => setIsActive(true)} />
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
React function components have no instance, so there is no `ref.restart()` method. The `key` prop achieves the same result and is the React-idiomatic way to reset any component tree.
|
|
301
|
+
|
|
235
302
|
### Context sharing
|
|
236
303
|
|
|
237
304
|
`<PathShell>` provides a path context automatically — step components rendered inside it can call `usePathContext()` without a separate `<PathProvider>`:
|
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
|
-
/**
|
|
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> {
|
|
@@ -51,6 +62,12 @@ export declare function usePathContext<TData extends PathData = PathData>(): Use
|
|
|
51
62
|
export interface PathShellProps {
|
|
52
63
|
/** The path definition to drive. */
|
|
53
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;
|
|
54
71
|
/** Map of step ID → content. The shell renders `steps[snapshot.stepId]` for the current step. */
|
|
55
72
|
steps: Record<string, ReactNode>;
|
|
56
73
|
/** Initial data passed to `engine.start()`. */
|
|
@@ -63,18 +80,25 @@ export interface PathShellProps {
|
|
|
63
80
|
onCancel?: (data: PathData) => void;
|
|
64
81
|
/** Called for every engine event. */
|
|
65
82
|
onEvent?: (event: PathEvent) => void;
|
|
66
|
-
/** Label for the
|
|
83
|
+
/** Label for the Previous button. Defaults to `"Previous"`. */
|
|
67
84
|
backLabel?: string;
|
|
68
85
|
/** Label for the Next button. Defaults to `"Next"`. */
|
|
69
86
|
nextLabel?: string;
|
|
70
|
-
/** Label for the
|
|
71
|
-
|
|
87
|
+
/** Label for the Complete button (shown on the last step). Defaults to `"Complete"`. */
|
|
88
|
+
completeLabel?: string;
|
|
72
89
|
/** Label for the Cancel button. Defaults to `"Cancel"`. */
|
|
73
90
|
cancelLabel?: string;
|
|
74
91
|
/** If true, hide the Cancel button. Defaults to `false`. */
|
|
75
92
|
hideCancel?: boolean;
|
|
76
|
-
/** If true, hide the progress indicator. Defaults to `false`. */
|
|
93
|
+
/** If true, hide the progress indicator. Also hidden automatically when the path has only one step. Defaults to `false`. */
|
|
77
94
|
hideProgress?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Footer layout mode:
|
|
97
|
+
* - `"auto"` (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
|
|
98
|
+
* - `"wizard"`: Back button on left, Cancel and Submit together on right.
|
|
99
|
+
* - `"form"`: Cancel on left, Submit alone on right. Back button never shown.
|
|
100
|
+
*/
|
|
101
|
+
footerLayout?: "wizard" | "form" | "auto";
|
|
78
102
|
/** Optional extra CSS class on the root element. */
|
|
79
103
|
className?: string;
|
|
80
104
|
/** Render prop to replace the entire header (progress area). Receives the snapshot. */
|
|
@@ -108,5 +132,6 @@ export interface PathShellActions {
|
|
|
108
132
|
* />
|
|
109
133
|
* ```
|
|
110
134
|
*/
|
|
111
|
-
export declare function PathShell({ path: pathDef, steps, initialData, autoStart, onComplete, onCancel, onEvent, backLabel, nextLabel,
|
|
112
|
-
export type { PathData, PathDefinition, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
|
|
135
|
+
export declare function PathShell({ path: pathDef, engine: externalEngine, steps, initialData, autoStart, onComplete, onCancel, onEvent, backLabel, nextLabel, completeLabel, cancelLabel, hideCancel, hideProgress, footerLayout, className, renderHeader, renderFooter, }: PathShellProps): ReactElement;
|
|
136
|
+
export type { PathData, FieldErrors, PathDefinition, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
|
|
137
|
+
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
|
-
//
|
|
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
|
-
//
|
|
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;
|
|
@@ -65,6 +79,14 @@ export function usePathContext() {
|
|
|
65
79
|
}
|
|
66
80
|
return ctx;
|
|
67
81
|
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Helpers
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/** Converts a camelCase or lowercase field key to a display label.
|
|
86
|
+
* e.g. "firstName" → "First Name", "email" → "Email" */
|
|
87
|
+
function formatFieldKey(key) {
|
|
88
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
89
|
+
}
|
|
68
90
|
/**
|
|
69
91
|
* Default UI shell that renders a progress indicator, step content, and navigation
|
|
70
92
|
* buttons. Pass a `steps` map to define per-step content.
|
|
@@ -81,8 +103,9 @@ export function usePathContext() {
|
|
|
81
103
|
* />
|
|
82
104
|
* ```
|
|
83
105
|
*/
|
|
84
|
-
export function PathShell({ path: pathDef, steps, initialData = {}, autoStart = true, onComplete, onCancel, onEvent, backLabel = "
|
|
106
|
+
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, footerLayout = "auto", className, renderHeader, renderFooter, }) {
|
|
85
107
|
const pathReturn = usePath({
|
|
108
|
+
engine: externalEngine,
|
|
86
109
|
onEvent(event) {
|
|
87
110
|
onEvent?.(event);
|
|
88
111
|
if (event.type === "completed")
|
|
@@ -92,10 +115,11 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
|
|
|
92
115
|
}
|
|
93
116
|
});
|
|
94
117
|
const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
|
|
95
|
-
// Auto-start on mount
|
|
118
|
+
// Auto-start on mount — skipped when an external engine is provided since
|
|
119
|
+
// the caller is responsible for starting it (e.g. via createPersistedEngine).
|
|
96
120
|
const startedRef = useRef(false);
|
|
97
121
|
useEffect(() => {
|
|
98
|
-
if (autoStart && !startedRef.current) {
|
|
122
|
+
if (autoStart && !startedRef.current && !externalEngine) {
|
|
99
123
|
startedRef.current = true;
|
|
100
124
|
start(pathDef, initialData);
|
|
101
125
|
}
|
|
@@ -118,16 +142,16 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
|
|
|
118
142
|
// Header — progress indicator
|
|
119
143
|
!hideProgress && (renderHeader
|
|
120
144
|
? renderHeader(snapshot)
|
|
121
|
-
: defaultHeader(snapshot)),
|
|
145
|
+
: (snapshot.stepCount > 1 || snapshot.nestingLevel > 0) && defaultHeader(snapshot)),
|
|
122
146
|
// Body — step content
|
|
123
147
|
createElement("div", { className: "pw-shell__body" }, stepContent),
|
|
124
|
-
// Validation messages
|
|
125
|
-
snapshot.
|
|
148
|
+
// Validation messages — labeled by field name
|
|
149
|
+
snapshot.hasAttemptedNext && Object.keys(snapshot.fieldMessages).length > 0 && createElement("ul", { className: "pw-shell__validation" }, ...Object.entries(snapshot.fieldMessages).map(([key, msg]) => createElement("li", { key, className: "pw-shell__validation-item" }, key !== "_" && createElement("span", { className: "pw-shell__validation-label" }, formatFieldKey(key)), msg))),
|
|
126
150
|
// Footer — navigation buttons
|
|
127
151
|
renderFooter
|
|
128
152
|
? renderFooter(snapshot, actions)
|
|
129
153
|
: defaultFooter(snapshot, actions, {
|
|
130
|
-
backLabel, nextLabel,
|
|
154
|
+
backLabel, nextLabel, completeLabel, cancelLabel, hideCancel, footerLayout
|
|
131
155
|
})));
|
|
132
156
|
}
|
|
133
157
|
// ---------------------------------------------------------------------------
|
|
@@ -143,22 +167,40 @@ function defaultHeader(snapshot) {
|
|
|
143
167
|
})));
|
|
144
168
|
}
|
|
145
169
|
function defaultFooter(snapshot, actions, labels) {
|
|
146
|
-
|
|
170
|
+
// Auto-detect layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
171
|
+
const resolvedLayout = labels.footerLayout === "auto"
|
|
172
|
+
? (snapshot.stepCount === 1 && snapshot.nestingLevel === 0 ? "form" : "wizard")
|
|
173
|
+
: labels.footerLayout;
|
|
174
|
+
const isFormMode = resolvedLayout === "form";
|
|
175
|
+
return createElement("div", { className: "pw-shell__footer" }, createElement("div", { className: "pw-shell__footer-left" },
|
|
176
|
+
// Form mode: Cancel on the left
|
|
177
|
+
isFormMode && !labels.hideCancel && createElement("button", {
|
|
178
|
+
type: "button",
|
|
179
|
+
className: "pw-shell__btn pw-shell__btn--cancel",
|
|
180
|
+
disabled: snapshot.isNavigating,
|
|
181
|
+
onClick: actions.cancel
|
|
182
|
+
}, labels.cancelLabel),
|
|
183
|
+
// Wizard mode: Back on the left
|
|
184
|
+
!isFormMode && !snapshot.isFirstStep && createElement("button", {
|
|
147
185
|
type: "button",
|
|
148
186
|
className: "pw-shell__btn pw-shell__btn--back",
|
|
149
187
|
disabled: snapshot.isNavigating || !snapshot.canMovePrevious,
|
|
150
188
|
onClick: actions.previous
|
|
151
|
-
}, labels.backLabel)), createElement("div", { className: "pw-shell__footer-right" },
|
|
189
|
+
}, labels.backLabel)), createElement("div", { className: "pw-shell__footer-right" },
|
|
190
|
+
// Wizard mode: Cancel on the right
|
|
191
|
+
!isFormMode && !labels.hideCancel && createElement("button", {
|
|
152
192
|
type: "button",
|
|
153
193
|
className: "pw-shell__btn pw-shell__btn--cancel",
|
|
154
194
|
disabled: snapshot.isNavigating,
|
|
155
195
|
onClick: actions.cancel
|
|
156
|
-
}, labels.cancelLabel),
|
|
196
|
+
}, labels.cancelLabel),
|
|
197
|
+
// Both modes: Submit on the right
|
|
198
|
+
createElement("button", {
|
|
157
199
|
type: "button",
|
|
158
200
|
className: "pw-shell__btn pw-shell__btn--next",
|
|
159
|
-
disabled: snapshot.isNavigating
|
|
201
|
+
disabled: snapshot.isNavigating,
|
|
160
202
|
onClick: actions.next
|
|
161
|
-
}, snapshot.isLastStep ? labels.
|
|
203
|
+
}, snapshot.isLastStep ? labels.completeLabel : labels.nextLabel)));
|
|
162
204
|
}
|
|
163
205
|
// ---------------------------------------------------------------------------
|
|
164
206
|
// Helpers
|
|
@@ -166,4 +208,5 @@ function defaultFooter(snapshot, actions, labels) {
|
|
|
166
208
|
function cls(...parts) {
|
|
167
209
|
return parts.filter(Boolean).join(" ");
|
|
168
210
|
}
|
|
211
|
+
export { PathEngine } from "@daltonr/pathwrite-core";
|
|
169
212
|
//# 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;
|
|
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
|
+
"version": "0.6.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.",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"import": "./dist/index.js"
|
|
28
28
|
},
|
|
29
|
-
"./styles.css": "./dist/index.css"
|
|
29
|
+
"./styles.css": "./dist/index.css",
|
|
30
|
+
"./dist/index.css": "./dist/index.css"
|
|
30
31
|
},
|
|
31
32
|
"main": "dist/index.js",
|
|
32
33
|
"types": "dist/index.d.ts",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"react": ">=18.0.0"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@daltonr/pathwrite-core": "^0.
|
|
49
|
+
"@daltonr/pathwrite-core": "^0.6.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"react": "^18.3.1",
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,18 @@ import {
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
|
|
23
23
|
export interface UsePathOptions {
|
|
24
|
-
/**
|
|
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
|
|
|
@@ -62,10 +73,11 @@ export type PathProviderProps = PropsWithChildren<{
|
|
|
62
73
|
// ---------------------------------------------------------------------------
|
|
63
74
|
|
|
64
75
|
export function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData> {
|
|
65
|
-
//
|
|
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).
|
|
66
78
|
const engineRef = useRef<PathEngine | null>(null);
|
|
67
79
|
if (engineRef.current === null) {
|
|
68
|
-
engineRef.current = new PathEngine();
|
|
80
|
+
engineRef.current = options?.engine ?? new PathEngine();
|
|
69
81
|
}
|
|
70
82
|
const engine = engineRef.current;
|
|
71
83
|
|
|
@@ -73,8 +85,20 @@ export function usePath<TData extends PathData = PathData>(options?: UsePathOpti
|
|
|
73
85
|
const onEventRef = useRef(options?.onEvent);
|
|
74
86
|
onEventRef.current = options?.onEvent;
|
|
75
87
|
|
|
76
|
-
//
|
|
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);
|
|
77
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
|
+
}
|
|
78
102
|
|
|
79
103
|
const subscribe = useCallback(
|
|
80
104
|
(callback: () => void) =>
|
|
@@ -165,6 +189,16 @@ export function usePathContext<TData extends PathData = PathData>(): UsePathRetu
|
|
|
165
189
|
return ctx as UsePathReturn<TData>;
|
|
166
190
|
}
|
|
167
191
|
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Helpers
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
/** Converts a camelCase or lowercase field key to a display label.
|
|
197
|
+
* e.g. "firstName" → "First Name", "email" → "Email" */
|
|
198
|
+
function formatFieldKey(key: string): string {
|
|
199
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, c => c.toUpperCase()).trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
168
202
|
// ---------------------------------------------------------------------------
|
|
169
203
|
// Default UI — PathShell
|
|
170
204
|
// ---------------------------------------------------------------------------
|
|
@@ -172,6 +206,12 @@ export function usePathContext<TData extends PathData = PathData>(): UsePathRetu
|
|
|
172
206
|
export interface PathShellProps {
|
|
173
207
|
/** The path definition to drive. */
|
|
174
208
|
path: PathDefinition<any>;
|
|
209
|
+
/**
|
|
210
|
+
* An externally-managed engine — for example, the engine returned by
|
|
211
|
+
* `createPersistedEngine()`. When supplied, `PathShell` will skip its own
|
|
212
|
+
* `start()` call and drive the UI from the provided engine instead.
|
|
213
|
+
*/
|
|
214
|
+
engine?: PathEngine;
|
|
175
215
|
/** Map of step ID → content. The shell renders `steps[snapshot.stepId]` for the current step. */
|
|
176
216
|
steps: Record<string, ReactNode>;
|
|
177
217
|
/** Initial data passed to `engine.start()`. */
|
|
@@ -184,18 +224,25 @@ export interface PathShellProps {
|
|
|
184
224
|
onCancel?: (data: PathData) => void;
|
|
185
225
|
/** Called for every engine event. */
|
|
186
226
|
onEvent?: (event: PathEvent) => void;
|
|
187
|
-
/** Label for the
|
|
227
|
+
/** Label for the Previous button. Defaults to `"Previous"`. */
|
|
188
228
|
backLabel?: string;
|
|
189
229
|
/** Label for the Next button. Defaults to `"Next"`. */
|
|
190
230
|
nextLabel?: string;
|
|
191
|
-
/** Label for the
|
|
192
|
-
|
|
231
|
+
/** Label for the Complete button (shown on the last step). Defaults to `"Complete"`. */
|
|
232
|
+
completeLabel?: string;
|
|
193
233
|
/** Label for the Cancel button. Defaults to `"Cancel"`. */
|
|
194
234
|
cancelLabel?: string;
|
|
195
235
|
/** If true, hide the Cancel button. Defaults to `false`. */
|
|
196
236
|
hideCancel?: boolean;
|
|
197
|
-
/** If true, hide the progress indicator. Defaults to `false`. */
|
|
237
|
+
/** If true, hide the progress indicator. Also hidden automatically when the path has only one step. Defaults to `false`. */
|
|
198
238
|
hideProgress?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Footer layout mode:
|
|
241
|
+
* - `"auto"` (default): Uses "form" for single-step top-level paths, "wizard" otherwise.
|
|
242
|
+
* - `"wizard"`: Back button on left, Cancel and Submit together on right.
|
|
243
|
+
* - `"form"`: Cancel on left, Submit alone on right. Back button never shown.
|
|
244
|
+
*/
|
|
245
|
+
footerLayout?: "wizard" | "form" | "auto";
|
|
199
246
|
/** Optional extra CSS class on the root element. */
|
|
200
247
|
className?: string;
|
|
201
248
|
/** Render prop to replace the entire header (progress area). Receives the snapshot. */
|
|
@@ -233,23 +280,26 @@ export interface PathShellActions {
|
|
|
233
280
|
*/
|
|
234
281
|
export function PathShell({
|
|
235
282
|
path: pathDef,
|
|
283
|
+
engine: externalEngine,
|
|
236
284
|
steps,
|
|
237
285
|
initialData = {},
|
|
238
286
|
autoStart = true,
|
|
239
287
|
onComplete,
|
|
240
288
|
onCancel,
|
|
241
289
|
onEvent,
|
|
242
|
-
backLabel = "
|
|
290
|
+
backLabel = "Previous",
|
|
243
291
|
nextLabel = "Next",
|
|
244
|
-
|
|
292
|
+
completeLabel = "Complete",
|
|
245
293
|
cancelLabel = "Cancel",
|
|
246
294
|
hideCancel = false,
|
|
247
295
|
hideProgress = false,
|
|
296
|
+
footerLayout = "auto",
|
|
248
297
|
className,
|
|
249
298
|
renderHeader,
|
|
250
299
|
renderFooter,
|
|
251
300
|
}: PathShellProps): ReactElement {
|
|
252
301
|
const pathReturn = usePath({
|
|
302
|
+
engine: externalEngine,
|
|
253
303
|
onEvent(event) {
|
|
254
304
|
onEvent?.(event);
|
|
255
305
|
if (event.type === "completed") onComplete?.(event.data);
|
|
@@ -259,10 +309,11 @@ export function PathShell({
|
|
|
259
309
|
|
|
260
310
|
const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
|
|
261
311
|
|
|
262
|
-
// Auto-start on mount
|
|
312
|
+
// Auto-start on mount — skipped when an external engine is provided since
|
|
313
|
+
// the caller is responsible for starting it (e.g. via createPersistedEngine).
|
|
263
314
|
const startedRef = useRef(false);
|
|
264
315
|
useEffect(() => {
|
|
265
|
-
if (autoStart && !startedRef.current) {
|
|
316
|
+
if (autoStart && !startedRef.current && !externalEngine) {
|
|
266
317
|
startedRef.current = true;
|
|
267
318
|
start(pathDef, initialData);
|
|
268
319
|
}
|
|
@@ -297,20 +348,23 @@ export function PathShell({
|
|
|
297
348
|
// Header — progress indicator
|
|
298
349
|
!hideProgress && (renderHeader
|
|
299
350
|
? renderHeader(snapshot)
|
|
300
|
-
: defaultHeader(snapshot)),
|
|
351
|
+
: (snapshot.stepCount > 1 || snapshot.nestingLevel > 0) && defaultHeader(snapshot)),
|
|
301
352
|
// Body — step content
|
|
302
353
|
createElement("div", { className: "pw-shell__body" }, stepContent),
|
|
303
|
-
// Validation messages
|
|
304
|
-
snapshot.
|
|
305
|
-
...snapshot.
|
|
306
|
-
createElement("li", { key
|
|
354
|
+
// Validation messages — labeled by field name
|
|
355
|
+
snapshot.hasAttemptedNext && Object.keys(snapshot.fieldMessages).length > 0 && createElement("ul", { className: "pw-shell__validation" },
|
|
356
|
+
...Object.entries(snapshot.fieldMessages).map(([key, msg]) =>
|
|
357
|
+
createElement("li", { key, className: "pw-shell__validation-item" },
|
|
358
|
+
key !== "_" && createElement("span", { className: "pw-shell__validation-label" }, formatFieldKey(key)),
|
|
359
|
+
msg
|
|
360
|
+
)
|
|
307
361
|
)
|
|
308
362
|
),
|
|
309
363
|
// Footer — navigation buttons
|
|
310
364
|
renderFooter
|
|
311
365
|
? renderFooter(snapshot, actions)
|
|
312
366
|
: defaultFooter(snapshot, actions, {
|
|
313
|
-
backLabel, nextLabel,
|
|
367
|
+
backLabel, nextLabel, completeLabel, cancelLabel, hideCancel, footerLayout
|
|
314
368
|
})
|
|
315
369
|
)
|
|
316
370
|
);
|
|
@@ -353,9 +407,10 @@ function defaultHeader(snapshot: PathSnapshot): ReactElement {
|
|
|
353
407
|
interface FooterLabels {
|
|
354
408
|
backLabel: string;
|
|
355
409
|
nextLabel: string;
|
|
356
|
-
|
|
410
|
+
completeLabel: string;
|
|
357
411
|
cancelLabel: string;
|
|
358
412
|
hideCancel: boolean;
|
|
413
|
+
footerLayout: "wizard" | "form" | "auto";
|
|
359
414
|
}
|
|
360
415
|
|
|
361
416
|
function defaultFooter(
|
|
@@ -363,9 +418,24 @@ function defaultFooter(
|
|
|
363
418
|
actions: PathShellActions,
|
|
364
419
|
labels: FooterLabels
|
|
365
420
|
): ReactElement {
|
|
421
|
+
// Auto-detect layout: single-step top-level paths use "form", everything else uses "wizard"
|
|
422
|
+
const resolvedLayout = labels.footerLayout === "auto"
|
|
423
|
+
? (snapshot.stepCount === 1 && snapshot.nestingLevel === 0 ? "form" : "wizard")
|
|
424
|
+
: labels.footerLayout;
|
|
425
|
+
|
|
426
|
+
const isFormMode = resolvedLayout === "form";
|
|
427
|
+
|
|
366
428
|
return createElement("div", { className: "pw-shell__footer" },
|
|
367
429
|
createElement("div", { className: "pw-shell__footer-left" },
|
|
368
|
-
|
|
430
|
+
// Form mode: Cancel on the left
|
|
431
|
+
isFormMode && !labels.hideCancel && createElement("button", {
|
|
432
|
+
type: "button",
|
|
433
|
+
className: "pw-shell__btn pw-shell__btn--cancel",
|
|
434
|
+
disabled: snapshot.isNavigating,
|
|
435
|
+
onClick: actions.cancel
|
|
436
|
+
}, labels.cancelLabel),
|
|
437
|
+
// Wizard mode: Back on the left
|
|
438
|
+
!isFormMode && !snapshot.isFirstStep && createElement("button", {
|
|
369
439
|
type: "button",
|
|
370
440
|
className: "pw-shell__btn pw-shell__btn--back",
|
|
371
441
|
disabled: snapshot.isNavigating || !snapshot.canMovePrevious,
|
|
@@ -373,18 +443,20 @@ function defaultFooter(
|
|
|
373
443
|
}, labels.backLabel)
|
|
374
444
|
),
|
|
375
445
|
createElement("div", { className: "pw-shell__footer-right" },
|
|
376
|
-
|
|
446
|
+
// Wizard mode: Cancel on the right
|
|
447
|
+
!isFormMode && !labels.hideCancel && createElement("button", {
|
|
377
448
|
type: "button",
|
|
378
449
|
className: "pw-shell__btn pw-shell__btn--cancel",
|
|
379
450
|
disabled: snapshot.isNavigating,
|
|
380
451
|
onClick: actions.cancel
|
|
381
452
|
}, labels.cancelLabel),
|
|
453
|
+
// Both modes: Submit on the right
|
|
382
454
|
createElement("button", {
|
|
383
455
|
type: "button",
|
|
384
456
|
className: "pw-shell__btn pw-shell__btn--next",
|
|
385
|
-
disabled: snapshot.isNavigating
|
|
457
|
+
disabled: snapshot.isNavigating,
|
|
386
458
|
onClick: actions.next
|
|
387
|
-
}, snapshot.isLastStep ? labels.
|
|
459
|
+
}, snapshot.isLastStep ? labels.completeLabel : labels.nextLabel)
|
|
388
460
|
)
|
|
389
461
|
);
|
|
390
462
|
}
|
|
@@ -403,6 +475,7 @@ function cls(...parts: (string | undefined | false | null)[]): string {
|
|
|
403
475
|
|
|
404
476
|
export type {
|
|
405
477
|
PathData,
|
|
478
|
+
FieldErrors,
|
|
406
479
|
PathDefinition,
|
|
407
480
|
PathEvent,
|
|
408
481
|
PathSnapshot,
|
|
@@ -411,3 +484,5 @@ export type {
|
|
|
411
484
|
SerializedPathState
|
|
412
485
|
} from "@daltonr/pathwrite-core";
|
|
413
486
|
|
|
487
|
+
export { PathEngine } from "@daltonr/pathwrite-core";
|
|
488
|
+
|