@daltonr/pathwrite-react 0.1.4 → 0.2.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
@@ -88,9 +88,10 @@ function NavButtons() {
88
88
  | `start(definition, data?)` | `function` | Start or re-start a path. |
89
89
  | `startSubPath(definition, data?)` | `function` | Push a sub-path. Requires an active path. |
90
90
  | `next()` | `function` | Advance one step. Completes the path on the last step. |
91
- | `previous()` | `function` | Go back one step. Cancels the path from the first step. |
91
+ | `previous()` | `function` | Go back one step. No-op when already on the first step of a top-level path. |
92
92
  | `cancel()` | `function` | Cancel the active path (or sub-path). |
93
93
  | `goToStep(stepId)` | `function` | Jump directly to a step by ID. Calls `onLeave` / `onEnter` but bypasses guards and `shouldSkip`. |
94
+ | `goToStepChecked(stepId)` | `function` | Jump to a step by ID, checking `canMoveNext` (forward) or `canMovePrevious` (backward) first. Navigation is blocked if the guard returns false. |
94
95
  | `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. |
95
96
 
96
97
  All action callbacks are **referentially stable** — safe to pass as props or include in dependency arrays without causing unnecessary re-renders.
package/dist/index.css CHANGED
@@ -6,11 +6,11 @@
6
6
  * Every visual value is a CSS custom property (--pw-*) so you can
7
7
  * theme the shell without overriding selectors.
8
8
  *
9
- * Usage:
10
- * import "@daltonr/pathwrite-react/styles.css"; // React
11
- * import "@daltonr/pathwrite-vue/styles.css"; // Vue
12
- * // Angular: add "node_modules/@daltonr/pathwrite-angular/dist/index.css"
13
- * // to the styles array in angular.json
9
+ * Usage (React / Vue):
10
+ * import "adapter-package/styles.css";
11
+ *
12
+ * Usage (Angular) add to the styles array in angular.json:
13
+ * "node_modules/@daltonr/pathwrite-angular/dist/index.css"
14
14
  */
15
15
 
16
16
  /* ------------------------------------------------------------------ */
@@ -170,6 +170,40 @@
170
170
  min-height: 120px;
171
171
  }
172
172
 
173
+ /* ------------------------------------------------------------------ */
174
+ /* Validation messages */
175
+ /* ------------------------------------------------------------------ */
176
+ :root {
177
+ --pw-color-error: #dc2626;
178
+ --pw-color-error-bg: #fef2f2;
179
+ --pw-color-error-border: #fecaca;
180
+ }
181
+
182
+ .pw-shell__validation {
183
+ list-style: none;
184
+ margin: 0;
185
+ padding: 12px 16px;
186
+ background: var(--pw-color-error-bg);
187
+ border: 1px solid var(--pw-color-error-border);
188
+ border-radius: var(--pw-shell-radius);
189
+ display: flex;
190
+ flex-direction: column;
191
+ gap: 4px;
192
+ }
193
+
194
+ .pw-shell__validation-item {
195
+ font-size: 13px;
196
+ color: var(--pw-color-error);
197
+ padding-left: 16px;
198
+ position: relative;
199
+ }
200
+
201
+ .pw-shell__validation-item::before {
202
+ content: "•";
203
+ position: absolute;
204
+ left: 4px;
205
+ }
206
+
173
207
  /* ------------------------------------------------------------------ */
174
208
  /* Footer — navigation buttons */
175
209
  /* ------------------------------------------------------------------ */
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export interface UsePathReturn<TData extends PathData = PathData> {
19
19
  cancel: () => void;
20
20
  /** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
21
21
  goToStep: (stepId: string) => void;
22
+ /** Jump directly to a step by ID, checking the current step's canMoveNext (forward) or canMovePrevious (backward) guard first. Navigation is blocked if the guard returns false. */
23
+ goToStepChecked: (stepId: string) => void;
22
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. */
23
25
  setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
24
26
  }
package/dist/index.js CHANGED
@@ -34,8 +34,9 @@ export function usePath(options) {
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
+ const goToStepChecked = useCallback((stepId) => engine.goToStepChecked(stepId), [engine]);
37
38
  const setData = useCallback((key, value) => engine.setData(key, value), [engine]);
38
- return { snapshot, start, startSubPath, next, previous, cancel, goToStep, setData };
39
+ return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
39
40
  }
40
41
  // ---------------------------------------------------------------------------
41
42
  // Context + Provider
@@ -116,6 +117,8 @@ export function PathShell({ path: pathDef, steps, initialData = {}, autoStart =
116
117
  : defaultHeader(snapshot)),
117
118
  // Body — step content
118
119
  createElement("div", { className: "pw-shell__body" }, stepContent),
120
+ // Validation messages
121
+ snapshot.validationMessages.length > 0 && createElement("ul", { className: "pw-shell__validation" }, ...snapshot.validationMessages.map((msg, i) => createElement("li", { key: i, className: "pw-shell__validation-item" }, msg))),
119
122
  // Footer — navigation buttons
120
123
  renderFooter
121
124
  ? renderFooter(snapshot, actions)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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.1.4",
3
+ "version": "0.2.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.",
@@ -32,6 +32,7 @@
32
32
  "types": "dist/index.d.ts",
33
33
  "files": [
34
34
  "dist",
35
+ "src",
35
36
  "README.md",
36
37
  "LICENSE"
37
38
  ],
@@ -44,7 +45,7 @@
44
45
  "react": ">=18.0.0"
45
46
  },
46
47
  "dependencies": {
47
- "@daltonr/pathwrite-core": "^0.1.4"
48
+ "@daltonr/pathwrite-core": "^0.2.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "react": "^18.3.1",
package/src/index.ts ADDED
@@ -0,0 +1,381 @@
1
+ import {
2
+ createContext,
3
+ createElement,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useSyncExternalStore
9
+ } from "react";
10
+ import type { PropsWithChildren, ReactElement, ReactNode } from "react";
11
+ import {
12
+ PathData,
13
+ PathDefinition,
14
+ PathEngine,
15
+ PathEvent,
16
+ PathSnapshot
17
+ } from "@daltonr/pathwrite-core";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Types
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export interface UsePathOptions {
24
+ /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
25
+ onEvent?: (event: PathEvent) => void;
26
+ }
27
+
28
+ export interface UsePathReturn<TData extends PathData = PathData> {
29
+ /** Current path snapshot, or `null` when no path is active. Triggers a React re-render on change. */
30
+ snapshot: PathSnapshot<TData> | null;
31
+ /** Start (or restart) a path. */
32
+ start: (path: PathDefinition, initialData?: PathData) => void;
33
+ /** Push a sub-path onto the stack. Requires an active path. */
34
+ startSubPath: (path: PathDefinition, initialData?: PathData) => void;
35
+ /** Advance one step. Completes the path on the last step. */
36
+ next: () => void;
37
+ /** Go back one step. Cancels the path from the first step. */
38
+ previous: () => void;
39
+ /** Cancel the active path (or sub-path). */
40
+ cancel: () => void;
41
+ /** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
42
+ goToStep: (stepId: string) => void;
43
+ /** Jump directly to a step by ID, checking the current step's canMoveNext (forward) or canMovePrevious (backward) guard first. Navigation is blocked if the guard returns false. */
44
+ goToStepChecked: (stepId: string) => void;
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
+ setData: <K extends string & keyof TData>(key: K, value: TData[K]) => void;
47
+ }
48
+
49
+ export type PathProviderProps = PropsWithChildren<{
50
+ /** Forwarded to the internal usePath hook. */
51
+ onEvent?: (event: PathEvent) => void;
52
+ }>;
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // usePath hook
56
+ // ---------------------------------------------------------------------------
57
+
58
+ export function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData> {
59
+ // Stable engine instance for the lifetime of the hook
60
+ const engineRef = useRef<PathEngine | null>(null);
61
+ if (engineRef.current === null) {
62
+ engineRef.current = new PathEngine();
63
+ }
64
+ const engine = engineRef.current;
65
+
66
+ // Keep the onEvent callback current without changing the subscribe identity
67
+ const onEventRef = useRef(options?.onEvent);
68
+ onEventRef.current = options?.onEvent;
69
+
70
+ // Cached snapshot — updated only inside the subscribe callback
71
+ const snapshotRef = useRef<PathSnapshot<TData> | null>(null);
72
+
73
+ const subscribe = useCallback(
74
+ (callback: () => void) =>
75
+ engine.subscribe((event: PathEvent) => {
76
+ if (event.type === "stateChanged" || event.type === "resumed") {
77
+ snapshotRef.current = event.snapshot as PathSnapshot<TData>;
78
+ } else if (event.type === "completed" || event.type === "cancelled") {
79
+ snapshotRef.current = null;
80
+ }
81
+ onEventRef.current?.(event);
82
+ callback();
83
+ }),
84
+ [engine]
85
+ );
86
+
87
+ const getSnapshot = useCallback(() => snapshotRef.current, []);
88
+
89
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot);
90
+
91
+ // Stable action callbacks
92
+ const start = useCallback(
93
+ (path: PathDefinition, initialData: PathData = {}) =>
94
+ engine.start(path, initialData),
95
+ [engine]
96
+ );
97
+
98
+ const startSubPath = useCallback(
99
+ (path: PathDefinition, initialData: PathData = {}) =>
100
+ engine.startSubPath(path, initialData),
101
+ [engine]
102
+ );
103
+
104
+ const next = useCallback(() => engine.next(), [engine]);
105
+ const previous = useCallback(() => engine.previous(), [engine]);
106
+ const cancel = useCallback(() => engine.cancel(), [engine]);
107
+
108
+ const goToStep = useCallback(
109
+ (stepId: string) => engine.goToStep(stepId),
110
+ [engine]
111
+ );
112
+
113
+ const goToStepChecked = useCallback(
114
+ (stepId: string) => engine.goToStepChecked(stepId),
115
+ [engine]
116
+ );
117
+
118
+ const setData = useCallback(
119
+ <K extends string & keyof TData>(key: K, value: TData[K]) => engine.setData(key, value as unknown),
120
+ [engine]
121
+ ) as UsePathReturn<TData>["setData"];
122
+
123
+ return { snapshot, start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData };
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Context + Provider
128
+ // ---------------------------------------------------------------------------
129
+
130
+ const PathContext = createContext<UsePathReturn | null>(null);
131
+
132
+ /**
133
+ * Provides a single `usePath` instance to all descendants.
134
+ * Consume with `usePathContext()`.
135
+ */
136
+ export function PathProvider({ children, onEvent }: PathProviderProps): ReactElement {
137
+ const path = usePath({ onEvent });
138
+ return createElement(PathContext.Provider, { value: path }, children);
139
+ }
140
+
141
+ /**
142
+ * Access the nearest `PathProvider`'s path instance.
143
+ * Throws if used outside of a `<PathProvider>`.
144
+ *
145
+ * The optional generic narrows `snapshot.data` for convenience — it is a
146
+ * **type-level assertion**, not a runtime guarantee.
147
+ */
148
+ export function usePathContext<TData extends PathData = PathData>(): UsePathReturn<TData> {
149
+ const ctx = useContext(PathContext);
150
+ if (ctx === null) {
151
+ throw new Error("usePathContext must be used within a <PathProvider>.");
152
+ }
153
+ return ctx as UsePathReturn<TData>;
154
+ }
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Default UI — PathShell
158
+ // ---------------------------------------------------------------------------
159
+
160
+ export interface PathShellProps {
161
+ /** The path definition to drive. */
162
+ path: PathDefinition;
163
+ /** Map of step ID → content. The shell renders `steps[snapshot.stepId]` for the current step. */
164
+ steps: Record<string, ReactNode>;
165
+ /** Initial data passed to `engine.start()`. */
166
+ initialData?: PathData;
167
+ /** If true, the path is started automatically on mount. Defaults to `true`. */
168
+ autoStart?: boolean;
169
+ /** Called when the path completes. Receives the final data. */
170
+ onComplete?: (data: PathData) => void;
171
+ /** Called when the path is cancelled. Receives the data at time of cancellation. */
172
+ onCancel?: (data: PathData) => void;
173
+ /** Called for every engine event. */
174
+ onEvent?: (event: PathEvent) => void;
175
+ /** Label for the Back button. Defaults to `"Back"`. */
176
+ backLabel?: string;
177
+ /** Label for the Next button. Defaults to `"Next"`. */
178
+ nextLabel?: string;
179
+ /** Label for the Finish button (shown on the last step). Defaults to `"Finish"`. */
180
+ finishLabel?: string;
181
+ /** Label for the Cancel button. Defaults to `"Cancel"`. */
182
+ cancelLabel?: string;
183
+ /** If true, hide the Cancel button. Defaults to `false`. */
184
+ hideCancel?: boolean;
185
+ /** If true, hide the progress indicator. Defaults to `false`. */
186
+ hideProgress?: boolean;
187
+ /** Optional extra CSS class on the root element. */
188
+ className?: string;
189
+ /** Render prop to replace the entire header (progress area). Receives the snapshot. */
190
+ renderHeader?: (snapshot: PathSnapshot) => ReactNode;
191
+ /** Render prop to replace the entire footer (navigation area). Receives the snapshot and actions. */
192
+ renderFooter?: (snapshot: PathSnapshot, actions: PathShellActions) => ReactNode;
193
+ }
194
+
195
+ export interface PathShellActions {
196
+ next: () => void;
197
+ previous: () => void;
198
+ cancel: () => void;
199
+ goToStep: (stepId: string) => void;
200
+ setData: (key: string, value: unknown) => void;
201
+ }
202
+
203
+ /**
204
+ * Default UI shell that renders a progress indicator, step content, and navigation
205
+ * buttons. Pass a `steps` map to define per-step content.
206
+ *
207
+ * ```tsx
208
+ * <PathShell
209
+ * path={myPath}
210
+ * initialData={{ name: "" }}
211
+ * onComplete={handleDone}
212
+ * steps={{
213
+ * details: <DetailsForm />,
214
+ * review: <ReviewPanel />,
215
+ * }}
216
+ * />
217
+ * ```
218
+ */
219
+ export function PathShell({
220
+ path: pathDef,
221
+ steps,
222
+ initialData = {},
223
+ autoStart = true,
224
+ onComplete,
225
+ onCancel,
226
+ onEvent,
227
+ backLabel = "Back",
228
+ nextLabel = "Next",
229
+ finishLabel = "Finish",
230
+ cancelLabel = "Cancel",
231
+ hideCancel = false,
232
+ hideProgress = false,
233
+ className,
234
+ renderHeader,
235
+ renderFooter,
236
+ }: PathShellProps): ReactElement {
237
+ const pathReturn = usePath({
238
+ onEvent(event) {
239
+ onEvent?.(event);
240
+ if (event.type === "completed") onComplete?.(event.data);
241
+ if (event.type === "cancelled") onCancel?.(event.data);
242
+ }
243
+ });
244
+
245
+ const { snapshot, start, next, previous, cancel, goToStep, setData } = pathReturn;
246
+
247
+ // Auto-start on mount
248
+ const startedRef = useRef(false);
249
+ useEffect(() => {
250
+ if (autoStart && !startedRef.current) {
251
+ startedRef.current = true;
252
+ start(pathDef, initialData);
253
+ }
254
+ // eslint-disable-next-line react-hooks/exhaustive-deps
255
+ }, []);
256
+
257
+ // Look up step content from the steps map
258
+ const stepContent = snapshot ? (steps[snapshot.stepId] ?? null) : null;
259
+
260
+ if (!snapshot) {
261
+ return createElement(PathContext.Provider, { value: pathReturn },
262
+ createElement("div", { className: cls("pw-shell", className) },
263
+ createElement("div", { className: "pw-shell__empty" },
264
+ createElement("p", null, "No active path."),
265
+ !autoStart && createElement("button", {
266
+ type: "button",
267
+ className: "pw-shell__start-btn",
268
+ onClick: () => start(pathDef, initialData)
269
+ }, "Start")
270
+ )
271
+ )
272
+ );
273
+ }
274
+
275
+ const actions: PathShellActions = { next, previous, cancel, goToStep, setData };
276
+
277
+ return createElement(PathContext.Provider, { value: pathReturn },
278
+ createElement("div", { className: cls("pw-shell", className) },
279
+ // Header — progress indicator
280
+ !hideProgress && (renderHeader
281
+ ? renderHeader(snapshot)
282
+ : defaultHeader(snapshot)),
283
+ // Body — step content
284
+ createElement("div", { className: "pw-shell__body" }, stepContent),
285
+ // Validation messages
286
+ snapshot.validationMessages.length > 0 && createElement("ul", { className: "pw-shell__validation" },
287
+ ...snapshot.validationMessages.map((msg, i) =>
288
+ createElement("li", { key: i, className: "pw-shell__validation-item" }, msg)
289
+ )
290
+ ),
291
+ // Footer — navigation buttons
292
+ renderFooter
293
+ ? renderFooter(snapshot, actions)
294
+ : defaultFooter(snapshot, actions, {
295
+ backLabel, nextLabel, finishLabel, cancelLabel, hideCancel
296
+ })
297
+ )
298
+ );
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Default header (progress indicator)
303
+ // ---------------------------------------------------------------------------
304
+
305
+ function defaultHeader(snapshot: PathSnapshot): ReactElement {
306
+ return createElement("div", { className: "pw-shell__header" },
307
+ createElement("div", { className: "pw-shell__steps" },
308
+ ...snapshot.steps.map((step, i) =>
309
+ createElement("div", {
310
+ key: step.id,
311
+ className: cls("pw-shell__step", `pw-shell__step--${step.status}`)
312
+ },
313
+ createElement("span", { className: "pw-shell__step-dot" },
314
+ step.status === "completed" ? "✓" : String(i + 1)
315
+ ),
316
+ createElement("span", { className: "pw-shell__step-label" },
317
+ step.title ?? step.id
318
+ )
319
+ )
320
+ )
321
+ ),
322
+ createElement("div", { className: "pw-shell__track" },
323
+ createElement("div", {
324
+ className: "pw-shell__track-fill",
325
+ style: { width: `${snapshot.progress * 100}%` }
326
+ })
327
+ )
328
+ );
329
+ }
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // Default footer (navigation buttons)
333
+ // ---------------------------------------------------------------------------
334
+
335
+ interface FooterLabels {
336
+ backLabel: string;
337
+ nextLabel: string;
338
+ finishLabel: string;
339
+ cancelLabel: string;
340
+ hideCancel: boolean;
341
+ }
342
+
343
+ function defaultFooter(
344
+ snapshot: PathSnapshot,
345
+ actions: PathShellActions,
346
+ labels: FooterLabels
347
+ ): ReactElement {
348
+ return createElement("div", { className: "pw-shell__footer" },
349
+ createElement("div", { className: "pw-shell__footer-left" },
350
+ !snapshot.isFirstStep && createElement("button", {
351
+ type: "button",
352
+ className: "pw-shell__btn pw-shell__btn--back",
353
+ disabled: snapshot.isNavigating || !snapshot.canMovePrevious,
354
+ onClick: actions.previous
355
+ }, labels.backLabel)
356
+ ),
357
+ createElement("div", { className: "pw-shell__footer-right" },
358
+ !labels.hideCancel && createElement("button", {
359
+ type: "button",
360
+ className: "pw-shell__btn pw-shell__btn--cancel",
361
+ disabled: snapshot.isNavigating,
362
+ onClick: actions.cancel
363
+ }, labels.cancelLabel),
364
+ createElement("button", {
365
+ type: "button",
366
+ className: "pw-shell__btn pw-shell__btn--next",
367
+ disabled: snapshot.isNavigating || !snapshot.canMoveNext,
368
+ onClick: actions.next
369
+ }, snapshot.isLastStep ? labels.finishLabel : labels.nextLabel)
370
+ )
371
+ );
372
+ }
373
+
374
+ // ---------------------------------------------------------------------------
375
+ // Helpers
376
+ // ---------------------------------------------------------------------------
377
+
378
+
379
+ function cls(...parts: (string | undefined | false | null)[]): string {
380
+ return parts.filter(Boolean).join(" ");
381
+ }