@daltonr/pathwrite-react 0.2.1 → 0.4.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 +352 -11
- package/dist/index.d.ts +13 -3
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +41 -9
package/README.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
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
|
+
PathData, // Re-exported from core
|
|
22
|
+
PathDefinition, // Re-exported from core
|
|
23
|
+
PathEvent, // Re-exported from core
|
|
24
|
+
PathSnapshot, // Re-exported from core
|
|
25
|
+
PathStep, // Re-exported from core
|
|
26
|
+
PathStepContext, // Re-exported from core
|
|
27
|
+
SerializedPathState // Re-exported from core
|
|
28
|
+
} from "@daltonr/pathwrite-react";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
5
33
|
## Setup
|
|
6
34
|
|
|
7
35
|
### Option A — `usePath` hook (component-scoped)
|
|
@@ -86,13 +114,14 @@ function NavButtons() {
|
|
|
86
114
|
|----------|------|-------------|
|
|
87
115
|
| `snapshot` | `PathSnapshot \| null` | Current snapshot. `null` when no path is active. Triggers a React re-render on change. |
|
|
88
116
|
| `start(definition, data?)` | `function` | Start or re-start a path. |
|
|
89
|
-
| `startSubPath(definition, data?)` | `function` | Push a sub-path. Requires an active path. |
|
|
117
|
+
| `startSubPath(definition, data?, meta?)` | `function` | Push a sub-path. Requires an active path. `meta` is returned unchanged to `onSubPathComplete` / `onSubPathCancel`. |
|
|
90
118
|
| `next()` | `function` | Advance one step. Completes the path on the last step. |
|
|
91
119
|
| `previous()` | `function` | Go back one step. No-op when already on the first step of a top-level path. |
|
|
92
120
|
| `cancel()` | `function` | Cancel the active path (or sub-path). |
|
|
93
121
|
| `goToStep(stepId)` | `function` | Jump directly to a step by ID. Calls `onLeave` / `onEnter` but bypasses guards and `shouldSkip`. |
|
|
94
122
|
| `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
123
|
| `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. |
|
|
124
|
+
| `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
125
|
|
|
97
126
|
All action callbacks are **referentially stable** — safe to pass as props or include in dependency arrays without causing unnecessary re-renders.
|
|
98
127
|
|
|
@@ -138,9 +167,74 @@ These update automatically when data changes (e.g. after `setData`). Async guard
|
|
|
138
167
|
|
|
139
168
|
Wrap a subtree in `<PathProvider>` so multiple components share the same engine instance. Consume with `usePathContext()`.
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Default UI — `PathShell`
|
|
173
|
+
|
|
174
|
+
`<PathShell>` is a ready-made shell component that renders a progress indicator, step content, and navigation buttons. Pass a `steps` map to define per-step content.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { PathShell } from "@daltonr/pathwrite-react";
|
|
178
|
+
|
|
179
|
+
<PathShell
|
|
180
|
+
path={myPath}
|
|
181
|
+
initialData={{ name: "" }}
|
|
182
|
+
onComplete={(data) => console.log("Done!", data)}
|
|
183
|
+
steps={{
|
|
184
|
+
details: <DetailsForm />,
|
|
185
|
+
review: <ReviewPanel />,
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Props
|
|
191
|
+
|
|
192
|
+
| Prop | Type | Default | Description |
|
|
193
|
+
|------|------|---------|-------------|
|
|
194
|
+
| `path` | `PathDefinition` | *required* | The path to run. |
|
|
195
|
+
| `steps` | `Record<string, ReactNode>` | *required* | Map of step ID → content to render. |
|
|
196
|
+
| `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
|
|
197
|
+
| `autoStart` | `boolean` | `true` | Start the path automatically on mount. |
|
|
198
|
+
| `onComplete` | `(data: PathData) => void` | — | Called when the path completes. |
|
|
199
|
+
| `onCancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
|
|
200
|
+
| `onEvent` | `(event: PathEvent) => void` | — | Called for every engine event. |
|
|
201
|
+
| `backLabel` | `string` | `"Back"` | Back button label. |
|
|
202
|
+
| `nextLabel` | `string` | `"Next"` | Next button label. |
|
|
203
|
+
| `finishLabel` | `string` | `"Finish"` | Finish button label (last step). |
|
|
204
|
+
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
205
|
+
| `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
|
|
206
|
+
| `hideProgress` | `boolean` | `false` | Hide the progress indicator. |
|
|
207
|
+
| `className` | `string` | — | Extra CSS class on the root element. |
|
|
208
|
+
| `renderHeader` | `(snapshot) => ReactNode` | — | Render prop to replace the progress header. |
|
|
209
|
+
| `renderFooter` | `(snapshot, actions) => ReactNode` | — | Render prop to replace the navigation footer. |
|
|
210
|
+
|
|
211
|
+
### Customising the header and footer
|
|
212
|
+
|
|
213
|
+
Use `renderHeader` and `renderFooter` to replace the built-in progress bar or navigation buttons with your own UI. Both receive the current `PathSnapshot`; `renderFooter` also receives a `PathShellActions` object with all navigation callbacks.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<PathShell
|
|
217
|
+
path={myPath}
|
|
218
|
+
steps={{ details: <DetailsForm />, review: <ReviewPanel /> }}
|
|
219
|
+
renderHeader={(snapshot) => (
|
|
220
|
+
<p>{snapshot.stepIndex + 1} / {snapshot.stepCount} — {snapshot.stepTitle}</p>
|
|
221
|
+
)}
|
|
222
|
+
renderFooter={(snapshot, actions) => (
|
|
223
|
+
<div>
|
|
224
|
+
<button onClick={actions.previous} disabled={snapshot.isFirstStep}>Back</button>
|
|
225
|
+
<button onClick={actions.next} disabled={!snapshot.canMoveNext}>
|
|
226
|
+
{snapshot.isLastStep ? "Finish" : "Next"}
|
|
227
|
+
</button>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`PathShellActions` contains: `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`.
|
|
234
|
+
|
|
235
|
+
### Context sharing
|
|
142
236
|
|
|
143
|
-
`<PathShell>`
|
|
237
|
+
`<PathShell>` provides a path context automatically — step components rendered inside it can call `usePathContext()` without a separate `<PathProvider>`:
|
|
144
238
|
|
|
145
239
|
```tsx
|
|
146
240
|
function DetailsForm() {
|
|
@@ -157,13 +251,12 @@ function DetailsForm() {
|
|
|
157
251
|
path={myPath}
|
|
158
252
|
initialData={{ name: "" }}
|
|
159
253
|
onComplete={handleDone}
|
|
160
|
-
steps={{
|
|
161
|
-
details: <DetailsForm />,
|
|
162
|
-
review: <ReviewPanel />,
|
|
163
|
-
}}
|
|
254
|
+
steps={{ details: <DetailsForm />, review: <ReviewPanel /> }}
|
|
164
255
|
/>
|
|
165
256
|
```
|
|
166
257
|
|
|
258
|
+
---
|
|
259
|
+
|
|
167
260
|
## Styling
|
|
168
261
|
|
|
169
262
|
`<PathShell>` renders structural HTML with BEM-style `pw-shell__*` CSS classes but ships with no embedded styles. Import the optional stylesheet for sensible defaults:
|
|
@@ -181,11 +274,259 @@ All visual values are CSS custom properties (`--pw-*`), so you can theme without
|
|
|
181
274
|
}
|
|
182
275
|
```
|
|
183
276
|
|
|
277
|
+
### Available CSS Custom Properties
|
|
278
|
+
|
|
279
|
+
**Layout:**
|
|
280
|
+
- `--pw-shell-max-width` — Maximum width of the shell (default: `720px`)
|
|
281
|
+
- `--pw-shell-padding` — Internal padding (default: `24px`)
|
|
282
|
+
- `--pw-shell-gap` — Gap between header, body, footer (default: `20px`)
|
|
283
|
+
- `--pw-shell-radius` — Border radius for cards (default: `10px`)
|
|
284
|
+
|
|
285
|
+
**Colors:**
|
|
286
|
+
- `--pw-color-bg` — Background color (default: `#ffffff`)
|
|
287
|
+
- `--pw-color-border` — Border color (default: `#dbe4f0`)
|
|
288
|
+
- `--pw-color-text` — Primary text color (default: `#1f2937`)
|
|
289
|
+
- `--pw-color-muted` — Muted text color (default: `#5b677a`)
|
|
290
|
+
- `--pw-color-primary` — Primary/accent color (default: `#2563eb`)
|
|
291
|
+
- `--pw-color-primary-light` — Light primary for backgrounds (default: `rgba(37, 99, 235, 0.12)`)
|
|
292
|
+
- `--pw-color-btn-bg` — Button background (default: `#f8fbff`)
|
|
293
|
+
- `--pw-color-btn-border` — Button border (default: `#c2d0e5`)
|
|
294
|
+
|
|
295
|
+
**Validation:**
|
|
296
|
+
- `--pw-color-error` — Error text color (default: `#dc2626`)
|
|
297
|
+
- `--pw-color-error-bg` — Error background (default: `#fef2f2`)
|
|
298
|
+
- `--pw-color-error-border` — Error border (default: `#fecaca`)
|
|
299
|
+
|
|
300
|
+
**Progress Indicator:**
|
|
301
|
+
- `--pw-dot-size` — Step dot size (default: `32px`)
|
|
302
|
+
- `--pw-dot-font-size` — Font size inside dots (default: `13px`)
|
|
303
|
+
- `--pw-track-height` — Progress track height (default: `4px`)
|
|
304
|
+
|
|
305
|
+
**Buttons:**
|
|
306
|
+
- `--pw-btn-padding` — Button padding (default: `8px 16px`)
|
|
307
|
+
- `--pw-btn-radius` — Button border radius (default: `6px`)
|
|
308
|
+
|
|
184
309
|
---
|
|
185
310
|
|
|
186
|
-
##
|
|
311
|
+
## Sub-Paths
|
|
312
|
+
|
|
313
|
+
Sub-paths allow you to nest multi-step workflows. Common use cases include:
|
|
314
|
+
- Running a child workflow per collection item (e.g., approve each document)
|
|
315
|
+
- Conditional drill-down flows (e.g., "Add payment method" modal)
|
|
316
|
+
- Reusable wizard components
|
|
317
|
+
|
|
318
|
+
### Basic Sub-Path Flow
|
|
319
|
+
|
|
320
|
+
When a sub-path is active:
|
|
321
|
+
- The shell switches to show the sub-path's steps
|
|
322
|
+
- The progress bar displays sub-path steps (not main path steps)
|
|
323
|
+
- Pressing Back on the first sub-path step **cancels** the sub-path and returns to the parent
|
|
324
|
+
- `usePathContext()` returns the **sub-path** snapshot, not the parent's
|
|
325
|
+
|
|
326
|
+
### Complete Example: Approver Collection
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
import type { PathData, PathDefinition } from "@daltonr/pathwrite-core";
|
|
330
|
+
|
|
331
|
+
// Sub-path data shape
|
|
332
|
+
interface ApproverReviewData extends PathData {
|
|
333
|
+
decision: "approve" | "reject" | "";
|
|
334
|
+
comments: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Main path data shape
|
|
338
|
+
interface ApprovalWorkflowData extends PathData {
|
|
339
|
+
documentTitle: string;
|
|
340
|
+
approvers: string[];
|
|
341
|
+
approvals: Array<{ approver: string; decision: string; comments: string }>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Define the sub-path (approver review wizard)
|
|
345
|
+
const approverReviewPath: PathDefinition<ApproverReviewData> = {
|
|
346
|
+
id: "approver-review",
|
|
347
|
+
steps: [
|
|
348
|
+
{ id: "review", title: "Review Document" },
|
|
349
|
+
{
|
|
350
|
+
id: "decision",
|
|
351
|
+
title: "Make Decision",
|
|
352
|
+
canMoveNext: ({ data }) =>
|
|
353
|
+
data.decision === "approve" || data.decision === "reject",
|
|
354
|
+
validationMessages: ({ data }) =>
|
|
355
|
+
!data.decision ? ["Please select Approve or Reject"] : []
|
|
356
|
+
},
|
|
357
|
+
{ id: "comments", title: "Add Comments" }
|
|
358
|
+
]
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Define the main path
|
|
362
|
+
const approvalWorkflowPath: PathDefinition<ApprovalWorkflowData> = {
|
|
363
|
+
id: "approval-workflow",
|
|
364
|
+
steps: [
|
|
365
|
+
{
|
|
366
|
+
id: "setup",
|
|
367
|
+
title: "Setup Approval",
|
|
368
|
+
canMoveNext: ({ data }) =>
|
|
369
|
+
(data.documentTitle ?? "").trim().length > 0 &&
|
|
370
|
+
data.approvers.length > 0
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: "run-approvals",
|
|
374
|
+
title: "Collect Approvals",
|
|
375
|
+
// Block "Next" until all approvers have completed their reviews
|
|
376
|
+
canMoveNext: ({ data }) =>
|
|
377
|
+
data.approvals.length === data.approvers.length,
|
|
378
|
+
validationMessages: ({ data }) => {
|
|
379
|
+
const remaining = data.approvers.length - data.approvals.length;
|
|
380
|
+
return remaining > 0
|
|
381
|
+
? [`${remaining} approver(s) pending review`]
|
|
382
|
+
: [];
|
|
383
|
+
},
|
|
384
|
+
// When an approver finishes their sub-path, record the result
|
|
385
|
+
onSubPathComplete(subPathId, subPathData, ctx, meta) {
|
|
386
|
+
const approverName = meta?.approverName as string;
|
|
387
|
+
const result = subPathData as ApproverReviewData;
|
|
388
|
+
return {
|
|
389
|
+
approvals: [
|
|
390
|
+
...ctx.data.approvals,
|
|
391
|
+
{
|
|
392
|
+
approver: approverName,
|
|
393
|
+
decision: result.decision,
|
|
394
|
+
comments: result.comments
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
// If an approver cancels (presses Back on first step), you can track it
|
|
400
|
+
onSubPathCancel(subPathId, subPathData, ctx, meta) {
|
|
401
|
+
console.log(`${meta?.approverName} cancelled their review`);
|
|
402
|
+
// Optionally return data changes, or just log
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
{ id: "summary", title: "Summary" }
|
|
406
|
+
]
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Component
|
|
410
|
+
function ApprovalWorkflow() {
|
|
411
|
+
const { startSubPath } = usePathContext<ApprovalWorkflowData>();
|
|
412
|
+
|
|
413
|
+
function launchReviewForApprover(approverName: string, index: number) {
|
|
414
|
+
// Pass correlation data via `meta` — it's echoed back to onSubPathComplete
|
|
415
|
+
startSubPath(
|
|
416
|
+
approverReviewPath,
|
|
417
|
+
{ decision: "", comments: "" },
|
|
418
|
+
{ approverName, approverIndex: index }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
187
421
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
422
|
+
return (
|
|
423
|
+
<PathShell
|
|
424
|
+
path={approvalWorkflowPath}
|
|
425
|
+
initialData={{ documentTitle: "", approvers: [], approvals: [] }}
|
|
426
|
+
steps={{
|
|
427
|
+
setup: <SetupStep />,
|
|
428
|
+
"run-approvals": <RunApprovalsStep onLaunchReview={launchReviewForApprover} />,
|
|
429
|
+
summary: <SummaryStep />,
|
|
430
|
+
// Sub-path steps (must be co-located in the same steps map)
|
|
431
|
+
review: <ReviewDocumentStep />,
|
|
432
|
+
decision: <MakeDecisionStep />,
|
|
433
|
+
comments: <AddCommentsStep />
|
|
434
|
+
}}
|
|
435
|
+
/>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Key Notes
|
|
441
|
+
|
|
442
|
+
**1. Sub-path steps must be co-located with main path steps**
|
|
443
|
+
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:
|
|
444
|
+
- Parent and sub-path step IDs **must not collide** (e.g., don't use `summary` in both)
|
|
445
|
+
- Sub-path step components can access parent data by referencing the parent path definition, but `usePathContext()` returns the **sub-path** snapshot
|
|
446
|
+
|
|
447
|
+
**2. The `meta` correlation field**
|
|
448
|
+
`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:
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
startSubPath(subPath, initialData, { itemIndex: 3, itemId: "abc" });
|
|
452
|
+
|
|
453
|
+
// In the parent step:
|
|
454
|
+
onSubPathComplete(subPathId, subPathData, ctx, meta) {
|
|
455
|
+
const itemIndex = meta?.itemIndex; // 3
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**3. Progress bar switches during sub-paths**
|
|
460
|
+
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.
|
|
461
|
+
|
|
462
|
+
**4. Accessing parent path data from sub-path components**
|
|
463
|
+
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`:
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
startSubPath(approverReviewPath, {
|
|
467
|
+
decision: "",
|
|
468
|
+
comments: "",
|
|
469
|
+
documentTitle: snapshot.data.documentTitle // copy from parent
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Guards and Lifecycle Hooks
|
|
476
|
+
|
|
477
|
+
### Defensive Guards (Important!)
|
|
478
|
+
|
|
479
|
+
**Guards and `validationMessages` are evaluated *before* `onEnter` runs on first entry.**
|
|
480
|
+
|
|
481
|
+
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:
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
// ✗ Unsafe — crashes if data.name is undefined
|
|
485
|
+
canMoveNext: ({ data }) => data.name.trim().length > 0
|
|
486
|
+
|
|
487
|
+
// ✓ Safe — handles undefined gracefully
|
|
488
|
+
canMoveNext: ({ data }) => (data.name ?? "").trim().length > 0
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Alternatively, pass `initialData` to `start()` / `<PathShell>` so all fields are present from the first snapshot:
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
<PathShell path={myPath} initialData={{ name: "", age: 0 }} />
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
If a guard throws, the engine catches it, logs a warning, and returns `true` (allow navigation) as a safe default.
|
|
498
|
+
|
|
499
|
+
### Async Guards and Validation Messages
|
|
500
|
+
|
|
501
|
+
Guards and `validationMessages` must be **synchronous** for inclusion in snapshots. Async functions are detected and warned about:
|
|
502
|
+
- Async `canMoveNext` / `canMovePrevious` default to `true` (optimistic)
|
|
503
|
+
- Async `validationMessages` default to `[]`
|
|
504
|
+
|
|
505
|
+
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.
|
|
506
|
+
|
|
507
|
+
### `isFirstEntry` Flag
|
|
508
|
+
|
|
509
|
+
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).
|
|
510
|
+
|
|
511
|
+
Use it to distinguish initialization from re-entry:
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
{
|
|
515
|
+
id: "details",
|
|
516
|
+
onEnter: ({ isFirstEntry, data }) => {
|
|
517
|
+
if (isFirstEntry) {
|
|
518
|
+
// Only pre-fill on first visit, not when returning via Back
|
|
519
|
+
return { name: "Default Name" };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Important:** `onEnter` fires every time you enter the step. If you want "initialize once" behavior, either:
|
|
526
|
+
1. Use `isFirstEntry` to conditionally return data
|
|
527
|
+
2. Provide `initialData` to `start()` instead of using `onEnter`
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Design notes
|
|
191
532
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,11 +9,11 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
9
9
|
snapshot: PathSnapshot<TData> | null;
|
|
10
10
|
/** Start (or restart) a path. */
|
|
11
11
|
start: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
12
|
-
/** Push a sub-path onto the stack. Requires an active path. */
|
|
13
|
-
startSubPath: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
12
|
+
/** Push a sub-path onto the stack. Requires an active path. Pass an optional `meta` object for correlation — it is returned unchanged to the parent step's `onSubPathComplete` / `onSubPathCancel` hooks. */
|
|
13
|
+
startSubPath: (path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>) => void;
|
|
14
14
|
/** Advance one step. Completes the path on the last step. */
|
|
15
15
|
next: () => void;
|
|
16
|
-
/** Go back one step.
|
|
16
|
+
/** Go back one step. No-op when already on the first step of a top-level path. Pops back to the parent path when on the first step of a sub-path. */
|
|
17
17
|
previous: () => void;
|
|
18
18
|
/** Cancel the active path (or sub-path). */
|
|
19
19
|
cancel: () => void;
|
|
@@ -23,6 +23,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
23
23
|
goToStepChecked: (stepId: string) => void;
|
|
24
24
|
/** 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
25
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Tear down any active path (without firing hooks) and immediately start the
|
|
28
|
+
* given path fresh. Safe to call whether or not a path is currently active.
|
|
29
|
+
* Use for "Start over" / retry flows without remounting the component.
|
|
30
|
+
*/
|
|
31
|
+
restart: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
26
32
|
}
|
|
27
33
|
export type PathProviderProps = PropsWithChildren<{
|
|
28
34
|
/** Forwarded to the internal usePath hook. */
|
|
@@ -81,7 +87,10 @@ export interface PathShellActions {
|
|
|
81
87
|
previous: () => void;
|
|
82
88
|
cancel: () => void;
|
|
83
89
|
goToStep: (stepId: string) => void;
|
|
90
|
+
goToStepChecked: (stepId: string) => void;
|
|
84
91
|
setData: (key: string, value: unknown) => void;
|
|
92
|
+
/** Restart the shell's current path with its current `initialData`. */
|
|
93
|
+
restart: () => void;
|
|
85
94
|
}
|
|
86
95
|
/**
|
|
87
96
|
* Default UI shell that renders a progress indicator, step content, and navigation
|
|
@@ -100,3 +109,4 @@ export interface PathShellActions {
|
|
|
100
109
|
* ```
|
|
101
110
|
*/
|
|
102
111
|
export declare function PathShell({ path: pathDef, steps, initialData, autoStart, onComplete, onCancel, onEvent, backLabel, nextLabel, finishLabel, cancelLabel, hideCancel, hideProgress, className, renderHeader, renderFooter, }: PathShellProps): ReactElement;
|
|
112
|
+
export type { PathData, PathDefinition, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
|
package/dist/index.js
CHANGED
|
@@ -29,14 +29,15 @@ export function usePath(options) {
|
|
|
29
29
|
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
30
30
|
// Stable action callbacks
|
|
31
31
|
const start = useCallback((path, initialData = {}) => engine.start(path, initialData), [engine]);
|
|
32
|
-
const startSubPath = useCallback((path, initialData = {}) => engine.startSubPath(path, initialData), [engine]);
|
|
32
|
+
const startSubPath = useCallback((path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta), [engine]);
|
|
33
33
|
const next = useCallback(() => engine.next(), [engine]);
|
|
34
34
|
const previous = useCallback(() => engine.previous(), [engine]);
|
|
35
35
|
const cancel = useCallback(() => engine.cancel(), [engine]);
|
|
36
36
|
const goToStep = useCallback((stepId) => engine.goToStep(stepId), [engine]);
|
|
37
37
|
const goToStepChecked = useCallback((stepId) => engine.goToStepChecked(stepId), [engine]);
|
|
38
38
|
const setData = useCallback((key, value) => engine.setData(key, value), [engine]);
|
|
39
|
-
|
|
39
|
+
const restart = useCallback((path, initialData = {}) => engine.restart(path, initialData), [engine]);
|
|
40
|
+
return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, restart };
|
|
40
41
|
}
|
|
41
42
|
// ---------------------------------------------------------------------------
|
|
42
43
|
// Context + Provider
|
|
@@ -90,7 +91,7 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
|
|
|
90
91
|
onCancel?.(event.data);
|
|
91
92
|
}
|
|
92
93
|
});
|
|
93
|
-
const { snapshot, start, next, previous, cancel, goToStep, setData } = pathReturn;
|
|
94
|
+
const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
|
|
94
95
|
// Auto-start on mount
|
|
95
96
|
const startedRef = useRef(false);
|
|
96
97
|
useEffect(() => {
|
|
@@ -109,7 +110,10 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
|
|
|
109
110
|
onClick: () => start(pathDef, initialData)
|
|
110
111
|
}, "Start"))));
|
|
111
112
|
}
|
|
112
|
-
const actions = {
|
|
113
|
+
const actions = {
|
|
114
|
+
next, previous, cancel, goToStep, goToStepChecked, setData,
|
|
115
|
+
restart: () => restart(pathDef, initialData)
|
|
116
|
+
};
|
|
113
117
|
return createElement(PathContext.Provider, { value: pathReturn }, createElement("div", { className: cls("pw-shell", className) },
|
|
114
118
|
// Header — progress indicator
|
|
115
119
|
!hideProgress && (renderHeader
|
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.4.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.
|
|
48
|
+
"@daltonr/pathwrite-core": "^0.4.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"react": "^18.3.1",
|
package/src/index.ts
CHANGED
|
@@ -30,11 +30,11 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
30
30
|
snapshot: PathSnapshot<TData> | null;
|
|
31
31
|
/** Start (or restart) a path. */
|
|
32
32
|
start: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
33
|
-
/** Push a sub-path onto the stack. Requires an active path. */
|
|
34
|
-
startSubPath: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
33
|
+
/** Push a sub-path onto the stack. Requires an active path. Pass an optional `meta` object for correlation — it is returned unchanged to the parent step's `onSubPathComplete` / `onSubPathCancel` hooks. */
|
|
34
|
+
startSubPath: (path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>) => void;
|
|
35
35
|
/** Advance one step. Completes the path on the last step. */
|
|
36
36
|
next: () => void;
|
|
37
|
-
/** Go back one step.
|
|
37
|
+
/** Go back one step. No-op when already on the first step of a top-level path. Pops back to the parent path when on the first step of a sub-path. */
|
|
38
38
|
previous: () => void;
|
|
39
39
|
/** Cancel the active path (or sub-path). */
|
|
40
40
|
cancel: () => void;
|
|
@@ -44,6 +44,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
|
|
|
44
44
|
goToStepChecked: (stepId: string) => void;
|
|
45
45
|
/** 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
46
|
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Tear down any active path (without firing hooks) and immediately start the
|
|
49
|
+
* given path fresh. Safe to call whether or not a path is currently active.
|
|
50
|
+
* Use for "Start over" / retry flows without remounting the component.
|
|
51
|
+
*/
|
|
52
|
+
restart: (path: PathDefinition<any>, initialData?: PathData) => void;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
export type PathProviderProps = PropsWithChildren<{
|
|
@@ -96,8 +102,8 @@ export function usePath<TData extends PathData = PathData>(options?: UsePathOpti
|
|
|
96
102
|
);
|
|
97
103
|
|
|
98
104
|
const startSubPath = useCallback(
|
|
99
|
-
(path: PathDefinition<any>, initialData: PathData = {}) =>
|
|
100
|
-
engine.startSubPath(path, initialData),
|
|
105
|
+
(path: PathDefinition<any>, initialData: PathData = {}, meta?: Record<string, unknown>) =>
|
|
106
|
+
engine.startSubPath(path, initialData, meta),
|
|
101
107
|
[engine]
|
|
102
108
|
);
|
|
103
109
|
|
|
@@ -120,7 +126,13 @@ export function usePath<TData extends PathData = PathData>(options?: UsePathOpti
|
|
|
120
126
|
[engine]
|
|
121
127
|
) as UsePathReturn<TData>["setData"];
|
|
122
128
|
|
|
123
|
-
|
|
129
|
+
const restart = useCallback(
|
|
130
|
+
(path: PathDefinition<any>, initialData: PathData = {}) =>
|
|
131
|
+
engine.restart(path, initialData),
|
|
132
|
+
[engine]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, restart };
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
// ---------------------------------------------------------------------------
|
|
@@ -197,7 +209,10 @@ export interface PathShellActions {
|
|
|
197
209
|
previous: () => void;
|
|
198
210
|
cancel: () => void;
|
|
199
211
|
goToStep: (stepId: string) => void;
|
|
212
|
+
goToStepChecked: (stepId: string) => void;
|
|
200
213
|
setData: (key: string, value: unknown) => void;
|
|
214
|
+
/** Restart the shell's current path with its current `initialData`. */
|
|
215
|
+
restart: () => void;
|
|
201
216
|
}
|
|
202
217
|
|
|
203
218
|
/**
|
|
@@ -242,7 +257,7 @@ export function PathShell({
|
|
|
242
257
|
}
|
|
243
258
|
});
|
|
244
259
|
|
|
245
|
-
const { snapshot, start, next, previous, cancel, goToStep, setData } = pathReturn;
|
|
260
|
+
const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart } = pathReturn;
|
|
246
261
|
|
|
247
262
|
// Auto-start on mount
|
|
248
263
|
const startedRef = useRef(false);
|
|
@@ -272,7 +287,10 @@ export function PathShell({
|
|
|
272
287
|
);
|
|
273
288
|
}
|
|
274
289
|
|
|
275
|
-
const actions: PathShellActions = {
|
|
290
|
+
const actions: PathShellActions = {
|
|
291
|
+
next, previous, cancel, goToStep, goToStepChecked, setData,
|
|
292
|
+
restart: () => restart(pathDef, initialData)
|
|
293
|
+
};
|
|
276
294
|
|
|
277
295
|
return createElement(PathContext.Provider, { value: pathReturn },
|
|
278
296
|
createElement("div", { className: cls("pw-shell", className) },
|
|
@@ -375,7 +393,21 @@ function defaultFooter(
|
|
|
375
393
|
// Helpers
|
|
376
394
|
// ---------------------------------------------------------------------------
|
|
377
395
|
|
|
378
|
-
|
|
379
396
|
function cls(...parts: (string | undefined | false | null)[]): string {
|
|
380
397
|
return parts.filter(Boolean).join(" ");
|
|
381
398
|
}
|
|
399
|
+
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
// Re-export core types for convenience
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
export type {
|
|
405
|
+
PathData,
|
|
406
|
+
PathDefinition,
|
|
407
|
+
PathEvent,
|
|
408
|
+
PathSnapshot,
|
|
409
|
+
PathStep,
|
|
410
|
+
PathStepContext,
|
|
411
|
+
SerializedPathState
|
|
412
|
+
} from "@daltonr/pathwrite-core";
|
|
413
|
+
|