@daltonr/pathwrite-svelte 0.5.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/LICENSE +22 -0
- package/README.md +468 -0
- package/dist/PathShell.svelte +181 -0
- package/dist/PathShell.svelte.d.ts +24 -0
- package/dist/PathShell.svelte.d.ts.map +1 -0
- package/dist/index.css +272 -0
- package/dist/index.svelte.d.ts +130 -0
- package/dist/index.svelte.d.ts.map +1 -0
- package/dist/index.svelte.js +141 -0
- package/package.json +62 -0
- package/src/PathShell.svelte +181 -0
- package/src/index.svelte.ts +254 -0
package/dist/index.css
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pathwrite — Default Shell Stylesheet
|
|
3
|
+
*
|
|
4
|
+
* Optional CSS for the <PathShell> / <pw-shell> default UI components.
|
|
5
|
+
* Import this file if you want sensible out-of-the-box styling.
|
|
6
|
+
* Every visual value is a CSS custom property (--pw-*) so you can
|
|
7
|
+
* theme the shell without overriding selectors.
|
|
8
|
+
*
|
|
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
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
/* Custom property defaults */
|
|
18
|
+
/* ------------------------------------------------------------------ */
|
|
19
|
+
:root {
|
|
20
|
+
/* Layout */
|
|
21
|
+
--pw-shell-max-width: 720px;
|
|
22
|
+
--pw-shell-padding: 24px;
|
|
23
|
+
--pw-shell-gap: 20px;
|
|
24
|
+
--pw-shell-radius: 10px;
|
|
25
|
+
|
|
26
|
+
/* Colours */
|
|
27
|
+
--pw-color-bg: #ffffff;
|
|
28
|
+
--pw-color-border: #dbe4f0;
|
|
29
|
+
--pw-color-text: #1f2937;
|
|
30
|
+
--pw-color-muted: #5b677a;
|
|
31
|
+
--pw-color-primary: #2563eb;
|
|
32
|
+
--pw-color-primary-light: rgba(37, 99, 235, 0.12);
|
|
33
|
+
--pw-color-btn-bg: #f8fbff;
|
|
34
|
+
--pw-color-btn-border: #c2d0e5;
|
|
35
|
+
|
|
36
|
+
/* Progress */
|
|
37
|
+
--pw-dot-size: 32px;
|
|
38
|
+
--pw-dot-font-size: 13px;
|
|
39
|
+
--pw-track-height: 4px;
|
|
40
|
+
|
|
41
|
+
/* Buttons */
|
|
42
|
+
--pw-btn-padding: 8px 16px;
|
|
43
|
+
--pw-btn-radius: 6px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ------------------------------------------------------------------ */
|
|
47
|
+
/* Shell root */
|
|
48
|
+
/* ------------------------------------------------------------------ */
|
|
49
|
+
.pw-shell {
|
|
50
|
+
max-width: var(--pw-shell-max-width);
|
|
51
|
+
margin: 0 auto;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: var(--pw-shell-gap);
|
|
55
|
+
padding: var(--pw-shell-padding);
|
|
56
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
57
|
+
color: var(--pw-color-text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ------------------------------------------------------------------ */
|
|
61
|
+
/* Empty state */
|
|
62
|
+
/* ------------------------------------------------------------------ */
|
|
63
|
+
.pw-shell__empty {
|
|
64
|
+
text-align: center;
|
|
65
|
+
padding: 32px 16px;
|
|
66
|
+
color: var(--pw-color-muted);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.pw-shell__start-btn {
|
|
70
|
+
margin-top: 12px;
|
|
71
|
+
border: 1px solid var(--pw-color-btn-border);
|
|
72
|
+
background: var(--pw-color-btn-bg);
|
|
73
|
+
color: var(--pw-color-text);
|
|
74
|
+
padding: var(--pw-btn-padding);
|
|
75
|
+
border-radius: var(--pw-btn-radius);
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ------------------------------------------------------------------ */
|
|
81
|
+
/* Header — progress indicator */
|
|
82
|
+
/* ------------------------------------------------------------------ */
|
|
83
|
+
.pw-shell__header {
|
|
84
|
+
background: var(--pw-color-bg);
|
|
85
|
+
border: 1px solid var(--pw-color-border);
|
|
86
|
+
border-radius: var(--pw-shell-radius);
|
|
87
|
+
padding: 20px 24px 16px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.pw-shell__steps {
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: space-between;
|
|
93
|
+
margin-bottom: 12px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.pw-shell__step {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 6px;
|
|
101
|
+
flex: 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.pw-shell__step-dot {
|
|
105
|
+
width: var(--pw-dot-size);
|
|
106
|
+
height: var(--pw-dot-size);
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
font-size: var(--pw-dot-font-size);
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
border: 2px solid var(--pw-color-border);
|
|
114
|
+
background: var(--pw-color-bg);
|
|
115
|
+
color: var(--pw-color-muted);
|
|
116
|
+
transition: all 0.25s ease;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.pw-shell__step--completed .pw-shell__step-dot {
|
|
120
|
+
background: var(--pw-color-primary);
|
|
121
|
+
border-color: var(--pw-color-primary);
|
|
122
|
+
color: #fff;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.pw-shell__step--current .pw-shell__step-dot {
|
|
126
|
+
border-color: var(--pw-color-primary);
|
|
127
|
+
color: var(--pw-color-primary);
|
|
128
|
+
box-shadow: 0 0 0 3px var(--pw-color-primary-light);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.pw-shell__step-label {
|
|
132
|
+
font-size: 12px;
|
|
133
|
+
color: var(--pw-color-muted);
|
|
134
|
+
text-align: center;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.pw-shell__step--current .pw-shell__step-label {
|
|
139
|
+
color: var(--pw-color-primary);
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.pw-shell__step--completed .pw-shell__step-label {
|
|
144
|
+
color: var(--pw-color-text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Track / fill */
|
|
148
|
+
.pw-shell__track {
|
|
149
|
+
height: var(--pw-track-height);
|
|
150
|
+
background: var(--pw-color-border);
|
|
151
|
+
border-radius: calc(var(--pw-track-height) / 2);
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.pw-shell__track-fill {
|
|
156
|
+
height: 100%;
|
|
157
|
+
background: var(--pw-color-primary);
|
|
158
|
+
border-radius: calc(var(--pw-track-height) / 2);
|
|
159
|
+
transition: width 0.3s ease;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ------------------------------------------------------------------ */
|
|
163
|
+
/* Body — step content */
|
|
164
|
+
/* ------------------------------------------------------------------ */
|
|
165
|
+
.pw-shell__body {
|
|
166
|
+
background: var(--pw-color-bg);
|
|
167
|
+
border: 1px solid var(--pw-color-border);
|
|
168
|
+
border-radius: var(--pw-shell-radius);
|
|
169
|
+
padding: var(--pw-shell-padding);
|
|
170
|
+
min-height: 120px;
|
|
171
|
+
}
|
|
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
|
+
|
|
207
|
+
/* ------------------------------------------------------------------ */
|
|
208
|
+
/* Footer — navigation buttons */
|
|
209
|
+
/* ------------------------------------------------------------------ */
|
|
210
|
+
.pw-shell__footer {
|
|
211
|
+
display: flex;
|
|
212
|
+
justify-content: space-between;
|
|
213
|
+
align-items: center;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.pw-shell__footer-left,
|
|
217
|
+
.pw-shell__footer-right {
|
|
218
|
+
display: flex;
|
|
219
|
+
gap: 8px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.pw-shell__btn {
|
|
223
|
+
border: 1px solid var(--pw-color-btn-border);
|
|
224
|
+
background: var(--pw-color-btn-bg);
|
|
225
|
+
color: var(--pw-color-text);
|
|
226
|
+
padding: var(--pw-btn-padding);
|
|
227
|
+
border-radius: var(--pw-btn-radius);
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
font-size: 14px;
|
|
230
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.pw-shell__btn:hover:not(:disabled) {
|
|
234
|
+
background: var(--pw-color-border);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.pw-shell__btn:disabled {
|
|
238
|
+
opacity: 0.5;
|
|
239
|
+
cursor: not-allowed;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.pw-shell__btn--next {
|
|
243
|
+
background: var(--pw-color-primary);
|
|
244
|
+
border-color: var(--pw-color-primary);
|
|
245
|
+
color: #fff;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.pw-shell__btn--next:hover:not(:disabled) {
|
|
249
|
+
background: #1d4ed8;
|
|
250
|
+
border-color: #1d4ed8;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.pw-shell__btn--back {
|
|
254
|
+
background: transparent;
|
|
255
|
+
border-color: var(--pw-color-primary);
|
|
256
|
+
color: var(--pw-color-primary);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.pw-shell__btn--back:hover:not(:disabled) {
|
|
260
|
+
background: var(--pw-color-primary-light);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.pw-shell__btn--cancel {
|
|
264
|
+
color: var(--pw-color-muted);
|
|
265
|
+
border-color: transparent;
|
|
266
|
+
background: transparent;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.pw-shell__btn--cancel:hover:not(:disabled) {
|
|
270
|
+
background: var(--pw-color-primary-light);
|
|
271
|
+
}
|
|
272
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot } from "@daltonr/pathwrite-core";
|
|
2
|
+
export type { PathData, PathDefinition, PathEngine, PathEvent, PathSnapshot, PathStep, PathStepContext, SerializedPathState } from "@daltonr/pathwrite-core";
|
|
3
|
+
export interface UsePathOptions {
|
|
4
|
+
/**
|
|
5
|
+
* An externally-managed `PathEngine` to subscribe to — for example, the engine
|
|
6
|
+
* returned by `restoreOrStart()` from `@daltonr/pathwrite-store-http`.
|
|
7
|
+
*
|
|
8
|
+
* When provided:
|
|
9
|
+
* - `usePath` will **not** create its own engine.
|
|
10
|
+
* - The snapshot is seeded immediately from the engine's current state.
|
|
11
|
+
* - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
|
|
12
|
+
* - `PathShell` will skip its own `autoStart` call.
|
|
13
|
+
*/
|
|
14
|
+
engine?: PathEngine;
|
|
15
|
+
/** Called for every engine event (stateChanged, completed, cancelled, resumed). */
|
|
16
|
+
onEvent?: (event: PathEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface UsePathReturn<TData extends PathData = PathData> {
|
|
19
|
+
/** Current path snapshot, or `null` when no path is active. Reactive via `$state`. */
|
|
20
|
+
readonly snapshot: PathSnapshot<TData> | null;
|
|
21
|
+
/** Start (or restart) a path. */
|
|
22
|
+
start: (path: PathDefinition<any>, initialData?: PathData) => Promise<void>;
|
|
23
|
+
/** 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. */
|
|
24
|
+
startSubPath: (path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>) => Promise<void>;
|
|
25
|
+
/** Advance one step. Completes the path on the last step. */
|
|
26
|
+
next: () => Promise<void>;
|
|
27
|
+
/** 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. */
|
|
28
|
+
previous: () => Promise<void>;
|
|
29
|
+
/** Cancel the active path (or sub-path). */
|
|
30
|
+
cancel: () => Promise<void>;
|
|
31
|
+
/** Jump directly to a step by ID. Calls onLeave / onEnter but bypasses guards and shouldSkip. */
|
|
32
|
+
goToStep: (stepId: string) => Promise<void>;
|
|
33
|
+
/** 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. */
|
|
34
|
+
goToStepChecked: (stepId: string) => Promise<void>;
|
|
35
|
+
/** 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. */
|
|
36
|
+
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Tear down any active path (without firing hooks) and immediately start the
|
|
39
|
+
* given path fresh. Safe to call whether or not a path is currently active.
|
|
40
|
+
* Use for "Start over" / retry flows without remounting the component.
|
|
41
|
+
*/
|
|
42
|
+
restart: (path: PathDefinition<any>, initialData?: PathData) => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a Pathwrite engine with Svelte 5 runes-based reactivity.
|
|
46
|
+
* Call this from inside a Svelte component to get a reactive snapshot.
|
|
47
|
+
* Cleanup is automatic via onDestroy.
|
|
48
|
+
*
|
|
49
|
+
* **Note:** `snapshot` is a reactive getter — access it via the returned
|
|
50
|
+
* object (e.g. `path.snapshot`). Destructuring `snapshot` will lose reactivity.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```svelte
|
|
54
|
+
* <script lang="ts">
|
|
55
|
+
* import { usePath } from '@daltonr/pathwrite-svelte';
|
|
56
|
+
*
|
|
57
|
+
* const path = usePath();
|
|
58
|
+
*
|
|
59
|
+
* onMount(() => {
|
|
60
|
+
* path.start(myPath, { name: '' });
|
|
61
|
+
* });
|
|
62
|
+
* </script>
|
|
63
|
+
*
|
|
64
|
+
* {#if path.snapshot}
|
|
65
|
+
* <h2>{path.snapshot.stepId}</h2>
|
|
66
|
+
* <button onclick={path.previous} disabled={path.snapshot.isFirstStep}>Previous</button>
|
|
67
|
+
* <button onclick={path.next} disabled={!path.snapshot.canMoveNext}>Next</button>
|
|
68
|
+
* {/if}
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData>;
|
|
72
|
+
export interface PathContext<TData extends PathData = PathData> {
|
|
73
|
+
readonly snapshot: PathSnapshot<TData> | null;
|
|
74
|
+
next: () => Promise<void>;
|
|
75
|
+
previous: () => Promise<void>;
|
|
76
|
+
cancel: () => Promise<void>;
|
|
77
|
+
goToStep: (stepId: string) => Promise<void>;
|
|
78
|
+
goToStepChecked: (stepId: string) => Promise<void>;
|
|
79
|
+
setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
|
|
80
|
+
restart: () => Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the PathContext from a parent PathShell component.
|
|
84
|
+
* Use this inside step components to access the path engine.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```svelte
|
|
88
|
+
* <script lang="ts">
|
|
89
|
+
* import { getPathContext } from '@daltonr/pathwrite-svelte';
|
|
90
|
+
*
|
|
91
|
+
* const ctx = getPathContext();
|
|
92
|
+
* </script>
|
|
93
|
+
*
|
|
94
|
+
* <input value={ctx.snapshot?.data.name}
|
|
95
|
+
* oninput={(e) => ctx.setData('name', e.target.value)} />
|
|
96
|
+
* <button onclick={ctx.next}>Next</button>
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function getPathContext<TData extends PathData = PathData>(): PathContext<TData>;
|
|
100
|
+
/**
|
|
101
|
+
* Internal: Set the PathContext for child components.
|
|
102
|
+
* Used by PathShell component.
|
|
103
|
+
*/
|
|
104
|
+
export declare function setPathContext<TData extends PathData = PathData>(ctx: PathContext<TData>): void;
|
|
105
|
+
/**
|
|
106
|
+
* Create a two-way binding helper for form inputs.
|
|
107
|
+
* Returns an object with a reactive `value` property.
|
|
108
|
+
*
|
|
109
|
+
* @param getSnapshot - A getter function returning the current snapshot (e.g. `() => path.snapshot`)
|
|
110
|
+
* @param setData - The `setData` function from `usePath()`
|
|
111
|
+
* @param key - The data key to bind
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```svelte
|
|
115
|
+
* <script lang="ts">
|
|
116
|
+
* import { usePath, bindData } from '@daltonr/pathwrite-svelte';
|
|
117
|
+
*
|
|
118
|
+
* const path = usePath();
|
|
119
|
+
* const name = bindData(() => path.snapshot, path.setData, 'name');
|
|
120
|
+
* </script>
|
|
121
|
+
*
|
|
122
|
+
* <input value={name.value} oninput={(e) => name.value = e.target.value} />
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function bindData<TData extends PathData, K extends string & keyof TData>(getSnapshot: () => PathSnapshot<TData> | null, setData: <Key extends string & keyof TData>(key: Key, value: TData[Key]) => Promise<void>, key: K): {
|
|
126
|
+
readonly value: TData[K];
|
|
127
|
+
set: (value: TData[K]) => void;
|
|
128
|
+
};
|
|
129
|
+
export { default as PathShell } from "./PathShell.svelte";
|
|
130
|
+
//# sourceMappingURL=index.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { onDestroy, getContext, setContext } from "svelte";
|
|
2
|
+
import { PathEngine as PathEngineClass } from "@daltonr/pathwrite-core";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// usePath - Runes-based API for Svelte 5
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* Create a Pathwrite engine with Svelte 5 runes-based reactivity.
|
|
8
|
+
* Call this from inside a Svelte component to get a reactive snapshot.
|
|
9
|
+
* Cleanup is automatic via onDestroy.
|
|
10
|
+
*
|
|
11
|
+
* **Note:** `snapshot` is a reactive getter — access it via the returned
|
|
12
|
+
* object (e.g. `path.snapshot`). Destructuring `snapshot` will lose reactivity.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```svelte
|
|
16
|
+
* <script lang="ts">
|
|
17
|
+
* import { usePath } from '@daltonr/pathwrite-svelte';
|
|
18
|
+
*
|
|
19
|
+
* const path = usePath();
|
|
20
|
+
*
|
|
21
|
+
* onMount(() => {
|
|
22
|
+
* path.start(myPath, { name: '' });
|
|
23
|
+
* });
|
|
24
|
+
* </script>
|
|
25
|
+
*
|
|
26
|
+
* {#if path.snapshot}
|
|
27
|
+
* <h2>{path.snapshot.stepId}</h2>
|
|
28
|
+
* <button onclick={path.previous} disabled={path.snapshot.isFirstStep}>Previous</button>
|
|
29
|
+
* <button onclick={path.next} disabled={!path.snapshot.canMoveNext}>Next</button>
|
|
30
|
+
* {/if}
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function usePath(options) {
|
|
34
|
+
const engine = options?.engine ?? new PathEngineClass();
|
|
35
|
+
// Reactive snapshot via $state rune
|
|
36
|
+
let _snapshot = $state(engine.snapshot());
|
|
37
|
+
// Subscribe to engine events
|
|
38
|
+
const unsubscribe = engine.subscribe((event) => {
|
|
39
|
+
if (event.type === "stateChanged" || event.type === "resumed") {
|
|
40
|
+
_snapshot = event.snapshot;
|
|
41
|
+
}
|
|
42
|
+
else if (event.type === "completed" || event.type === "cancelled") {
|
|
43
|
+
_snapshot = null;
|
|
44
|
+
}
|
|
45
|
+
options?.onEvent?.(event);
|
|
46
|
+
});
|
|
47
|
+
// Auto-cleanup when component is destroyed
|
|
48
|
+
onDestroy(unsubscribe);
|
|
49
|
+
const start = (path, initialData = {}) => engine.start(path, initialData);
|
|
50
|
+
const startSubPath = (path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta);
|
|
51
|
+
const next = () => engine.next();
|
|
52
|
+
const previous = () => engine.previous();
|
|
53
|
+
const cancel = () => engine.cancel();
|
|
54
|
+
const goToStep = (stepId) => engine.goToStep(stepId);
|
|
55
|
+
const goToStepChecked = (stepId) => engine.goToStepChecked(stepId);
|
|
56
|
+
const setData = ((key, value) => engine.setData(key, value));
|
|
57
|
+
const restart = (path, initialData = {}) => engine.restart(path, initialData);
|
|
58
|
+
return {
|
|
59
|
+
get snapshot() { return _snapshot; },
|
|
60
|
+
start,
|
|
61
|
+
startSubPath,
|
|
62
|
+
next,
|
|
63
|
+
previous,
|
|
64
|
+
cancel,
|
|
65
|
+
goToStep,
|
|
66
|
+
goToStepChecked,
|
|
67
|
+
setData,
|
|
68
|
+
restart
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Context API for PathShell
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
|
|
75
|
+
/**
|
|
76
|
+
* Get the PathContext from a parent PathShell component.
|
|
77
|
+
* Use this inside step components to access the path engine.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```svelte
|
|
81
|
+
* <script lang="ts">
|
|
82
|
+
* import { getPathContext } from '@daltonr/pathwrite-svelte';
|
|
83
|
+
*
|
|
84
|
+
* const ctx = getPathContext();
|
|
85
|
+
* </script>
|
|
86
|
+
*
|
|
87
|
+
* <input value={ctx.snapshot?.data.name}
|
|
88
|
+
* oninput={(e) => ctx.setData('name', e.target.value)} />
|
|
89
|
+
* <button onclick={ctx.next}>Next</button>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function getPathContext() {
|
|
93
|
+
const ctx = getContext(PATH_CONTEXT_KEY);
|
|
94
|
+
if (!ctx) {
|
|
95
|
+
throw new Error("getPathContext() must be called from a component inside a <PathShell>. " +
|
|
96
|
+
"Ensure the PathShell component is a parent in the component tree.");
|
|
97
|
+
}
|
|
98
|
+
return ctx;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Internal: Set the PathContext for child components.
|
|
102
|
+
* Used by PathShell component.
|
|
103
|
+
*/
|
|
104
|
+
export function setPathContext(ctx) {
|
|
105
|
+
setContext(PATH_CONTEXT_KEY, ctx);
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Helper for binding form inputs
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* Create a two-way binding helper for form inputs.
|
|
112
|
+
* Returns an object with a reactive `value` property.
|
|
113
|
+
*
|
|
114
|
+
* @param getSnapshot - A getter function returning the current snapshot (e.g. `() => path.snapshot`)
|
|
115
|
+
* @param setData - The `setData` function from `usePath()`
|
|
116
|
+
* @param key - The data key to bind
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```svelte
|
|
120
|
+
* <script lang="ts">
|
|
121
|
+
* import { usePath, bindData } from '@daltonr/pathwrite-svelte';
|
|
122
|
+
*
|
|
123
|
+
* const path = usePath();
|
|
124
|
+
* const name = bindData(() => path.snapshot, path.setData, 'name');
|
|
125
|
+
* </script>
|
|
126
|
+
*
|
|
127
|
+
* <input value={name.value} oninput={(e) => name.value = e.target.value} />
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function bindData(getSnapshot, setData, key) {
|
|
131
|
+
return {
|
|
132
|
+
get value() {
|
|
133
|
+
return (getSnapshot()?.data[key] ?? undefined);
|
|
134
|
+
},
|
|
135
|
+
set(value) {
|
|
136
|
+
setData(key, value);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Export PathShell component
|
|
141
|
+
export { default as PathShell } from "./PathShell.svelte";
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@daltonr/pathwrite-svelte",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Svelte 5 adapter for @daltonr/pathwrite-core — runes-based reactive bindings and optional PathShell component.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/richardadalton/pathwrite.git",
|
|
10
|
+
"directory": "packages/svelte-adapter"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"wizard",
|
|
14
|
+
"stepper",
|
|
15
|
+
"path",
|
|
16
|
+
"svelte",
|
|
17
|
+
"runes",
|
|
18
|
+
"multi-step",
|
|
19
|
+
"workflow"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": [
|
|
22
|
+
"**/*.css"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.svelte.d.ts",
|
|
27
|
+
"svelte": "./dist/index.svelte.js",
|
|
28
|
+
"import": "./dist/index.svelte.js"
|
|
29
|
+
},
|
|
30
|
+
"./PathShell.svelte": {
|
|
31
|
+
"svelte": "./dist/PathShell.svelte",
|
|
32
|
+
"import": "./dist/PathShell.svelte"
|
|
33
|
+
},
|
|
34
|
+
"./styles.css": "./dist/index.css"
|
|
35
|
+
},
|
|
36
|
+
"svelte": "./dist/index.svelte.js",
|
|
37
|
+
"main": "dist/index.svelte.js",
|
|
38
|
+
"types": "dist/index.svelte.d.ts",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"src",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "svelte-package -i src -o dist && cp ../shell.css dist/index.css",
|
|
47
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
48
|
+
"prepublishOnly": "test -d dist && echo 'dist already built, skipping' || (npm run clean && npm run build)"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"svelte": ">=5.0.0"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@daltonr/pathwrite-core": "^0.5.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@sveltejs/package": "^2.5.7",
|
|
58
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
59
|
+
"@testing-library/svelte": "^5.0.0",
|
|
60
|
+
"svelte": "^5.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|