@classic-homes/theme-svelte 0.1.7 → 0.1.9

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,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const Alert: import("svelte").Component<{
3
3
  [key: string]: unknown;
4
- variant?: "default" | "destructive" | "error" | "success" | "warning" | "info" | undefined;
4
+ variant?: "default" | "destructive" | "warning" | "error" | "success" | "info" | undefined;
5
5
  class?: string;
6
6
  children: Snippet;
7
7
  }, {}, "">;
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const Badge: import("svelte").Component<{
3
3
  [key: string]: unknown;
4
- variant?: "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | undefined;
4
+ variant?: "default" | "secondary" | "destructive" | "outline" | "warning" | "success" | "info" | undefined;
5
5
  size?: "default" | "sm" | "dot" | undefined;
6
6
  clickable?: boolean;
7
7
  class?: string;
@@ -5,8 +5,14 @@
5
5
  * Features:
6
6
  * - Clean border styling with sharp look
7
7
  * - Optional hover shadow effect for interactive cards
8
- * - Featured variant with thicker border (border-2)
8
+ * - Multiple variants: default, featured, highlight, warning
9
9
  * - Consistent with brand guidelines
10
+ *
11
+ * Variants:
12
+ * - default: Standard card with 1px border
13
+ * - featured: Thicker 2px border for emphasis
14
+ * - highlight: Teal border (brand-core-1) for positive/info callouts
15
+ * - warning: Red border (brand-red-primary) for warnings/alerts
10
16
  */
11
17
  import type { Snippet } from 'svelte';
12
18
  import { cn } from '../utils.js';
@@ -14,9 +20,15 @@
14
20
  interface Props {
15
21
  /** Enable hover shadow effect for interactive cards */
16
22
  interactive?: boolean;
17
- /** Card variant - default has 1px border, featured has 2px border */
18
- variant?: 'default' | 'featured';
19
- /** Border color for featured variant */
23
+ /**
24
+ * Card variant
25
+ * - default: Standard 1px border
26
+ * - featured: Thicker 2px border
27
+ * - highlight: Teal border for positive/info callouts
28
+ * - warning: Red border for warnings/alerts
29
+ */
30
+ variant?: 'default' | 'featured' | 'highlight' | 'warning';
31
+ /** Border color override (ignored for highlight/warning variants) */
20
32
  borderColor?: 'default' | 'primary' | 'muted';
21
33
  /** Additional classes */
22
34
  class?: string;
@@ -39,15 +51,26 @@
39
51
  primary: 'border-primary',
40
52
  muted: 'border-gray-200',
41
53
  };
54
+
55
+ // Variant-specific border colors (highlight and warning override borderColor)
56
+ const variantBorderClasses = $derived({
57
+ default: borderColorClasses[borderColor],
58
+ featured: borderColorClasses[borderColor],
59
+ highlight: 'border-brand-core-1',
60
+ warning: 'border-brand-red-primary',
61
+ });
62
+
63
+ // Variants with thick borders
64
+ const thickBorderVariants = ['featured', 'highlight', 'warning'];
42
65
  </script>
43
66
 
44
67
  <div
45
68
  class={cn(
46
69
  'rounded-md bg-card text-card-foreground',
47
- // Border thickness based on variant
48
- variant === 'featured' ? 'border-2' : 'border',
49
- // Border color
50
- borderColorClasses[borderColor],
70
+ // Border thickness - thick for featured, highlight, warning
71
+ thickBorderVariants.includes(variant) ? 'border-2' : 'border',
72
+ // Border color based on variant
73
+ variantBorderClasses[variant],
51
74
  // Interactive hover effect
52
75
  interactive && 'hover:shadow-lg transition-shadow duration-200',
53
76
  className
@@ -4,16 +4,28 @@
4
4
  * Features:
5
5
  * - Clean border styling with sharp look
6
6
  * - Optional hover shadow effect for interactive cards
7
- * - Featured variant with thicker border (border-2)
7
+ * - Multiple variants: default, featured, highlight, warning
8
8
  * - Consistent with brand guidelines
9
+ *
10
+ * Variants:
11
+ * - default: Standard card with 1px border
12
+ * - featured: Thicker 2px border for emphasis
13
+ * - highlight: Teal border (brand-core-1) for positive/info callouts
14
+ * - warning: Red border (brand-red-primary) for warnings/alerts
9
15
  */
10
16
  import type { Snippet } from 'svelte';
11
17
  interface Props {
12
18
  /** Enable hover shadow effect for interactive cards */
13
19
  interactive?: boolean;
14
- /** Card variant - default has 1px border, featured has 2px border */
15
- variant?: 'default' | 'featured';
16
- /** Border color for featured variant */
20
+ /**
21
+ * Card variant
22
+ * - default: Standard 1px border
23
+ * - featured: Thicker 2px border
24
+ * - highlight: Teal border for positive/info callouts
25
+ * - warning: Red border for warnings/alerts
26
+ */
27
+ variant?: 'default' | 'featured' | 'highlight' | 'warning';
28
+ /** Border color override (ignored for highlight/warning variants) */
17
29
  borderColor?: 'default' | 'primary' | 'muted';
18
30
  /** Additional classes */
19
31
  class?: string;
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const Toast: import("svelte").Component<{
3
3
  [key: string]: unknown;
4
- type?: "error" | "success" | "warning" | "info" | undefined;
4
+ type?: "warning" | "error" | "success" | "info" | undefined;
5
5
  title?: string;
6
6
  message: string;
7
7
  class?: string;
@@ -6,3 +6,4 @@
6
6
  */
7
7
  export { useForm, type UseFormOptions, type UseFormReturn, type FieldError, type FormState, type InferFormData, } from './useForm.svelte.js';
8
8
  export { useAsync, runAsync, type UseAsyncOptions, type UseAsyncReturn, } from './useAsync.svelte.js';
9
+ export { usePersistedForm, type UsePersistedFormOptions, type UsePersistedFormReturn, type InferPersistedFormData, } from './usePersistedForm.svelte.js';
@@ -8,3 +8,5 @@
8
8
  export { useForm, } from './useForm.svelte.js';
9
9
  // Async operation composable
10
10
  export { useAsync, runAsync, } from './useAsync.svelte.js';
11
+ // Persisted form composable with localStorage draft saving
12
+ export { usePersistedForm, } from './usePersistedForm.svelte.js';
@@ -84,6 +84,8 @@ export interface UseFormReturn<T extends z.ZodObject<z.ZodRawShape>> {
84
84
  readonly data: z.infer<T>;
85
85
  /** Field errors (reactive) */
86
86
  readonly errors: Record<string, string>;
87
+ /** Global error message for API/submission errors (reactive) */
88
+ readonly globalError: string;
87
89
  /** Whether form is submitting (reactive) */
88
90
  readonly isSubmitting: boolean;
89
91
  /** Whether form has been submitted (reactive) */
@@ -110,8 +112,12 @@ export interface UseFormReturn<T extends z.ZodObject<z.ZodRawShape>> {
110
112
  clearErrors: () => void;
111
113
  /** Set a specific error */
112
114
  setError: (field: string, message: string) => void;
115
+ /** Set the global error message */
116
+ setGlobalError: (message: string) => void;
113
117
  /** Mark a field as dirty */
114
118
  markDirty: (field: string) => void;
119
+ /** Handle field blur - validates the field and marks dirty */
120
+ handleBlur: (field: keyof z.infer<T>) => void;
115
121
  }
116
122
  /**
117
123
  * Create a form handler with Zod validation and Svelte 5 runes
@@ -51,12 +51,13 @@ export function useForm(options) {
51
51
  // Reactive state using Svelte 5 runes
52
52
  let data = $state(cloneInitialValues());
53
53
  let errors = $state({});
54
+ let globalError = $state('');
54
55
  let isSubmitting = $state(false);
55
56
  let isSubmitted = $state(false);
56
57
  let dirtyFields = $state(new Set());
57
58
  // Derived state
58
59
  const isDirty = $derived(dirtyFields.size > 0);
59
- const isValid = $derived(Object.keys(errors).length === 0);
60
+ const isValid = $derived(Object.keys(errors).length === 0 && globalError === '');
60
61
  /**
61
62
  * Set nested value in object using dot notation path
62
63
  */
@@ -159,6 +160,9 @@ export function useForm(options) {
159
160
  */
160
161
  async function handleSubmit(event) {
161
162
  event?.preventDefault();
163
+ // Clear previous errors
164
+ errors = {};
165
+ globalError = '';
162
166
  isSubmitted = true;
163
167
  // Validate all fields
164
168
  if (!validate()) {
@@ -180,38 +184,56 @@ export function useForm(options) {
180
184
  }
181
185
  catch (error) {
182
186
  const err = error instanceof Error ? error : new Error(String(error));
187
+ const message = errorMessage || err.message || 'An error occurred';
188
+ // Set global error for display in form
189
+ globalError = message;
183
190
  if (onError) {
184
191
  onError(err);
185
192
  }
186
193
  if (showToastOnError) {
187
- toastStore.error(errorMessage || err.message || 'An error occurred');
194
+ toastStore.error(message);
188
195
  }
189
196
  }
190
197
  finally {
191
198
  isSubmitting = false;
192
199
  }
193
200
  }
201
+ /**
202
+ * Handle field blur - validates the field and marks dirty
203
+ */
204
+ function handleBlur(field) {
205
+ markDirty(field);
206
+ validateField(field);
207
+ }
194
208
  /**
195
209
  * Reset form to initial values
196
210
  */
197
211
  function reset() {
198
212
  data = cloneInitialValues();
199
213
  errors = {};
214
+ globalError = '';
200
215
  isSubmitted = false;
201
216
  dirtyFields = new Set();
202
217
  }
203
218
  /**
204
- * Clear all errors
219
+ * Clear all errors (field and global)
205
220
  */
206
221
  function clearErrors() {
207
222
  errors = {};
223
+ globalError = '';
208
224
  }
209
225
  /**
210
- * Set a specific error manually
226
+ * Set a specific field error manually
211
227
  */
212
228
  function setError(field, message) {
213
229
  errors = { ...errors, [field]: message };
214
230
  }
231
+ /**
232
+ * Set the global error message
233
+ */
234
+ function setGlobalError(message) {
235
+ globalError = message;
236
+ }
215
237
  return {
216
238
  get data() {
217
239
  return data;
@@ -219,6 +241,9 @@ export function useForm(options) {
219
241
  get errors() {
220
242
  return errors;
221
243
  },
244
+ get globalError() {
245
+ return globalError;
246
+ },
222
247
  get isSubmitting() {
223
248
  return isSubmitting;
224
249
  },
@@ -240,6 +265,8 @@ export function useForm(options) {
240
265
  reset,
241
266
  clearErrors,
242
267
  setError,
268
+ setGlobalError,
243
269
  markDirty,
270
+ handleBlur,
244
271
  };
245
272
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * usePersistedForm - Form state management with localStorage persistence
3
+ *
4
+ * Composes with useForm to add:
5
+ * - Automatic draft saving to localStorage with debounce
6
+ * - Draft restoration on mount
7
+ * - Configurable expiration time
8
+ * - Field exclusion from persistence (e.g., consent fields)
9
+ * - Duplicate submission prevention with cooldown
10
+ *
11
+ * @example
12
+ * ```svelte
13
+ * <script lang="ts">
14
+ * import { usePersistedForm } from '@classic-homes/theme-svelte';
15
+ * import { z } from 'zod';
16
+ *
17
+ * const schema = z.object({
18
+ * email: z.string().email(),
19
+ * name: z.string().min(1),
20
+ * consent: z.boolean(),
21
+ * });
22
+ *
23
+ * const form = usePersistedForm({
24
+ * schema,
25
+ * initialValues: { email: '', name: '', consent: false },
26
+ * onSubmit: async (data) => {
27
+ * await submitForm(data);
28
+ * },
29
+ * storageKey: 'my-form-draft',
30
+ * excludeFields: ['consent'], // Don't persist consent
31
+ * expirationMs: 7 * 24 * 60 * 60 * 1000, // 7 days
32
+ * onDraftRestored: () => {
33
+ * console.log('Draft was restored');
34
+ * },
35
+ * });
36
+ * </script>
37
+ *
38
+ * <form onsubmit={form.handleSubmit}>
39
+ * <input bind:value={form.data.email} />
40
+ * {#if form.draftRestored}
41
+ * <div>Your previous draft has been restored.</div>
42
+ * {/if}
43
+ * </form>
44
+ * ```
45
+ */
46
+ import { z } from 'zod';
47
+ import { type UseFormOptions, type UseFormReturn } from './useForm.svelte.js';
48
+ export interface UsePersistedFormOptions<T extends z.ZodObject<z.ZodRawShape>> extends UseFormOptions<T> {
49
+ /** localStorage key for draft persistence */
50
+ storageKey: string;
51
+ /** Fields to exclude from persistence (e.g., consent fields that require re-confirmation) */
52
+ excludeFields?: (keyof z.infer<T>)[];
53
+ /** Expiration time in milliseconds (default: 7 days) */
54
+ expirationMs?: number;
55
+ /** Callback when a draft is restored */
56
+ onDraftRestored?: () => void;
57
+ /** Debounce time for saving drafts in milliseconds (default: 500ms) */
58
+ debounceMs?: number;
59
+ /** Cooldown time between submissions in milliseconds (default: 5000ms) */
60
+ submitCooldownMs?: number;
61
+ }
62
+ export interface UsePersistedFormReturn<T extends z.ZodObject<z.ZodRawShape>> extends UseFormReturn<T> {
63
+ /** Whether a draft was restored on mount (reactive) */
64
+ readonly draftRestored: boolean;
65
+ /** Clear the stored draft */
66
+ clearDraft: () => void;
67
+ /** Dismiss the draft restored notification */
68
+ dismissDraftNotification: () => void;
69
+ }
70
+ /**
71
+ * Create a form handler with localStorage persistence
72
+ * Composes with useForm for all base functionality
73
+ */
74
+ export declare function usePersistedForm<T extends z.ZodObject<z.ZodRawShape>>(options: UsePersistedFormOptions<T>): UsePersistedFormReturn<T>;
75
+ /**
76
+ * Type helper to extract form data type from schema
77
+ */
78
+ export type InferPersistedFormData<T extends z.ZodObject<z.ZodRawShape>> = z.infer<T>;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * usePersistedForm - Form state management with localStorage persistence
3
+ *
4
+ * Composes with useForm to add:
5
+ * - Automatic draft saving to localStorage with debounce
6
+ * - Draft restoration on mount
7
+ * - Configurable expiration time
8
+ * - Field exclusion from persistence (e.g., consent fields)
9
+ * - Duplicate submission prevention with cooldown
10
+ *
11
+ * @example
12
+ * ```svelte
13
+ * <script lang="ts">
14
+ * import { usePersistedForm } from '@classic-homes/theme-svelte';
15
+ * import { z } from 'zod';
16
+ *
17
+ * const schema = z.object({
18
+ * email: z.string().email(),
19
+ * name: z.string().min(1),
20
+ * consent: z.boolean(),
21
+ * });
22
+ *
23
+ * const form = usePersistedForm({
24
+ * schema,
25
+ * initialValues: { email: '', name: '', consent: false },
26
+ * onSubmit: async (data) => {
27
+ * await submitForm(data);
28
+ * },
29
+ * storageKey: 'my-form-draft',
30
+ * excludeFields: ['consent'], // Don't persist consent
31
+ * expirationMs: 7 * 24 * 60 * 60 * 1000, // 7 days
32
+ * onDraftRestored: () => {
33
+ * console.log('Draft was restored');
34
+ * },
35
+ * });
36
+ * </script>
37
+ *
38
+ * <form onsubmit={form.handleSubmit}>
39
+ * <input bind:value={form.data.email} />
40
+ * {#if form.draftRestored}
41
+ * <div>Your previous draft has been restored.</div>
42
+ * {/if}
43
+ * </form>
44
+ * ```
45
+ */
46
+ import { useForm } from './useForm.svelte.js';
47
+ import { toastStore } from '../stores/toast.svelte.js';
48
+ const DEFAULT_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
49
+ const DEFAULT_DEBOUNCE_MS = 500;
50
+ const DEFAULT_SUBMIT_COOLDOWN_MS = 5000;
51
+ /**
52
+ * Check if localStorage is available (also checks for browser environment)
53
+ */
54
+ function isStorageAvailable() {
55
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
56
+ return false;
57
+ }
58
+ try {
59
+ const test = '__storage_test__';
60
+ localStorage.setItem(test, test);
61
+ localStorage.removeItem(test);
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ /**
69
+ * Create a form handler with localStorage persistence
70
+ * Composes with useForm for all base functionality
71
+ */
72
+ export function usePersistedForm(options) {
73
+ const { storageKey, excludeFields = [], expirationMs = DEFAULT_EXPIRATION_MS, onDraftRestored, debounceMs = DEFAULT_DEBOUNCE_MS, submitCooldownMs = DEFAULT_SUBMIT_COOLDOWN_MS, onSubmit, initialValues, ...baseOptions } = options;
74
+ // Persistence state
75
+ let draftRestored = $state(false);
76
+ let lastSubmitTime = $state(0);
77
+ let debounceTimer = null;
78
+ /**
79
+ * Load draft from localStorage
80
+ */
81
+ function loadDraft() {
82
+ if (!isStorageAvailable())
83
+ return null;
84
+ try {
85
+ const stored = localStorage.getItem(storageKey);
86
+ if (!stored)
87
+ return null;
88
+ const draft = JSON.parse(stored);
89
+ // Check if draft has expired
90
+ if (Date.now() - draft.timestamp > expirationMs) {
91
+ clearDraft();
92
+ return null;
93
+ }
94
+ return draft.data;
95
+ }
96
+ catch {
97
+ // Silently fail - corrupt data
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Clear draft from localStorage
103
+ */
104
+ function clearDraft() {
105
+ if (!isStorageAvailable())
106
+ return;
107
+ try {
108
+ localStorage.removeItem(storageKey);
109
+ }
110
+ catch {
111
+ // Silently fail
112
+ }
113
+ }
114
+ /**
115
+ * Get the initial values, merging with any restored draft
116
+ */
117
+ function getInitialValues() {
118
+ const draft = loadDraft();
119
+ if (draft && Object.keys(draft).length > 0) {
120
+ // Schedule callback for after initialization
121
+ if (typeof window !== 'undefined') {
122
+ queueMicrotask(() => {
123
+ draftRestored = true;
124
+ onDraftRestored?.();
125
+ });
126
+ }
127
+ // Merge draft with initial values (keeps excluded fields at defaults)
128
+ return { ...JSON.parse(JSON.stringify(initialValues)), ...draft };
129
+ }
130
+ return JSON.parse(JSON.stringify(initialValues));
131
+ }
132
+ /**
133
+ * Wrapped onSubmit with cooldown and draft clearing
134
+ */
135
+ async function wrappedOnSubmit(data) {
136
+ // Check for duplicate submission
137
+ const now = Date.now();
138
+ if (now - lastSubmitTime < submitCooldownMs) {
139
+ toastStore.warning('Please wait before submitting again');
140
+ throw new Error('Submission cooldown active');
141
+ }
142
+ try {
143
+ await onSubmit(data);
144
+ lastSubmitTime = now;
145
+ // Clear draft on successful submission
146
+ clearDraft();
147
+ }
148
+ catch (error) {
149
+ // Reset cooldown on error to allow retry
150
+ lastSubmitTime = 0;
151
+ throw error;
152
+ }
153
+ }
154
+ // Create base form with potentially restored initial values
155
+ const baseForm = useForm({
156
+ ...baseOptions,
157
+ initialValues: getInitialValues(),
158
+ onSubmit: wrappedOnSubmit,
159
+ });
160
+ /**
161
+ * Get data to persist (excludes specified fields)
162
+ */
163
+ function getDataToPersist() {
164
+ const dataCopy = { ...baseForm.data };
165
+ for (const field of excludeFields) {
166
+ delete dataCopy[field];
167
+ }
168
+ return dataCopy;
169
+ }
170
+ /**
171
+ * Save draft to localStorage with debounce
172
+ */
173
+ function saveDraft() {
174
+ if (!isStorageAvailable())
175
+ return;
176
+ if (debounceTimer) {
177
+ clearTimeout(debounceTimer);
178
+ }
179
+ debounceTimer = setTimeout(() => {
180
+ try {
181
+ const draft = {
182
+ data: getDataToPersist(),
183
+ timestamp: Date.now(),
184
+ };
185
+ localStorage.setItem(storageKey, JSON.stringify(draft));
186
+ }
187
+ catch (error) {
188
+ // Silently fail - localStorage may be full or unavailable
189
+ console.warn('Failed to save form draft', error);
190
+ }
191
+ }, debounceMs);
192
+ }
193
+ // Watch for form changes and save draft
194
+ $effect(() => {
195
+ // Access data to create dependency
196
+ JSON.stringify(baseForm.data);
197
+ if (baseForm.isDirty) {
198
+ saveDraft();
199
+ }
200
+ });
201
+ /**
202
+ * Extended reset that also clears the draft
203
+ */
204
+ function reset() {
205
+ baseForm.reset();
206
+ draftRestored = false;
207
+ clearDraft();
208
+ }
209
+ /**
210
+ * Dismiss the draft restored notification
211
+ */
212
+ function dismissDraftNotification() {
213
+ draftRestored = false;
214
+ }
215
+ return {
216
+ // Delegate all base form properties
217
+ get data() {
218
+ return baseForm.data;
219
+ },
220
+ get errors() {
221
+ return baseForm.errors;
222
+ },
223
+ get globalError() {
224
+ return baseForm.globalError;
225
+ },
226
+ get isSubmitting() {
227
+ return baseForm.isSubmitting;
228
+ },
229
+ get isSubmitted() {
230
+ return baseForm.isSubmitted;
231
+ },
232
+ get isDirty() {
233
+ return baseForm.isDirty;
234
+ },
235
+ get isValid() {
236
+ return baseForm.isValid;
237
+ },
238
+ // Persistence-specific properties
239
+ get draftRestored() {
240
+ return draftRestored;
241
+ },
242
+ // Delegate base form methods
243
+ setField: baseForm.setField,
244
+ setFields: baseForm.setFields,
245
+ setNestedField: baseForm.setNestedField,
246
+ validateField: baseForm.validateField,
247
+ validate: baseForm.validate,
248
+ handleSubmit: baseForm.handleSubmit,
249
+ clearErrors: baseForm.clearErrors,
250
+ setError: baseForm.setError,
251
+ setGlobalError: baseForm.setGlobalError,
252
+ markDirty: baseForm.markDirty,
253
+ handleBlur: baseForm.handleBlur,
254
+ // Override reset to also clear draft
255
+ reset,
256
+ // Persistence-specific methods
257
+ clearDraft,
258
+ dismissDraftNotification,
259
+ };
260
+ }
@@ -72,5 +72,5 @@ export { cn } from './utils.js';
72
72
  export { tv, type VariantProps } from 'tailwind-variants';
73
73
  export { validateNonEmptyArray, validateRequired, validateOneOf, validateRange, validateProps, createValidator, type ValidationResult, } from './validation.js';
74
74
  export { perfStart, perfEnd, measure, measureAsync, getPerformanceEntries, clearPerformanceEntries, createPerfMonitor, type PerformanceMark, } from './performance.js';
75
- export { useForm, useAsync, runAsync, type UseFormOptions, type UseFormReturn, type FieldError, type FormState, type InferFormData, type UseAsyncOptions, type UseAsyncReturn, } from './composables/index.js';
75
+ export { useForm, useAsync, runAsync, usePersistedForm, type UseFormOptions, type UseFormReturn, type FieldError, type FormState, type InferFormData, type UseAsyncOptions, type UseAsyncReturn, type UsePersistedFormOptions, type UsePersistedFormReturn, type InferPersistedFormData, } from './composables/index.js';
76
76
  export * from './schemas/index.js';
package/dist/lib/index.js CHANGED
@@ -89,6 +89,6 @@ export { validateNonEmptyArray, validateRequired, validateOneOf, validateRange,
89
89
  // Performance monitoring utilities (dev-only, tree-shaken in production)
90
90
  export { perfStart, perfEnd, measure, measureAsync, getPerformanceEntries, clearPerformanceEntries, createPerfMonitor, } from './performance.js';
91
91
  // Composables - reusable Svelte 5 state management patterns
92
- export { useForm, useAsync, runAsync, } from './composables/index.js';
92
+ export { useForm, useAsync, runAsync, usePersistedForm, } from './composables/index.js';
93
93
  // Validation schemas - Zod schemas for forms and data validation
94
94
  export * from './schemas/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-svelte",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",