@daltonr/pathwrite-solid 0.14.0 → 0.14.1

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/dist/index.js CHANGED
@@ -1,281 +1,572 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "solid-js/jsx-runtime";
2
- import { createSignal, createMemo, untrack, onCleanup, onMount, createContext, useContext, createEffect, on, For, Show, } from "solid-js";
3
- import { PathEngine, formatFieldKey, errorPhaseMessage, } from "@daltonr/pathwrite-core";
4
- // ---------------------------------------------------------------------------
5
- // usePath composable
6
- // ---------------------------------------------------------------------------
7
- export function usePath(options) {
8
- let ownEngine = null;
9
- const resolveEngine = () => {
10
- const external = typeof options?.engine === "function" ? options.engine() : options?.engine;
11
- return external ?? (ownEngine ??= new PathEngine());
12
- };
13
- let engine = resolveEngine();
14
- const [snapshot, setSnapshot] = createSignal(engine.snapshot(),
1
+ import { delegateEvents, createComponent, getNextElement, getNextMarker, insert, memo, runHydrationEvents, effect, className, setStyleProperty, addEventListener, setProperty, template } from "solid-js/web";
2
+ import { createContext, createSignal, createEffect, on, onCleanup, useContext, onMount, createMemo, Show, For, untrack } from "solid-js";
3
+ import { PathEngine, formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
4
+ import { PathEngine as PathEngine2 } from "@daltonr/pathwrite-core";
5
+ var _tmpl$ = /* @__PURE__ */ template(`<div class=pw-shell__body>`), _tmpl$2 = /* @__PURE__ */ template(`<ul class=pw-shell__validation>`), _tmpl$3 = /* @__PURE__ */ template(`<ul class=pw-shell__warnings>`), _tmpl$4 = /* @__PURE__ */ template(`<p class=pw-shell__blocking-error>`), _tmpl$5 = /* @__PURE__ */ template(`<div><!$><!/><!$><!/><!$><!/><!$><!/>`), _tmpl$6 = /* @__PURE__ */ template(`<button type=button class=pw-shell__start-btn>Start`), _tmpl$7 = /* @__PURE__ */ template(`<div class=pw-shell><div class=pw-shell__empty><p>No active path.</p><!$><!/>`), _tmpl$8 = /* @__PURE__ */ template(`<div class=pw-shell__completion><p class=pw-shell__completion-message>All done.</p><button type=button class=pw-shell__completion-restart>Start over`), _tmpl$9 = /* @__PURE__ */ template(`<span class=pw-shell__validation-label>`), _tmpl$0 = /* @__PURE__ */ template(`<li class=pw-shell__validation-item><!$><!/><!$><!/>`), _tmpl$1 = /* @__PURE__ */ template(`<span class=pw-shell__warnings-label>`), _tmpl$10 = /* @__PURE__ */ template(`<li class=pw-shell__warnings-item><!$><!/><!$><!/>`), _tmpl$11 = /* @__PURE__ */ template(`<div class=pw-shell__root-progress><div class=pw-shell__steps></div><div class=pw-shell__track><div class=pw-shell__track-fill>`), _tmpl$12 = /* @__PURE__ */ template(`<div><span class=pw-shell__step-dot></span><span class=pw-shell__step-label>`), _tmpl$13 = /* @__PURE__ */ template(`<div class=pw-shell__header><div class=pw-shell__steps></div><div class=pw-shell__track><div class=pw-shell__track-fill>`), _tmpl$14 = /* @__PURE__ */ template(`<button type=button class="pw-shell__btn pw-shell__btn--retry">Try again`), _tmpl$15 = /* @__PURE__ */ template(`<button type=button>Save and come back later`), _tmpl$16 = /* @__PURE__ */ template(`<div class=pw-shell__error><div class=pw-shell__error-title></div><div class=pw-shell__error-message><!$><!/><!$><!/></div><div class=pw-shell__error-actions><!$><!/><!$><!/><!$><!/>`), _tmpl$17 = /* @__PURE__ */ template(`<button type=button class="pw-shell__btn pw-shell__btn--cancel">`), _tmpl$18 = /* @__PURE__ */ template(`<button type=button class="pw-shell__btn pw-shell__btn--back">`), _tmpl$19 = /* @__PURE__ */ template(`<div class=pw-shell__footer><div class=pw-shell__footer-left><!$><!/><!$><!/></div><div class=pw-shell__footer-right><!$><!/><button type=button>`);
6
+ function usePath(options) {
7
+ let ownEngine = null;
8
+ const resolveEngine = () => {
9
+ const external = typeof options?.engine === "function" ? options.engine() : options?.engine;
10
+ return external ?? (ownEngine ??= new PathEngine());
11
+ };
12
+ let engine = resolveEngine();
13
+ const [snapshot, setSnapshot] = createSignal(
14
+ engine.snapshot(),
15
15
  // always notify — PathEngine produces new snapshot objects on every event
16
- { equals: false });
17
- const onEngineEvent = (event) => {
18
- if (event.type === "stateChanged" || event.type === "resumed") {
19
- setSnapshot(event.snapshot);
20
- }
21
- else if (event.type === "completed" || event.type === "cancelled") {
22
- setSnapshot(engine.snapshot());
23
- }
24
- options?.onEvent?.(event);
25
- };
26
- let unsubscribe = engine.subscribe(onEngineEvent);
27
- // Adopt a late or swapped engine: re-subscribe and re-seed the snapshot.
28
- createEffect(on(resolveEngine, (next) => {
29
- if (next === engine)
30
- return;
31
- unsubscribe();
32
- engine = next;
33
- setSnapshot(engine.snapshot());
34
- unsubscribe = engine.subscribe(onEngineEvent);
35
- }, { defer: true }));
36
- onCleanup(() => unsubscribe());
37
- const start = (path, initialData = {}) => engine.start(path, initialData);
38
- const startSubPath = (path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta);
39
- const next = () => engine.next();
40
- const previous = () => engine.previous();
41
- const cancel = () => engine.cancel();
42
- const goToStep = (stepId, options) => engine.goToStep(stepId, options);
43
- const goToStepChecked = (stepId, options) => engine.goToStepChecked(stepId, options);
44
- const setData = (key, value) => engine.setData(key, value);
45
- const resetStep = () => engine.resetStep();
46
- const restart = () => engine.restart();
47
- const retry = () => engine.retry();
48
- const suspend = () => engine.suspend();
49
- const validate = () => engine.validate();
50
- return {
51
- snapshot,
52
- start,
53
- startSubPath,
54
- next,
55
- previous,
56
- cancel,
57
- goToStep,
58
- goToStepChecked,
59
- setData,
60
- resetStep,
61
- restart,
62
- retry,
63
- suspend,
64
- validate,
65
- };
66
- }
67
- const PathContext = createContext(undefined);
68
- /**
69
- * Access the nearest `PathShell`'s path instance and optional services object.
70
- * Throws if used outside of a PathShell component.
71
- *
72
- * Both generics are type-level assertions, not runtime guarantees:
73
- * - `TData` narrows `snapshot().data`
74
- * - `TServices` types the `services` value — must match what was passed to `PathShell`
75
- */
76
- export function usePathContext() {
77
- const ctx = useContext(PathContext);
78
- if (!ctx) {
79
- throw new Error("usePathContext must be used within a PathShell component.");
16
+ {
17
+ equals: false
80
18
  }
81
- return {
82
- ...ctx.path,
83
- services: ctx.services,
84
- };
19
+ );
20
+ const onEngineEvent = (event) => {
21
+ if (event.type === "stateChanged" || event.type === "resumed") {
22
+ setSnapshot(event.snapshot);
23
+ } else if (event.type === "completed" || event.type === "cancelled") {
24
+ setSnapshot(engine.snapshot());
25
+ }
26
+ options?.onEvent?.(event);
27
+ };
28
+ let unsubscribe = engine.subscribe(onEngineEvent);
29
+ createEffect(on(resolveEngine, (next2) => {
30
+ if (next2 === engine) return;
31
+ unsubscribe();
32
+ engine = next2;
33
+ setSnapshot(engine.snapshot());
34
+ unsubscribe = engine.subscribe(onEngineEvent);
35
+ }, {
36
+ defer: true
37
+ }));
38
+ onCleanup(() => unsubscribe());
39
+ const start = (path, initialData = {}) => engine.start(path, initialData);
40
+ const startSubPath = (path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta);
41
+ const next = () => engine.next();
42
+ const previous = () => engine.previous();
43
+ const cancel = () => engine.cancel();
44
+ const goToStep = (stepId, options2) => engine.goToStep(stepId, options2);
45
+ const goToStepChecked = (stepId, options2) => engine.goToStepChecked(stepId, options2);
46
+ const setData = (key, value) => engine.setData(key, value);
47
+ const resetStep = () => engine.resetStep();
48
+ const restart = () => engine.restart();
49
+ const retry = () => engine.retry();
50
+ const suspend = () => engine.suspend();
51
+ const validate = () => engine.validate();
52
+ return {
53
+ snapshot,
54
+ start,
55
+ startSubPath,
56
+ next,
57
+ previous,
58
+ cancel,
59
+ goToStep,
60
+ goToStepChecked,
61
+ setData,
62
+ resetStep,
63
+ restart,
64
+ retry,
65
+ suspend,
66
+ validate
67
+ };
68
+ }
69
+ const PathContext = createContext(void 0);
70
+ function usePathContext() {
71
+ const ctx = useContext(PathContext);
72
+ if (!ctx) {
73
+ throw new Error("usePathContext must be used within a PathShell component.");
74
+ }
75
+ return {
76
+ ...ctx.path,
77
+ services: ctx.services
78
+ };
85
79
  }
86
- export const PathShell = (props) => {
87
- // Read outer PathShell context BEFORE providing our own.
88
- const outerCtx = useContext(PathContext);
89
- // When remounting under restoreKey, rebuild the inner engine from the state the
90
- // previous instance exported, instead of starting the path and jumping to the
91
- // step (which re-ran onEnter/onLeave and lost attempted / visited state).
92
- const restoredEngine = (() => {
93
- if (props.engine || !props.restoreKey || !outerCtx)
94
- return null;
80
+ const PathShell = (props) => {
81
+ const outerCtx = useContext(PathContext);
82
+ const restoredEngine = (() => {
83
+ if (props.engine || !props.restoreKey || !outerCtx) return null;
84
+ const stored = outerCtx.path.snapshot()?.data[props.restoreKey];
85
+ if (!stored || typeof stored !== "object" || !stored.serializedState) return null;
86
+ try {
87
+ return PathEngine.fromState(stored.serializedState, {
88
+ [props.path.id]: props.path
89
+ });
90
+ } catch {
91
+ return null;
92
+ }
93
+ })();
94
+ const ownEngine = restoredEngine ?? new PathEngine();
95
+ const currentEngine = () => props.engine ?? ownEngine;
96
+ const pathReturn = usePath({
97
+ engine: currentEngine,
98
+ onEvent(event) {
99
+ props.onEvent?.(event);
100
+ if (event.type === "completed") props.onComplete?.(event.data);
101
+ if (event.type === "cancelled") props.onCancel?.(event.data);
102
+ if (props.restoreKey && outerCtx && event.type === "stateChanged") {
103
+ void outerCtx.path.setData(props.restoreKey, {
104
+ ...event.snapshot,
105
+ serializedState: currentEngine().exportState()
106
+ });
107
+ }
108
+ }
109
+ });
110
+ const {
111
+ snapshot,
112
+ start,
113
+ next,
114
+ previous,
115
+ cancel,
116
+ goToStep,
117
+ goToStepChecked,
118
+ setData,
119
+ restart,
120
+ retry,
121
+ suspend,
122
+ validate
123
+ } = pathReturn;
124
+ onMount(() => {
125
+ if (props.autoStart !== false && !props.engine && !restoredEngine) {
126
+ let startData = props.initialData ?? {};
127
+ let restoreStepId;
128
+ if (props.restoreKey && outerCtx) {
95
129
  const stored = outerCtx.path.snapshot()?.data[props.restoreKey];
96
- if (!stored || typeof stored !== "object" || !stored.serializedState)
97
- return null;
98
- try {
99
- return PathEngine.fromState(stored.serializedState, { [props.path.id]: props.path });
100
- }
101
- catch {
102
- return null; // unusable state (e.g. the path definition changed): start fresh below
130
+ if (stored != null && typeof stored === "object" && "stepId" in stored) {
131
+ startData = stored.data;
132
+ if (stored.stepIndex > 0) restoreStepId = stored.stepId;
103
133
  }
104
- })();
105
- // The shell's own engine is created once; an `engine` prop — present at
106
- // mount or arriving later — always takes precedence and is adopted by usePath.
107
- const ownEngine = restoredEngine ?? new PathEngine();
108
- const currentEngine = () => props.engine ?? ownEngine;
109
- const pathReturn = usePath({
110
- engine: currentEngine,
111
- onEvent(event) {
112
- props.onEvent?.(event);
113
- if (event.type === "completed")
114
- props.onComplete?.(event.data);
115
- if (event.type === "cancelled")
116
- props.onCancel?.(event.data);
117
- if (props.restoreKey && outerCtx && event.type === "stateChanged") {
118
- void outerCtx.path.setData(props.restoreKey, {
119
- ...event.snapshot,
120
- serializedState: currentEngine().exportState(),
121
- });
122
- }
123
- },
134
+ }
135
+ const p = start(props.path, startData);
136
+ if (restoreStepId) {
137
+ p.then(() => goToStep(restoreStepId));
138
+ }
139
+ }
140
+ });
141
+ createEffect(() => {
142
+ if (props.validateWhen) validate();
143
+ });
144
+ const contextValue = {
145
+ path: pathReturn,
146
+ services: props.services ?? null
147
+ };
148
+ const actions = {
149
+ next,
150
+ previous,
151
+ cancel,
152
+ goToStep,
153
+ goToStepChecked,
154
+ setData,
155
+ restart: () => restart(),
156
+ retry: () => retry(),
157
+ suspend: () => suspend()
158
+ };
159
+ const snap = () => snapshot();
160
+ const shellClass = () => {
161
+ const base = "pw-shell";
162
+ const layout = props.progressLayout;
163
+ const mod = layout && layout !== "merged" ? ` pw-shell--progress-${layout}` : "";
164
+ return props.class ? `${base}${mod} ${props.class}` : `${base}${mod}`;
165
+ };
166
+ const effectiveHideProgress = () => props.hideProgress || props.layout === "tabs";
167
+ const effectiveHideFooter = () => props.hideFooter || props.layout === "tabs";
168
+ const showRoot = () => !effectiveHideProgress() && !!snap().rootProgress && props.progressLayout !== "activeOnly";
169
+ const showActive = () => !effectiveHideProgress() && (props.renderHeader ? true : (snap().stepCount > 1 || snap().nestingLevel > 0) && props.progressLayout !== "rootOnly");
170
+ const stepLookupKey = createMemo(() => {
171
+ const s = snapshot();
172
+ if (!s) return null;
173
+ if (s.formId && props.steps?.[s.formId]) return s.formId;
174
+ return s.stepId;
175
+ });
176
+ const stepIdentity = createMemo(() => {
177
+ const s = snapshot();
178
+ return s ? `${s.nestingLevel}:${s.pathId}:${s.formId ?? s.stepId}` : null;
179
+ });
180
+ const liveSnapshot = new Proxy({}, {
181
+ get: (_target, key) => snapshot()?.[key],
182
+ has: (_target, key) => {
183
+ const s = snapshot();
184
+ return s ? key in s : false;
185
+ },
186
+ ownKeys: () => {
187
+ const s = snapshot();
188
+ return s ? Reflect.ownKeys(s) : [];
189
+ },
190
+ getOwnPropertyDescriptor: (_target, key) => {
191
+ const s = snapshot();
192
+ const d = s ? Object.getOwnPropertyDescriptor(s, key) : void 0;
193
+ return d ? {
194
+ ...d,
195
+ configurable: true
196
+ } : void 0;
197
+ }
198
+ });
199
+ const StepContent = () => {
200
+ const content = createMemo(() => {
201
+ if (stepIdentity() === null) return null;
202
+ const render = props.steps?.[untrack(stepLookupKey)];
203
+ return render ? untrack(() => render(liveSnapshot)) : null;
124
204
  });
125
- const { snapshot, start, next, previous, cancel, goToStep, goToStepChecked, setData, restart, retry, suspend, validate, } = pathReturn;
126
- onMount(() => {
127
- if (props.autoStart !== false && !props.engine && !restoredEngine) {
128
- let startData = props.initialData ?? {};
129
- let restoreStepId;
130
- if (props.restoreKey && outerCtx) {
131
- const stored = outerCtx.path.snapshot()?.data[props.restoreKey];
132
- if (stored != null && typeof stored === "object" && "stepId" in stored) {
133
- startData = stored.data;
134
- if (stored.stepIndex > 0)
135
- restoreStepId = stored.stepId;
205
+ return memo(content);
206
+ };
207
+ const showValidation = () => props.validationDisplay !== "inline" && (snap().hasAttemptedNext || snap().hasValidated) && Object.keys(snap().fieldErrors).length > 0;
208
+ const showWarnings = () => props.validationDisplay !== "inline" && Object.keys(snap().fieldWarnings).length > 0;
209
+ const showBlockingError = () => props.validationDisplay !== "inline" && (snap().hasAttemptedNext || snap().hasValidated) && !!snap().blockingError;
210
+ const resolvedFooterLayout = () => {
211
+ const fl = props.layout ?? "auto";
212
+ if (fl !== "auto" && fl !== "tabs") return fl;
213
+ return snap().stepCount === 1 && snap().nestingLevel === 0 ? "form" : "wizard";
214
+ };
215
+ return createComponent(PathContext.Provider, {
216
+ value: contextValue,
217
+ get children() {
218
+ return createComponent(Show, {
219
+ get when() {
220
+ return snapshot();
221
+ },
222
+ get fallback() {
223
+ return (() => {
224
+ var _el$13 = getNextElement(_tmpl$7), _el$14 = _el$13.firstChild, _el$15 = _el$14.firstChild, _el$17 = _el$15.nextSibling, [_el$18, _co$5] = getNextMarker(_el$17.nextSibling);
225
+ insert(_el$14, createComponent(Show, {
226
+ get when() {
227
+ return props.autoStart === false;
228
+ },
229
+ get children() {
230
+ var _el$16 = getNextElement(_tmpl$6);
231
+ _el$16.$$click = () => start(props.path, props.initialData ?? {});
232
+ runHydrationEvents();
233
+ return _el$16;
234
+ }
235
+ }), _el$18, _co$5);
236
+ return _el$13;
237
+ })();
238
+ },
239
+ get children() {
240
+ var _el$ = getNextElement(_tmpl$5), _el$7 = _el$.firstChild, [_el$8, _co$] = getNextMarker(_el$7.nextSibling), _el$9 = _el$8.nextSibling, [_el$0, _co$2] = getNextMarker(_el$9.nextSibling), _el$1 = _el$0.nextSibling, [_el$10, _co$3] = getNextMarker(_el$1.nextSibling), _el$11 = _el$10.nextSibling, [_el$12, _co$4] = getNextMarker(_el$11.nextSibling);
241
+ insert(_el$, createComponent(Show, {
242
+ get when() {
243
+ return showRoot();
244
+ },
245
+ get children() {
246
+ return createComponent(SolidRootProgress, {
247
+ get root() {
248
+ return snap().rootProgress;
249
+ }
250
+ });
251
+ }
252
+ }), _el$8, _co$);
253
+ insert(_el$, createComponent(Show, {
254
+ get when() {
255
+ return showActive();
256
+ },
257
+ get children() {
258
+ return memo(() => !!props.renderHeader)() ? props.renderHeader(snap()) : createComponent(SolidHeader, {
259
+ get snapshot() {
260
+ return snap();
136
261
  }
262
+ });
137
263
  }
138
- const p = start(props.path, startData);
139
- if (restoreStepId) {
140
- p.then(() => goToStep(restoreStepId));
264
+ }), _el$0, _co$2);
265
+ insert(_el$, createComponent(Show, {
266
+ get when() {
267
+ return snap().status === "completed";
268
+ },
269
+ get children() {
270
+ var _el$2 = getNextElement(_tmpl$);
271
+ insert(_el$2, (() => {
272
+ var _c$ = memo(() => !!props.completionContent);
273
+ return () => _c$() ? props.completionContent(snap()) : (() => {
274
+ var _el$19 = getNextElement(_tmpl$8), _el$20 = _el$19.firstChild, _el$21 = _el$20.nextSibling;
275
+ _el$21.$$click = () => restart();
276
+ runHydrationEvents();
277
+ return _el$19;
278
+ })();
279
+ })());
280
+ return _el$2;
141
281
  }
282
+ }), _el$10, _co$3);
283
+ insert(_el$, createComponent(Show, {
284
+ get when() {
285
+ return snap().status !== "completed";
286
+ },
287
+ get children() {
288
+ return [(() => {
289
+ var _el$3 = getNextElement(_tmpl$);
290
+ insert(_el$3, createComponent(StepContent, {}));
291
+ return _el$3;
292
+ })(), createComponent(Show, {
293
+ get when() {
294
+ return showValidation();
295
+ },
296
+ get children() {
297
+ var _el$4 = getNextElement(_tmpl$2);
298
+ insert(_el$4, createComponent(For, {
299
+ get each() {
300
+ return Object.entries(snap().fieldErrors);
301
+ },
302
+ children: ([key, msg]) => (() => {
303
+ var _el$22 = getNextElement(_tmpl$0), _el$24 = _el$22.firstChild, [_el$25, _co$6] = getNextMarker(_el$24.nextSibling), _el$26 = _el$25.nextSibling, [_el$27, _co$7] = getNextMarker(_el$26.nextSibling);
304
+ insert(_el$22, createComponent(Show, {
305
+ when: key !== "_",
306
+ get children() {
307
+ var _el$23 = getNextElement(_tmpl$9);
308
+ insert(_el$23, () => formatFieldKey(key));
309
+ return _el$23;
310
+ }
311
+ }), _el$25, _co$6);
312
+ insert(_el$22, msg, _el$27, _co$7);
313
+ return _el$22;
314
+ })()
315
+ }));
316
+ return _el$4;
317
+ }
318
+ }), createComponent(Show, {
319
+ get when() {
320
+ return showWarnings();
321
+ },
322
+ get children() {
323
+ var _el$5 = getNextElement(_tmpl$3);
324
+ insert(_el$5, createComponent(For, {
325
+ get each() {
326
+ return Object.entries(snap().fieldWarnings);
327
+ },
328
+ children: ([key, msg]) => (() => {
329
+ var _el$28 = getNextElement(_tmpl$10), _el$30 = _el$28.firstChild, [_el$31, _co$8] = getNextMarker(_el$30.nextSibling), _el$32 = _el$31.nextSibling, [_el$33, _co$9] = getNextMarker(_el$32.nextSibling);
330
+ insert(_el$28, createComponent(Show, {
331
+ when: key !== "_",
332
+ get children() {
333
+ var _el$29 = getNextElement(_tmpl$1);
334
+ insert(_el$29, () => formatFieldKey(key));
335
+ return _el$29;
336
+ }
337
+ }), _el$31, _co$8);
338
+ insert(_el$28, msg, _el$33, _co$9);
339
+ return _el$28;
340
+ })()
341
+ }));
342
+ return _el$5;
343
+ }
344
+ }), createComponent(Show, {
345
+ get when() {
346
+ return showBlockingError();
347
+ },
348
+ get children() {
349
+ var _el$6 = getNextElement(_tmpl$4);
350
+ insert(_el$6, () => snap().blockingError);
351
+ return _el$6;
352
+ }
353
+ }), createComponent(Show, {
354
+ get when() {
355
+ return memo(() => snap().status === "error")() && snap().error;
356
+ },
357
+ get fallback() {
358
+ return createComponent(Show, {
359
+ get when() {
360
+ return !effectiveHideFooter();
361
+ },
362
+ get children() {
363
+ return memo(() => !!props.renderFooter)() ? props.renderFooter(snap(), actions) : createComponent(SolidFooter, {
364
+ get snapshot() {
365
+ return snap();
366
+ },
367
+ actions,
368
+ get backLabel() {
369
+ return props.backLabel ?? "Previous";
370
+ },
371
+ get nextLabel() {
372
+ return props.nextLabel ?? "Next";
373
+ },
374
+ get completeLabel() {
375
+ return props.completeLabel ?? "Complete";
376
+ },
377
+ get loadingLabel() {
378
+ return props.loadingLabel;
379
+ },
380
+ get cancelLabel() {
381
+ return props.cancelLabel ?? "Cancel";
382
+ },
383
+ get hideCancel() {
384
+ return props.hideCancel ?? false;
385
+ },
386
+ get layout() {
387
+ return resolvedFooterLayout();
388
+ }
389
+ });
390
+ }
391
+ });
392
+ },
393
+ get children() {
394
+ return createComponent(SolidErrorPanel, {
395
+ get snapshot() {
396
+ return snap();
397
+ },
398
+ actions
399
+ });
400
+ }
401
+ })];
402
+ }
403
+ }), _el$12, _co$4);
404
+ effect(() => className(_el$, shellClass()));
405
+ return _el$;
142
406
  }
143
- });
144
- createEffect(() => {
145
- if (props.validateWhen)
146
- validate();
147
- });
148
- const contextValue = { path: pathReturn, services: props.services ?? null };
149
- const actions = {
150
- next,
151
- previous,
152
- cancel,
153
- goToStep,
154
- goToStepChecked,
155
- setData,
156
- restart: () => restart(),
157
- retry: () => retry(),
158
- suspend: () => suspend(),
159
- };
160
- // Convenience — non-null snapshot, only valid inside <Show when={snapshot()}>
161
- const snap = () => snapshot();
162
- const shellClass = () => {
163
- const base = "pw-shell";
164
- const layout = props.progressLayout;
165
- const mod = layout && layout !== "merged" ? ` pw-shell--progress-${layout}` : "";
166
- return props.class ? `${base}${mod} ${props.class}` : `${base}${mod}`;
167
- };
168
- const effectiveHideProgress = () => props.hideProgress || props.layout === "tabs";
169
- const effectiveHideFooter = () => props.hideFooter || props.layout === "tabs";
170
- const showRoot = () => !effectiveHideProgress() && !!snap().rootProgress && props.progressLayout !== "activeOnly";
171
- // A custom header is the consumer's decision: show it whenever progress is
172
- // not hidden, even for a single-step path. Only the *default* header hides
173
- // for one step (same rule as the React / Vue shells).
174
- const showActive = () => !effectiveHideProgress() &&
175
- (props.renderHeader
176
- ? true
177
- : (snap().stepCount > 1 || snap().nestingLevel > 0) && props.progressLayout !== "rootOnly");
178
- // The step render function must only run when the *step* changes. The
179
- // snapshot signal is `{ equals: false }` (a new object on every engine
180
- // event), so reading it in a tracked render position would tear the step
181
- // component down and re-create it on every setData — losing the input's
182
- // DOM node and focus on each keystroke. Key the rendered content on the
183
- // step's identity and create it untracked; live state reaches the step
184
- // through the reactive snapshot proxy below or `usePathContext()`.
185
- // Which key of `props.steps` renders the current step: the StepChoice's
186
- // inner step id (`formId`) when content is registered under it, otherwise
187
- // the slot's own id — the same fallback the React / Vue / Svelte shells use.
188
- const stepLookupKey = createMemo(() => {
189
- const s = snapshot();
190
- if (!s)
191
- return null;
192
- if (s.formId && props.steps?.[s.formId])
193
- return s.formId;
194
- return s.stepId;
195
- });
196
- const stepIdentity = createMemo(() => {
197
- const s = snapshot();
198
- return s ? `${s.nestingLevel}:${s.pathId}:${s.formId ?? s.stepId}` : null;
199
- });
200
- // A snapshot whose property reads go through the signal, so a step that
201
- // received it as a prop (`(snap) => <Step snapshot={snap} />`) sees the
202
- // current values reactively even though the step itself is created once.
203
- const liveSnapshot = new Proxy({}, {
204
- get: (_target, key) => snapshot()?.[key],
205
- has: (_target, key) => {
206
- const s = snapshot();
207
- return s ? key in s : false;
208
- },
209
- ownKeys: () => {
210
- const s = snapshot();
211
- return s ? Reflect.ownKeys(s) : [];
212
- },
213
- getOwnPropertyDescriptor: (_target, key) => {
214
- const s = snapshot();
215
- const d = s ? Object.getOwnPropertyDescriptor(s, key) : undefined;
216
- return d ? { ...d, configurable: true } : undefined;
217
- },
218
- });
219
- // Rendered inside <PathContext.Provider>, so the memo (and the step it
220
- // creates) is owned by the provider and `usePathContext()` resolves.
221
- const StepContent = () => {
222
- const content = createMemo(() => {
223
- if (stepIdentity() === null)
224
- return null;
225
- const render = props.steps?.[untrack(stepLookupKey)];
226
- return render ? untrack(() => render(liveSnapshot)) : null;
227
- });
228
- return _jsx(_Fragment, { children: content() });
229
- };
230
- const showValidation = () => props.validationDisplay !== "inline" &&
231
- (snap().hasAttemptedNext || snap().hasValidated) &&
232
- Object.keys(snap().fieldErrors).length > 0;
233
- const showWarnings = () => props.validationDisplay !== "inline" && Object.keys(snap().fieldWarnings).length > 0;
234
- const showBlockingError = () => props.validationDisplay !== "inline" &&
235
- (snap().hasAttemptedNext || snap().hasValidated) &&
236
- !!snap().blockingError;
237
- const resolvedFooterLayout = () => {
238
- const fl = props.layout ?? "auto";
239
- if (fl !== "auto" && fl !== "tabs")
240
- return fl;
241
- return snap().stepCount === 1 && snap().nestingLevel === 0 ? "form" : "wizard";
242
- };
243
- return (_jsx(PathContext.Provider, { value: contextValue, children: _jsx(Show, { when: snapshot(), fallback: _jsx("div", { class: "pw-shell", children: _jsxs("div", { class: "pw-shell__empty", children: [_jsx("p", { children: "No active path." }), _jsx(Show, { when: props.autoStart === false, children: _jsx("button", { type: "button", class: "pw-shell__start-btn", onClick: () => start(props.path, props.initialData ?? {}), children: "Start" }) })] }) }), children: _jsxs("div", { class: shellClass(), children: [_jsx(Show, { when: showRoot(), children: _jsx(SolidRootProgress, { root: snap().rootProgress }) }), _jsx(Show, { when: showActive(), children: props.renderHeader ? props.renderHeader(snap()) : _jsx(SolidHeader, { snapshot: snap() }) }), _jsx(Show, { when: snap().status === "completed", children: _jsx("div", { class: "pw-shell__body", children: props.completionContent ? (props.completionContent(snap())) : (_jsxs("div", { class: "pw-shell__completion", children: [_jsx("p", { class: "pw-shell__completion-message", children: "All done." }), _jsx("button", { type: "button", class: "pw-shell__completion-restart", onClick: () => restart(), children: "Start over" })] })) }) }), _jsxs(Show, { when: snap().status !== "completed", children: [_jsx("div", { class: "pw-shell__body", children: _jsx(StepContent, {}) }), _jsx(Show, { when: showValidation(), children: _jsx("ul", { class: "pw-shell__validation", children: _jsx(For, { each: Object.entries(snap().fieldErrors), children: ([key, msg]) => (_jsxs("li", { class: "pw-shell__validation-item", children: [_jsx(Show, { when: key !== "_", children: _jsx("span", { class: "pw-shell__validation-label", children: formatFieldKey(key) }) }), msg] })) }) }) }), _jsx(Show, { when: showWarnings(), children: _jsx("ul", { class: "pw-shell__warnings", children: _jsx(For, { each: Object.entries(snap().fieldWarnings), children: ([key, msg]) => (_jsxs("li", { class: "pw-shell__warnings-item", children: [_jsx(Show, { when: key !== "_", children: _jsx("span", { class: "pw-shell__warnings-label", children: formatFieldKey(key) }) }), msg] })) }) }) }), _jsx(Show, { when: showBlockingError(), children: _jsx("p", { class: "pw-shell__blocking-error", children: snap().blockingError }) }), _jsx(Show, { when: snap().status === "error" && snap().error, fallback: _jsx(Show, { when: !effectiveHideFooter(), children: props.renderFooter ? (props.renderFooter(snap(), actions)) : (_jsx(SolidFooter, { snapshot: snap(), actions: actions, backLabel: props.backLabel ?? "Previous", nextLabel: props.nextLabel ?? "Next", completeLabel: props.completeLabel ?? "Complete", loadingLabel: props.loadingLabel, cancelLabel: props.cancelLabel ?? "Cancel", hideCancel: props.hideCancel ?? false, layout: resolvedFooterLayout() })) }), children: _jsx(SolidErrorPanel, { snapshot: snap(), actions: actions }) })] })] }) }) }));
407
+ });
408
+ }
409
+ });
244
410
  };
245
- // ---------------------------------------------------------------------------
246
- // Root progress
247
- // ---------------------------------------------------------------------------
248
411
  function SolidRootProgress(props) {
249
- return (_jsxs("div", { class: "pw-shell__root-progress", children: [_jsx("div", { class: "pw-shell__steps", children: _jsx(For, { each: props.root.steps, children: (step, i) => (_jsxs("div", { class: `pw-shell__step pw-shell__step--${step.status}`, children: [_jsx("span", { class: "pw-shell__step-dot", children: step.status === "completed" ? "✓" : String(i() + 1) }), _jsx("span", { class: "pw-shell__step-label", children: step.title ?? step.id })] })) }) }), _jsx("div", { class: "pw-shell__track", children: _jsx("div", { class: "pw-shell__track-fill", style: { width: `${props.root.progress * 100}%` } }) })] }));
412
+ return (() => {
413
+ var _el$34 = getNextElement(_tmpl$11), _el$35 = _el$34.firstChild, _el$36 = _el$35.nextSibling, _el$37 = _el$36.firstChild;
414
+ insert(_el$35, createComponent(For, {
415
+ get each() {
416
+ return props.root.steps;
417
+ },
418
+ children: (step, i) => (() => {
419
+ var _el$38 = getNextElement(_tmpl$12), _el$39 = _el$38.firstChild, _el$40 = _el$39.nextSibling;
420
+ insert(_el$39, (() => {
421
+ var _c$2 = memo(() => step.status === "completed");
422
+ return () => _c$2() ? "✓" : String(i() + 1);
423
+ })());
424
+ insert(_el$40, () => step.title ?? step.id);
425
+ effect(() => className(_el$38, `pw-shell__step pw-shell__step--${step.status}`));
426
+ return _el$38;
427
+ })()
428
+ }));
429
+ effect((_$p) => setStyleProperty(_el$37, "width", `${props.root.progress * 100}%`));
430
+ return _el$34;
431
+ })();
250
432
  }
251
- // ---------------------------------------------------------------------------
252
- // Default header (progress indicator)
253
- // ---------------------------------------------------------------------------
254
433
  function SolidHeader(props) {
255
- return (_jsxs("div", { class: "pw-shell__header", children: [_jsx("div", { class: "pw-shell__steps", children: _jsx(For, { each: props.snapshot.steps, children: (step, i) => (_jsxs("div", { class: `pw-shell__step pw-shell__step--${step.status}`, children: [_jsx("span", { class: "pw-shell__step-dot", children: step.status === "completed" ? "✓" : String(i() + 1) }), _jsx("span", { class: "pw-shell__step-label", children: step.title ?? step.id })] })) }) }), _jsx("div", { class: "pw-shell__track", children: _jsx("div", { class: "pw-shell__track-fill", style: { width: `${props.snapshot.progress * 100}%` } }) })] }));
434
+ return (() => {
435
+ var _el$41 = getNextElement(_tmpl$13), _el$42 = _el$41.firstChild, _el$43 = _el$42.nextSibling, _el$44 = _el$43.firstChild;
436
+ insert(_el$42, createComponent(For, {
437
+ get each() {
438
+ return props.snapshot.steps;
439
+ },
440
+ children: (step, i) => (() => {
441
+ var _el$45 = getNextElement(_tmpl$12), _el$46 = _el$45.firstChild, _el$47 = _el$46.nextSibling;
442
+ insert(_el$46, (() => {
443
+ var _c$3 = memo(() => step.status === "completed");
444
+ return () => _c$3() ? "✓" : String(i() + 1);
445
+ })());
446
+ insert(_el$47, () => step.title ?? step.id);
447
+ effect(() => className(_el$45, `pw-shell__step pw-shell__step--${step.status}`));
448
+ return _el$45;
449
+ })()
450
+ }));
451
+ effect((_$p) => setStyleProperty(_el$44, "width", `${props.snapshot.progress * 100}%`));
452
+ return _el$41;
453
+ })();
256
454
  }
257
- // ---------------------------------------------------------------------------
258
- // Error panel
259
- // ---------------------------------------------------------------------------
260
455
  function SolidErrorPanel(props) {
261
- const error = () => props.snapshot.error;
262
- const escalated = () => error().retryCount >= 2;
263
- const title = () => (escalated() ? "Still having trouble." : "Something went wrong.");
264
- const phaseMsg = () => errorPhaseMessage(error().phase);
265
- return (_jsxs("div", { class: "pw-shell__error", children: [_jsx("div", { class: "pw-shell__error-title", children: title() }), _jsxs("div", { class: "pw-shell__error-message", children: [phaseMsg(), error().message ? ` ${error().message}` : ""] }), _jsxs("div", { class: "pw-shell__error-actions", children: [_jsx(Show, { when: !escalated(), children: _jsx("button", { type: "button", class: "pw-shell__btn pw-shell__btn--retry", onClick: props.actions.retry, children: "Try again" }) }), _jsx(Show, { when: props.snapshot.hasPersistence, children: _jsx("button", { type: "button", class: `pw-shell__btn ${escalated() ? "pw-shell__btn--retry" : "pw-shell__btn--suspend"}`, onClick: props.actions.suspend, children: "Save and come back later" }) }), _jsx(Show, { when: escalated() && !props.snapshot.hasPersistence, children: _jsx("button", { type: "button", class: "pw-shell__btn pw-shell__btn--retry", onClick: props.actions.retry, children: "Try again" }) })] })] }));
456
+ const error = () => props.snapshot.error;
457
+ const escalated = () => error().retryCount >= 2;
458
+ const title = () => escalated() ? "Still having trouble." : "Something went wrong.";
459
+ const phaseMsg = () => errorPhaseMessage(error().phase);
460
+ return (() => {
461
+ var _el$48 = getNextElement(_tmpl$16), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.firstChild, [_el$52, _co$0] = getNextMarker(_el$51.nextSibling), _el$53 = _el$52.nextSibling, [_el$54, _co$1] = getNextMarker(_el$53.nextSibling), _el$55 = _el$50.nextSibling, _el$59 = _el$55.firstChild, [_el$60, _co$10] = getNextMarker(_el$59.nextSibling), _el$61 = _el$60.nextSibling, [_el$62, _co$11] = getNextMarker(_el$61.nextSibling), _el$63 = _el$62.nextSibling, [_el$64, _co$12] = getNextMarker(_el$63.nextSibling);
462
+ insert(_el$49, title);
463
+ insert(_el$50, phaseMsg, _el$52, _co$0);
464
+ insert(_el$50, (() => {
465
+ var _c$4 = memo(() => !!error().message);
466
+ return () => _c$4() ? ` ${error().message}` : "";
467
+ })(), _el$54, _co$1);
468
+ insert(_el$55, createComponent(Show, {
469
+ get when() {
470
+ return !escalated();
471
+ },
472
+ get children() {
473
+ var _el$56 = getNextElement(_tmpl$14);
474
+ addEventListener(_el$56, "click", props.actions.retry, true);
475
+ runHydrationEvents();
476
+ return _el$56;
477
+ }
478
+ }), _el$60, _co$10);
479
+ insert(_el$55, createComponent(Show, {
480
+ get when() {
481
+ return props.snapshot.hasPersistence;
482
+ },
483
+ get children() {
484
+ var _el$57 = getNextElement(_tmpl$15);
485
+ addEventListener(_el$57, "click", props.actions.suspend, true);
486
+ effect(() => className(_el$57, `pw-shell__btn ${escalated() ? "pw-shell__btn--retry" : "pw-shell__btn--suspend"}`));
487
+ runHydrationEvents();
488
+ return _el$57;
489
+ }
490
+ }), _el$62, _co$11);
491
+ insert(_el$55, createComponent(Show, {
492
+ get when() {
493
+ return memo(() => !!escalated())() && !props.snapshot.hasPersistence;
494
+ },
495
+ get children() {
496
+ var _el$58 = getNextElement(_tmpl$14);
497
+ addEventListener(_el$58, "click", props.actions.retry, true);
498
+ runHydrationEvents();
499
+ return _el$58;
500
+ }
501
+ }), _el$64, _co$12);
502
+ return _el$48;
503
+ })();
266
504
  }
267
- // ---------------------------------------------------------------------------
268
- // Default footer (navigation buttons)
269
- // ---------------------------------------------------------------------------
270
505
  function SolidFooter(props) {
271
- const isFormMode = () => props.layout === "form";
272
- const isLoading = () => props.snapshot.status !== "idle";
273
- const submitLabel = () => isLoading() && props.loadingLabel
274
- ? props.loadingLabel
275
- : props.snapshot.isLastStep
276
- ? props.completeLabel
277
- : props.nextLabel;
278
- return (_jsxs("div", { class: "pw-shell__footer", children: [_jsxs("div", { class: "pw-shell__footer-left", children: [_jsx(Show, { when: isFormMode() && !props.hideCancel, children: _jsx("button", { type: "button", class: "pw-shell__btn pw-shell__btn--cancel", disabled: isLoading(), onClick: props.actions.cancel, children: props.cancelLabel }) }), _jsx(Show, { when: !isFormMode() && !props.snapshot.isFirstStep, children: _jsx("button", { type: "button", class: "pw-shell__btn pw-shell__btn--back", disabled: isLoading() || !props.snapshot.canMovePrevious, onClick: props.actions.previous, children: props.backLabel }) })] }), _jsxs("div", { class: "pw-shell__footer-right", children: [_jsx(Show, { when: !isFormMode() && !props.hideCancel, children: _jsx("button", { type: "button", class: "pw-shell__btn pw-shell__btn--cancel", disabled: isLoading(), onClick: props.actions.cancel, children: props.cancelLabel }) }), _jsx("button", { type: "button", class: `pw-shell__btn pw-shell__btn--next${isLoading() ? " pw-shell__btn--loading" : ""}`, disabled: isLoading(), onClick: props.actions.next, children: submitLabel() })] })] }));
506
+ const isFormMode = () => props.layout === "form";
507
+ const isLoading = () => props.snapshot.status !== "idle";
508
+ const submitLabel = () => isLoading() && props.loadingLabel ? props.loadingLabel : props.snapshot.isLastStep ? props.completeLabel : props.nextLabel;
509
+ return (() => {
510
+ var _el$65 = getNextElement(_tmpl$19), _el$66 = _el$65.firstChild, _el$69 = _el$66.firstChild, [_el$70, _co$13] = getNextMarker(_el$69.nextSibling), _el$71 = _el$70.nextSibling, [_el$72, _co$14] = getNextMarker(_el$71.nextSibling), _el$73 = _el$66.nextSibling, _el$76 = _el$73.firstChild, [_el$77, _co$15] = getNextMarker(_el$76.nextSibling), _el$75 = _el$77.nextSibling;
511
+ insert(_el$66, createComponent(Show, {
512
+ get when() {
513
+ return memo(() => !!isFormMode())() && !props.hideCancel;
514
+ },
515
+ get children() {
516
+ var _el$67 = getNextElement(_tmpl$17);
517
+ addEventListener(_el$67, "click", props.actions.cancel, true);
518
+ insert(_el$67, () => props.cancelLabel);
519
+ effect(() => setProperty(_el$67, "disabled", isLoading()));
520
+ runHydrationEvents();
521
+ return _el$67;
522
+ }
523
+ }), _el$70, _co$13);
524
+ insert(_el$66, createComponent(Show, {
525
+ get when() {
526
+ return memo(() => !!!isFormMode())() && !props.snapshot.isFirstStep;
527
+ },
528
+ get children() {
529
+ var _el$68 = getNextElement(_tmpl$18);
530
+ addEventListener(_el$68, "click", props.actions.previous, true);
531
+ insert(_el$68, () => props.backLabel);
532
+ effect(() => setProperty(_el$68, "disabled", isLoading() || !props.snapshot.canMovePrevious));
533
+ runHydrationEvents();
534
+ return _el$68;
535
+ }
536
+ }), _el$72, _co$14);
537
+ insert(_el$73, createComponent(Show, {
538
+ get when() {
539
+ return memo(() => !!!isFormMode())() && !props.hideCancel;
540
+ },
541
+ get children() {
542
+ var _el$74 = getNextElement(_tmpl$17);
543
+ addEventListener(_el$74, "click", props.actions.cancel, true);
544
+ insert(_el$74, () => props.cancelLabel);
545
+ effect(() => setProperty(_el$74, "disabled", isLoading()));
546
+ runHydrationEvents();
547
+ return _el$74;
548
+ }
549
+ }), _el$77, _co$15);
550
+ addEventListener(_el$75, "click", props.actions.next, true);
551
+ insert(_el$75, submitLabel);
552
+ effect((_p$) => {
553
+ var _v$ = `pw-shell__btn pw-shell__btn--next${isLoading() ? " pw-shell__btn--loading" : ""}`, _v$2 = isLoading();
554
+ _v$ !== _p$.e && className(_el$75, _p$.e = _v$);
555
+ _v$2 !== _p$.t && setProperty(_el$75, "disabled", _p$.t = _v$2);
556
+ return _p$;
557
+ }, {
558
+ e: void 0,
559
+ t: void 0
560
+ });
561
+ runHydrationEvents();
562
+ return _el$65;
563
+ })();
279
564
  }
280
- export { PathEngine } from "@daltonr/pathwrite-core";
281
- //# sourceMappingURL=index.js.map
565
+ delegateEvents(["click"]);
566
+ export {
567
+ PathEngine2 as PathEngine,
568
+ PathShell,
569
+ usePath,
570
+ usePathContext
571
+ };
572
+ //# sourceMappingURL=index.js.map