@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/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
+ }