@daltonr/pathwrite-svelte 0.13.1 → 0.14.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.
@@ -1,11 +1,5 @@
1
1
  import { onDestroy, getContext, setContext } from "svelte";
2
- import type {
3
- PathData,
4
- PathDefinition,
5
- PathEngine,
6
- PathEvent,
7
- PathSnapshot
8
- } from "@daltonr/pathwrite-core";
2
+ import type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
9
3
  import { PathEngine as PathEngineClass } from "@daltonr/pathwrite-core";
10
4
 
11
5
  // Re-export core utilities and types for convenience
@@ -17,18 +11,19 @@ export type {
17
11
  PathEngine,
18
12
  PathEvent,
19
13
  PathSnapshot,
14
+ StepStatus,
20
15
  PathStep,
21
16
  PathStepContext,
22
17
  ProgressLayout,
23
18
  RootProgress,
24
- SerializedPathState
19
+ SerializedPathState,
25
20
  } from "@daltonr/pathwrite-core";
26
21
 
27
22
  // ---------------------------------------------------------------------------
28
23
  // Types
29
24
  // ---------------------------------------------------------------------------
30
25
 
31
- export interface UsePathOptions {
26
+ export interface UsePathOptions<TData extends PathData = PathData> {
32
27
  /**
33
28
  * An externally-managed `PathEngine` to subscribe to — for example, the engine
34
29
  * returned by `restoreOrStart()` from `@daltonr/pathwrite-store`.
@@ -45,23 +40,27 @@ export interface UsePathOptions {
45
40
  * later (e.g. from an async `restoreOrStart()`) or is swapped is adopted —
46
41
  * the hook re-subscribes and re-seeds its snapshot from the new engine.
47
42
  */
48
- engine?: PathEngine;
43
+ engine?: PathEngine<TData>;
49
44
  /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
50
- onEvent?: (event: PathEvent) => void;
45
+ onEvent?: (event: PathEvent<TData>) => void;
51
46
  }
52
47
 
53
48
  export interface UsePathReturn<TData extends PathData = PathData> {
54
49
  /**
55
- * Current path snapshot, or `null` when no path is active. Reactive via `$state`.
50
+ * Current path snapshot, or `null` when no path is active. Reactive via `$state.raw`.
56
51
  *
57
52
  * ⚠️ **Do not destructure.** `const { snapshot } = usePath()` captures the value
58
53
  * once and loses reactivity. Always access as `path.snapshot`.
59
54
  */
60
55
  readonly snapshot: PathSnapshot<TData> | null;
61
56
  /** Start (or restart) a path. */
62
- start: (path: PathDefinition<any>, initialData?: PathData) => Promise<void>;
63
- /** 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. */
64
- startSubPath: (path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>) => Promise<void>;
57
+ start: (path: PathDefinition<TData>, initialData?: Partial<TData>) => Promise<void>;
58
+ /** Push a sub-path onto the stack. Requires an active path. A sub-path has its own data, so any definition is accepted. Pass an optional `meta` object for correlation — it is returned unchanged to the parent step's `onSubPathComplete` / `onSubPathCancel` hooks. */
59
+ startSubPath: (
60
+ path: PathDefinition,
61
+ initialData?: PathData,
62
+ meta?: Record<string, unknown>
63
+ ) => Promise<void>;
65
64
  /** Advance one step. Completes the path on the last step. */
66
65
  next: () => Promise<void>;
67
66
  /** 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. */
@@ -150,21 +149,24 @@ export interface UsePathReturn<TData extends PathData = PathData> {
150
149
  * ```
151
150
  */
152
151
  export function usePath<TData extends PathData = PathData>(
153
- options?: UsePathOptions
152
+ options?: UsePathOptions<TData>
154
153
  ): UsePathReturn<TData> {
155
- let ownEngine: PathEngine | null = null;
156
- const resolveEngine = (): PathEngine => options?.engine ?? (ownEngine ??= new PathEngineClass());
154
+ let ownEngine: PathEngine<TData> | null = null;
155
+ const resolveEngine = (): PathEngine<TData> =>
156
+ options?.engine ?? (ownEngine ??= new PathEngineClass<TData>());
157
157
  let engine = resolveEngine();
158
158
 
159
- let _snapshot: PathSnapshot<TData> | null = $state(
160
- engine.snapshot() as PathSnapshot<TData> | null
161
- );
159
+ // `$state.raw`, not `$state`: snapshots are immutable values the engine
160
+ // replaces wholesale on every change and nothing mutates them in place, so a
161
+ // deep proxy over each one (and its `data`) would be pure overhead. Every
162
+ // update below reassigns the whole value, which is what `$state.raw` tracks.
163
+ let _snapshot: PathSnapshot<TData> | null = $state.raw(engine.snapshot());
162
164
 
163
- const onEngineEvent = (event: PathEvent): void => {
165
+ const onEngineEvent = (event: PathEvent<TData>): void => {
164
166
  if (event.type === "stateChanged" || event.type === "resumed") {
165
- _snapshot = event.snapshot as PathSnapshot<TData>;
167
+ _snapshot = event.snapshot;
166
168
  } else if (event.type === "completed" || event.type === "cancelled") {
167
- _snapshot = engine.snapshot() as PathSnapshot<TData> | null;
169
+ _snapshot = engine.snapshot();
168
170
  }
169
171
  options?.onEvent?.(event);
170
172
  };
@@ -179,19 +181,22 @@ export function usePath<TData extends PathData = PathData>(
179
181
  if (next === engine) return;
180
182
  unsubscribe();
181
183
  engine = next;
182
- _snapshot = engine.snapshot() as PathSnapshot<TData> | null;
184
+ _snapshot = engine.snapshot();
183
185
  unsubscribe = engine.subscribe(onEngineEvent);
184
186
  });
185
187
  });
186
188
 
187
189
  // Auto-cleanup when component is destroyed
188
- onDestroy(() => { unsubscribe(); stopWatching(); });
190
+ onDestroy(() => {
191
+ unsubscribe();
192
+ stopWatching();
193
+ });
189
194
 
190
- const start = (path: PathDefinition<any>, initialData: PathData = {}): Promise<void> =>
195
+ const start = (path: PathDefinition<TData>, initialData: Partial<TData> = {}): Promise<void> =>
191
196
  engine.start(path, initialData);
192
197
 
193
198
  const startSubPath = (
194
- path: PathDefinition<any>,
199
+ path: PathDefinition,
195
200
  initialData: PathData = {},
196
201
  meta?: Record<string, unknown>
197
202
  ): Promise<void> => engine.startSubPath(path, initialData, meta);
@@ -200,11 +205,13 @@ export function usePath<TData extends PathData = PathData>(
200
205
  const previous = (): Promise<void> => engine.previous();
201
206
  const cancel = (): Promise<void> => engine.cancel();
202
207
 
203
- const goToStep = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> => engine.goToStep(stepId, options);
204
- const goToStepChecked = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> => engine.goToStepChecked(stepId, options);
208
+ const goToStep = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> =>
209
+ engine.goToStep(stepId, options);
210
+ const goToStepChecked = (stepId: string, options?: { validateOnLeave?: boolean }): Promise<void> =>
211
+ engine.goToStepChecked(stepId, options);
205
212
 
206
- const setData = (<K extends string & keyof TData>(key: K, value: TData[K]): Promise<void> =>
207
- engine.setData(key, value as unknown)) as UsePathReturn<TData>["setData"];
213
+ const setData = <K extends string & keyof TData>(key: K, value: TData[K]): Promise<void> =>
214
+ engine.setData(key, value);
208
215
 
209
216
  const resetStep = (): Promise<void> => engine.resetStep();
210
217
 
@@ -215,7 +222,9 @@ export function usePath<TData extends PathData = PathData>(
215
222
  const validate = (): void => engine.validate();
216
223
 
217
224
  return {
218
- get snapshot() { return _snapshot; },
225
+ get snapshot() {
226
+ return _snapshot;
227
+ },
219
228
  start,
220
229
  startSubPath,
221
230
  next,
@@ -228,7 +237,7 @@ export function usePath<TData extends PathData = PathData>(
228
237
  restart,
229
238
  retry,
230
239
  suspend,
231
- validate
240
+ validate,
232
241
  };
233
242
  }
234
243
 
@@ -263,7 +272,10 @@ const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
263
272
  * returns (derived from `UsePathReturn`, so the two cannot drift apart) plus
264
273
  * the `services` object given to `<PathShell>`.
265
274
  */
266
- export interface PathContext<TData extends PathData = PathData, TServices = unknown> extends UsePathReturn<TData> {
275
+ export interface PathContext<
276
+ TData extends PathData = PathData,
277
+ TServices = unknown,
278
+ > extends UsePathReturn<TData> {
267
279
  services: TServices;
268
280
  }
269
281
 
@@ -287,7 +299,10 @@ export interface PathContext<TData extends PathData = PathData, TServices = unkn
287
299
  * <button onclick={ctx.next}>Next</button>
288
300
  * ```
289
301
  */
290
- export function usePathContext<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices> {
302
+ export function usePathContext<TData extends PathData = PathData, TServices = unknown>(): PathContext<
303
+ TData,
304
+ TServices
305
+ > {
291
306
  const ctx = getContext<PathContext<TData, TServices>>(PATH_CONTEXT_KEY);
292
307
  if (!ctx) {
293
308
  throw new Error(
@@ -302,7 +317,9 @@ export function usePathContext<TData extends PathData = PathData, TServices = un
302
317
  * Internal: Set the PathContext for child components.
303
318
  * Used by PathShell component.
304
319
  */
305
- export function setPathContext<TData extends PathData = PathData, TServices = unknown>(ctx: PathContext<TData, TServices>): void {
320
+ export function setPathContext<TData extends PathData = PathData, TServices = unknown>(
321
+ ctx: PathContext<TData, TServices>
322
+ ): void {
306
323
  setContext(PATH_CONTEXT_KEY, ctx);
307
324
  }
308
325
 
@@ -312,7 +329,8 @@ export function setPathContext<TData extends PathData = PathData, TServices = un
312
329
  * the outer shell's context for `restoreKey` auto-wiring — must be called
313
330
  * before `setPathContext()` so it reads the parent rather than self.
314
331
  */
315
- export function getPathContextOrNull<TData extends PathData = PathData, TServices = unknown>(): PathContext<TData, TServices> | undefined {
332
+ export function getPathContextOrNull<TData extends PathData = PathData, TServices = unknown>():
333
+ PathContext<TData, TServices> | undefined {
316
334
  return getContext<PathContext<TData, TServices>>(PATH_CONTEXT_KEY);
317
335
  }
318
336
 
@@ -351,15 +369,15 @@ export function bindData<TData extends PathData, K extends string & keyof TData>
351
369
  },
352
370
  set(value: TData[K]) {
353
371
  setData(key, value);
354
- }
372
+ },
355
373
  };
356
374
  }
357
375
 
358
376
  /**
359
377
  * Converts a hyphenated step ID to camelCase.
360
- * Used internally by PathShell to resolve step snippets when a step ID contains
361
- * hyphens (e.g. "cover-letter" → "coverLetter"), since Svelte prop names must
362
- * be valid JavaScript identifiers.
378
+ * Used internally by PathShell as a fallback key into its `steps` record, so a
379
+ * hyphenated step ID (e.g. "cover-letter") also resolves an entry registered
380
+ * under its camelCase form ("coverLetter").
363
381
  */
364
382
  export function stepIdToCamelCase(id: string): string {
365
383
  return id.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
@@ -367,4 +385,3 @@ export function stepIdToCamelCase(id: string): string {
367
385
 
368
386
  // Export PathShell component
369
387
  export { default as PathShell } from "./PathShell.svelte";
370
-